001/* 002 * Copyright 2008-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2008-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds.controls; 037 038 039 040import java.util.ArrayList; 041import java.util.Collections; 042import java.util.LinkedHashMap; 043import java.util.List; 044import java.util.Map; 045 046import com.unboundid.asn1.ASN1Boolean; 047import com.unboundid.asn1.ASN1Element; 048import com.unboundid.asn1.ASN1Integer; 049import com.unboundid.asn1.ASN1OctetString; 050import com.unboundid.asn1.ASN1Sequence; 051import com.unboundid.ldap.sdk.Control; 052import com.unboundid.ldap.sdk.DecodeableControl; 053import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 054import com.unboundid.ldap.sdk.LDAPException; 055import com.unboundid.ldap.sdk.ResultCode; 056import com.unboundid.ldap.sdk.SearchResultEntry; 057import com.unboundid.util.Debug; 058import com.unboundid.util.NotMutable; 059import com.unboundid.util.NotNull; 060import com.unboundid.util.Nullable; 061import com.unboundid.util.StaticUtils; 062import com.unboundid.util.ThreadSafety; 063import com.unboundid.util.ThreadSafetyLevel; 064import com.unboundid.util.json.JSONBoolean; 065import com.unboundid.util.json.JSONField; 066import com.unboundid.util.json.JSONNumber; 067import com.unboundid.util.json.JSONObject; 068import com.unboundid.util.json.JSONValue; 069 070import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 071 072 073 074/** 075 * This class provides an implementation of the account usable response control, 076 * which may be returned with search result entries to provide information about 077 * the usability of the associated user accounts. 078 * <BR> 079 * <BLOCKQUOTE> 080 * <B>NOTE:</B> This class, and other classes within the 081 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 082 * supported for use against Ping Identity, UnboundID, and 083 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 084 * for proprietary functionality or for external specifications that are not 085 * considered stable or mature enough to be guaranteed to work in an 086 * interoperable way with other types of LDAP servers. 087 * </BLOCKQUOTE> 088 * <BR> 089 * Information that may be included in the account usable response control 090 * includes: 091 * <UL> 092 * <LI>{@code accountIsActive} -- Indicates that the account is active and may 093 * include the length of time in seconds until the password expires.</LI> 094 * <LI>{@code accountIsInactive} -- Indicates that the account has been locked 095 * or deactivated.</LI> 096 * <LI>{@code mustChangePassword} -- Indicates that the user must change his 097 * or her password before being allowed to perform any other 098 * operations.</LI> 099 * <LI>{@code passwordIsExpired} -- Indicates that the user's password has 100 * expired.</LI> 101 * <LI>{@code remainingGraceLogins} -- Indicates the number of grace logins 102 * remaining for the user.</LI> 103 * <LI>{@code secondsUntilUnlock} -- Indicates the length of time in seconds 104 * until the account will be automatically unlocked.</LI> 105 * </UL> 106 * See the {@link AccountUsableRequestControl} documentation for an example 107 * demonstrating the use of the account usable request and response controls. 108 * <BR><BR> 109 * This control was designed by Sun Microsystems and is not based on any RFC or 110 * Internet draft. The value of this control is encoded as follows: 111 * <BR><BR> 112 * <PRE> 113 * ACCOUNT_USABLE_RESPONSE ::= CHOICE { 114 * isUsable [0] INTEGER, -- Seconds until password expiration -- 115 * isNotUsable [1] MORE_INFO } 116 * 117 * MORE_INFO ::= SEQUENCE { 118 * accountIsInactive [0] BOOLEAN DEFAULT FALSE, 119 * mustChangePassword [1] BOOLEAN DEFAULT FALSE, 120 * passwordIsExpired [2] BOOLEAN DEFAULT FALSE, 121 * remainingGraceLogins [3] INTEGER OPTIONAL, 122 * secondsUntilUnlock [4] INTEGER OPTIONAL } 123 * </PRE> 124 */ 125@NotMutable() 126@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 127public final class AccountUsableResponseControl 128 extends Control 129 implements DecodeableControl 130{ 131 /** 132 * The OID (1.3.6.1.4.1.42.2.27.9.5.8) for the account usable response 133 * control. 134 */ 135 @NotNull public static final String ACCOUNT_USABLE_RESPONSE_OID = 136 "1.3.6.1.4.1.42.2.27.9.5.8"; 137 138 139 140 /** 141 * The BER type that will be used for the element that indicates the account 142 * is usable and provides the number of seconds until expiration. 143 */ 144 private static final byte TYPE_SECONDS_UNTIL_EXPIRATION = (byte) 0x80; 145 146 147 148 /** 149 * The BER type that will be used for the element that indicates the account 150 * is not usable and provides additional information about the reason. 151 */ 152 private static final byte TYPE_MORE_INFO = (byte) 0xA1; 153 154 155 156 /** 157 * The BER type that will be used for the element that indicates whether the 158 * account is inactive. 159 */ 160 private static final byte TYPE_IS_INACTIVE = (byte) 0x80; 161 162 163 164 /** 165 * The BER type that will be used for the element that indicates whether the 166 * user must change their password. 167 */ 168 private static final byte TYPE_MUST_CHANGE = (byte) 0x81; 169 170 171 172 /** 173 * The BER type that will be used for the element that indicates whether the 174 * password is expired. 175 */ 176 private static final byte TYPE_IS_EXPIRED = (byte) 0x82; 177 178 179 180 /** 181 * The BER type that will be used for the element that provides the number of 182 * remaining grace logins. 183 */ 184 private static final byte TYPE_REMAINING_GRACE_LOGINS = (byte) 0x83; 185 186 187 188 /** 189 * The BER type that will be used for the element that provides the number of 190 * seconds until the account is unlocked. 191 */ 192 private static final byte TYPE_SECONDS_UNTIL_UNLOCK = (byte) 0x84; 193 194 195 196 /** 197 * The name of the field used to indicate whether the account is inactive in 198 * the JSON representation of this control. 199 */ 200 @NotNull private static final String JSON_FIELD_ACCOUNT_IS_INACTIVE = 201 "account-is-inactive"; 202 203 204 205 /** 206 * The name of the field used to indicate whether the account is usable in the 207 * JSON representation of this control. 208 */ 209 @NotNull private static final String JSON_FIELD_ACCOUNT_IS_USABLE = 210 "account-is-usable"; 211 212 213 214 /** 215 * The name of the field used to indicate whether the user must change their 216 * password in the JSON representation of this control. 217 */ 218 @NotNull private static final String JSON_FIELD_MUST_CHANGE_PASSWORD = 219 "must-change-password"; 220 221 222 223 /** 224 * The name of the field used to indicate whether the password is expired in 225 * the JSON representation of this control. 226 */ 227 @NotNull private static final String JSON_FIELD_PASSWORD_IS_EXPIRED = 228 "password-is-expired"; 229 230 231 232 /** 233 * The name of the field used to indicate hold the number of grace logins 234 * remaining in the JSON representation of this control. 235 */ 236 @NotNull private static final String JSON_FIELD_REMAINING_GRACE_LOGINS = 237 "remaining-grace-logins"; 238 239 240 241 /** 242 * The name of the field used to hold the number of seconds until password 243 * expiration in the JSON representation of this control. 244 */ 245 @NotNull private static final String JSON_FIELD_SECONDS_UNTIL_PW_EXPIRATION = 246 "seconds-until-password-expiration"; 247 248 249 250 /** 251 * The name of the field used to hold the number of seconds until the account 252 * is unlocked in the JSON representation of this control. 253 */ 254 @NotNull private static final String JSON_FIELD_SECONDS_UNTIL_UNLOCK = 255 "seconds-until-unlock"; 256 257 258 259 /** 260 * The serial version UID for this serializable class. 261 */ 262 private static final long serialVersionUID = -9150988495337467770L; 263 264 265 266 // Indicates whether the account has been inactivated. 267 private final boolean isInactive; 268 269 // Indicates whether the account is usable. 270 private final boolean isUsable; 271 272 // Indicates whether the user's password must be changed before other 273 // operations will be allowed. 274 private final boolean mustChangePassword; 275 276 // Indicates whether the user's password is expired. 277 private final boolean passwordIsExpired; 278 279 // The list of reasons that this account may be considered unusable. 280 @NotNull private final List<String> unusableReasons; 281 282 // The number of grace logins remaining. 283 private final int remainingGraceLogins; 284 285 // The length of time in seconds until the password expires. 286 private final int secondsUntilExpiration; 287 288 // The length of time in seconds until the account is unlocked. 289 private final int secondsUntilUnlock; 290 291 292 293 /** 294 * Creates a new empty control instance that is intended to be used only for 295 * decoding controls via the {@code DecodeableControl} interface. 296 */ 297 AccountUsableResponseControl() 298 { 299 isUsable = false; 300 secondsUntilExpiration = 0; 301 isInactive = false; 302 mustChangePassword = false; 303 passwordIsExpired = false; 304 remainingGraceLogins = 0; 305 secondsUntilUnlock = 0; 306 unusableReasons = Collections.emptyList(); 307 } 308 309 310 311 /** 312 * Creates a new account usable response control which indicates that the 313 * account is usable. 314 * 315 * @param secondsUntilExpiration The length of time in seconds until the 316 * user's password expires, or -1 if password 317 * expiration is not enabled for the user. 318 */ 319 public AccountUsableResponseControl(final int secondsUntilExpiration) 320 { 321 super(ACCOUNT_USABLE_RESPONSE_OID, false, 322 encodeValue(secondsUntilExpiration)); 323 324 isUsable = true; 325 this.secondsUntilExpiration = secondsUntilExpiration; 326 isInactive = false; 327 mustChangePassword = false; 328 passwordIsExpired = false; 329 remainingGraceLogins = -1; 330 secondsUntilUnlock = -1; 331 unusableReasons = Collections.emptyList(); 332 } 333 334 335 336 /** 337 * Creates a new account usable response control which indicates that the 338 * account is not usable. 339 * 340 * @param isInactive Indicates whether the user account has been 341 * inactivated. 342 * @param mustChangePassword Indicates whether the user is required to 343 * change his/her password before any other 344 * operations will be allowed. 345 * @param passwordIsExpired Indicates whether the user's password has 346 * expired. 347 * @param remainingGraceLogins The number of remaining grace logins for the 348 * user. 349 * @param secondsUntilUnlock The length of time in seconds until the 350 * user's account will be automatically 351 * unlocked. 352 */ 353 public AccountUsableResponseControl(final boolean isInactive, 354 final boolean mustChangePassword, 355 final boolean passwordIsExpired, 356 final int remainingGraceLogins, 357 final int secondsUntilUnlock) 358 { 359 super(ACCOUNT_USABLE_RESPONSE_OID, false, 360 encodeValue(isInactive, mustChangePassword, passwordIsExpired, 361 remainingGraceLogins, secondsUntilUnlock)); 362 363 isUsable = false; 364 secondsUntilExpiration = -1; 365 this.isInactive = isInactive; 366 this.mustChangePassword = mustChangePassword; 367 this.passwordIsExpired = passwordIsExpired; 368 this.remainingGraceLogins = remainingGraceLogins; 369 this.secondsUntilUnlock = secondsUntilUnlock; 370 371 final ArrayList<String> unusableList = new ArrayList<>(5); 372 if (isInactive) 373 { 374 unusableList.add(ERR_ACCT_UNUSABLE_INACTIVE.get()); 375 } 376 377 if (mustChangePassword) 378 { 379 unusableList.add(ERR_ACCT_UNUSABLE_MUST_CHANGE_PW.get()); 380 } 381 382 if (passwordIsExpired) 383 { 384 unusableList.add(ERR_ACCT_UNUSABLE_PW_EXPIRED.get()); 385 } 386 387 if (remainingGraceLogins >= 0) 388 { 389 switch (remainingGraceLogins) 390 { 391 case 0: 392 unusableList.add(ERR_ACCT_UNUSABLE_REMAINING_GRACE_NONE.get()); 393 break; 394 case 1: 395 unusableList.add(ERR_ACCT_UNUSABLE_REMAINING_GRACE_ONE.get()); 396 break; 397 default: 398 unusableList.add(ERR_ACCT_UNUSABLE_REMAINING_GRACE_MULTIPLE.get( 399 remainingGraceLogins)); 400 break; 401 } 402 } 403 404 if (secondsUntilUnlock > 0) 405 { 406 unusableList.add( 407 ERR_ACCT_UNUSABLE_SECONDS_UNTIL_UNLOCK.get(secondsUntilUnlock)); 408 } 409 410 unusableReasons = Collections.unmodifiableList(unusableList); 411 } 412 413 414 415 /** 416 * Creates a new account usable response control with the provided 417 * information. 418 * 419 * @param oid The OID for the control. 420 * @param isCritical Indicates whether the control should be marked 421 * critical. 422 * @param value The encoded value for the control. This may be 423 * {@code null} if no value was provided. 424 * 425 * @throws LDAPException If the provided control cannot be decoded as an 426 * account usable response control. 427 */ 428 public AccountUsableResponseControl(@NotNull final String oid, 429 final boolean isCritical, 430 @Nullable final ASN1OctetString value) 431 throws LDAPException 432 { 433 super(oid, isCritical, value); 434 435 if (value == null) 436 { 437 throw new LDAPException(ResultCode.DECODING_ERROR, 438 ERR_ACCOUNT_USABLE_RESPONSE_NO_VALUE.get()); 439 } 440 441 final ASN1Element valueElement; 442 try 443 { 444 valueElement = ASN1Element.decode(value.getValue()); 445 } 446 catch (final Exception e) 447 { 448 Debug.debugException(e); 449 throw new LDAPException(ResultCode.DECODING_ERROR, 450 ERR_ACCOUNT_USABLE_RESPONSE_VALUE_NOT_ELEMENT.get(e), e); 451 } 452 453 454 final boolean decodedIsUsable; 455 boolean decodedIsInactive = false; 456 boolean decodedMustChangePassword = false; 457 boolean decodedPasswordIsExpired = false; 458 int decodedRemainingGraceLogins = -1; 459 int decodedSecondsUntilExpiration = -1; 460 int decodedSecondsUntilUnlock = -1; 461 462 final List<String> decodedUnusableReasons = new ArrayList<>(5); 463 464 465 final byte type = valueElement.getType(); 466 if (type == TYPE_SECONDS_UNTIL_EXPIRATION) 467 { 468 decodedIsUsable = true; 469 470 try 471 { 472 decodedSecondsUntilExpiration = 473 ASN1Integer.decodeAsInteger(valueElement).intValue(); 474 if (decodedSecondsUntilExpiration < 0) 475 { 476 decodedSecondsUntilExpiration = -1; 477 } 478 } 479 catch (final Exception e) 480 { 481 Debug.debugException(e); 482 throw new LDAPException(ResultCode.DECODING_ERROR, 483 ERR_ACCOUNT_USABLE_RESPONSE_STE_NOT_INT.get(e), e); 484 } 485 } 486 else if (type == TYPE_MORE_INFO) 487 { 488 decodedIsUsable = false; 489 490 final ASN1Element[] elements; 491 try 492 { 493 elements = ASN1Sequence.decodeAsSequence(valueElement).elements(); 494 } 495 catch (final Exception e) 496 { 497 Debug.debugException(e); 498 throw new LDAPException(ResultCode.DECODING_ERROR, 499 ERR_ACCOUNT_USABLE_RESPONSE_VALUE_NOT_SEQUENCE.get(e), 500 e); 501 } 502 503 for (final ASN1Element element : elements) 504 { 505 switch (element.getType()) 506 { 507 case TYPE_IS_INACTIVE: 508 try 509 { 510 decodedIsInactive = 511 ASN1Boolean.decodeAsBoolean(element).booleanValue(); 512 decodedUnusableReasons.add(ERR_ACCT_UNUSABLE_INACTIVE.get()); 513 } 514 catch (final Exception e) 515 { 516 Debug.debugException(e); 517 throw new LDAPException(ResultCode.DECODING_ERROR, 518 ERR_ACCOUNT_USABLE_RESPONSE_INACTIVE_NOT_BOOLEAN.get(e), e); 519 } 520 break; 521 522 case TYPE_MUST_CHANGE: 523 try 524 { 525 decodedMustChangePassword = 526 ASN1Boolean.decodeAsBoolean(element).booleanValue(); 527 decodedUnusableReasons.add( 528 ERR_ACCT_UNUSABLE_MUST_CHANGE_PW.get()); 529 } 530 catch (final Exception e) 531 { 532 Debug.debugException(e); 533 throw new LDAPException(ResultCode.DECODING_ERROR, 534 ERR_ACCOUNT_USABLE_RESPONSE_MUST_CHANGE_NOT_BOOLEAN.get(e), 535 e); 536 } 537 break; 538 539 case TYPE_IS_EXPIRED: 540 try 541 { 542 decodedPasswordIsExpired = 543 ASN1Boolean.decodeAsBoolean(element).booleanValue(); 544 decodedUnusableReasons.add(ERR_ACCT_UNUSABLE_PW_EXPIRED.get()); 545 } 546 catch (final Exception e) 547 { 548 Debug.debugException(e); 549 throw new LDAPException(ResultCode.DECODING_ERROR, 550 ERR_ACCOUNT_USABLE_RESPONSE_IS_EXP_NOT_BOOLEAN.get(e), e); 551 } 552 break; 553 554 case TYPE_REMAINING_GRACE_LOGINS: 555 try 556 { 557 decodedRemainingGraceLogins = 558 ASN1Integer.decodeAsInteger(element).intValue(); 559 if (decodedRemainingGraceLogins < 0) 560 { 561 decodedRemainingGraceLogins = -1; 562 } 563 else 564 { 565 switch (decodedRemainingGraceLogins) 566 { 567 case 0: 568 decodedUnusableReasons.add( 569 ERR_ACCT_UNUSABLE_REMAINING_GRACE_NONE.get()); 570 break; 571 case 1: 572 decodedUnusableReasons.add( 573 ERR_ACCT_UNUSABLE_REMAINING_GRACE_ONE.get()); 574 break; 575 default: 576 decodedUnusableReasons.add( 577 ERR_ACCT_UNUSABLE_REMAINING_GRACE_MULTIPLE.get( 578 decodedRemainingGraceLogins)); 579 break; 580 } 581 } 582 } 583 catch (final Exception e) 584 { 585 Debug.debugException(e); 586 throw new LDAPException(ResultCode.DECODING_ERROR, 587 ERR_ACCOUNT_USABLE_RESPONSE_GRACE_LOGINS_NOT_INT.get(e), e); 588 } 589 break; 590 591 case TYPE_SECONDS_UNTIL_UNLOCK: 592 try 593 { 594 decodedSecondsUntilUnlock = 595 ASN1Integer.decodeAsInteger(element).intValue(); 596 if (decodedSecondsUntilUnlock < 0) 597 { 598 decodedSecondsUntilUnlock = -1; 599 } 600 else if (decodedSecondsUntilUnlock > 0) 601 { 602 decodedUnusableReasons.add( 603 ERR_ACCT_UNUSABLE_SECONDS_UNTIL_UNLOCK.get( 604 decodedSecondsUntilUnlock)); 605 } 606 } 607 catch (final Exception e) 608 { 609 Debug.debugException(e); 610 throw new LDAPException(ResultCode.DECODING_ERROR, 611 ERR_ACCOUNT_USABLE_RESPONSE_STU_NOT_INT.get(e), e); 612 } 613 break; 614 615 default: 616 throw new LDAPException(ResultCode.DECODING_ERROR, 617 ERR_ACCOUNT_USABLE_RESPONSE_MORE_INFO_INVALID_TYPE.get( 618 StaticUtils.toHex(element.getType()))); 619 } 620 } 621 } 622 else 623 { 624 throw new LDAPException(ResultCode.DECODING_ERROR, 625 ERR_ACCOUNT_USABLE_RESPONSE_INVALID_TYPE.get( 626 StaticUtils.toHex(type))); 627 } 628 629 isUsable = decodedIsUsable; 630 secondsUntilExpiration = decodedSecondsUntilExpiration; 631 isInactive = decodedIsInactive; 632 mustChangePassword = decodedMustChangePassword; 633 passwordIsExpired = decodedPasswordIsExpired; 634 remainingGraceLogins = decodedRemainingGraceLogins; 635 secondsUntilUnlock = decodedSecondsUntilUnlock; 636 unusableReasons = 637 Collections.unmodifiableList(decodedUnusableReasons); 638 } 639 640 641 642 /** 643 * Creates an ASN.1 octet string that may be used as the value of an account 644 * usable response control if the account is usable. 645 * 646 * @param secondsUntilExpiration The length of time in seconds until the 647 * user's password expires, or -1 if password 648 * expiration is not enabled for the user. 649 * 650 * @return The ASN.1 octet string that may be used as the control value. 651 */ 652 @NotNull() 653 private static ASN1OctetString encodeValue(final int secondsUntilExpiration) 654 { 655 final ASN1Integer sueInteger = 656 new ASN1Integer(TYPE_SECONDS_UNTIL_EXPIRATION, secondsUntilExpiration); 657 658 return new ASN1OctetString(sueInteger.encode()); 659 } 660 661 662 663 /** 664 * Creates an ASN.1 octet string that may be used of the value of an account 665 * usable response control if the account is not usable. 666 * 667 * @param isInactive Indicates whether the user account has been 668 * inactivated. 669 * @param mustChangePassword Indicates whether the user is required to 670 * change his/her password before any other 671 * operations will be allowed. 672 * @param passwordIsExpired Indicates whether the user's password has 673 * expired. 674 * @param remainingGraceLogins The number of remaining grace logins for the 675 * user. 676 * @param secondsUntilUnlock The length of time in seconds until the 677 * user's account will be automatically 678 * unlocked. 679 * 680 * @return The ASN.1 octet string that may be used as the control value. 681 */ 682 @NotNull() 683 private static ASN1OctetString encodeValue(final boolean isInactive, 684 final boolean mustChangePassword, 685 final boolean passwordIsExpired, 686 final int remainingGraceLogins, 687 final int secondsUntilUnlock) 688 { 689 final ArrayList<ASN1Element> elements = new ArrayList<>(5); 690 691 if (isInactive) 692 { 693 elements.add(new ASN1Boolean(TYPE_IS_INACTIVE, true)); 694 } 695 696 if (mustChangePassword) 697 { 698 elements.add(new ASN1Boolean(TYPE_MUST_CHANGE, true)); 699 } 700 701 if (passwordIsExpired) 702 { 703 elements.add(new ASN1Boolean(TYPE_IS_EXPIRED, true)); 704 } 705 706 if (remainingGraceLogins >= 0) 707 { 708 elements.add(new ASN1Integer(TYPE_REMAINING_GRACE_LOGINS, 709 remainingGraceLogins)); 710 } 711 712 if (secondsUntilUnlock >= 0) 713 { 714 elements.add(new ASN1Integer(TYPE_SECONDS_UNTIL_UNLOCK, 715 secondsUntilUnlock)); 716 } 717 718 final ASN1Sequence valueSequence = 719 new ASN1Sequence(TYPE_MORE_INFO, elements); 720 return new ASN1OctetString(valueSequence.encode()); 721 } 722 723 724 725 /** 726 * {@inheritDoc} 727 */ 728 @Override() 729 @NotNull() 730 public AccountUsableResponseControl decodeControl(@NotNull final String oid, 731 final boolean isCritical, 732 @Nullable final ASN1OctetString value) 733 throws LDAPException 734 { 735 return new AccountUsableResponseControl(oid, isCritical, value); 736 } 737 738 739 740 /** 741 * Extracts an account usable response control from the provided search result 742 * entry. 743 * 744 * @param entry The search result entry from which to retrieve the account 745 * usable response control. 746 * 747 * @return The account usable response control contained in the provided 748 * search result entry, or {@code null} if the entry did not contain 749 * an account usable response control. 750 * 751 * @throws LDAPException If a problem is encountered while attempting to 752 * decode the account usable response control 753 * contained in the provided result. 754 */ 755 @Nullable() 756 public static AccountUsableResponseControl get( 757 @NotNull final SearchResultEntry entry) 758 throws LDAPException 759 { 760 final Control c = entry.getControl(ACCOUNT_USABLE_RESPONSE_OID); 761 if (c == null) 762 { 763 return null; 764 } 765 766 if (c instanceof AccountUsableResponseControl) 767 { 768 return (AccountUsableResponseControl) c; 769 } 770 else 771 { 772 return new AccountUsableResponseControl(c.getOID(), c.isCritical(), 773 c.getValue()); 774 } 775 } 776 777 778 779 /** 780 * Indicates whether the associated user account is usable. 781 * 782 * @return {@code true} if the user account is usable, or {@code false} if 783 * not. 784 */ 785 public boolean isUsable() 786 { 787 return isUsable; 788 } 789 790 791 792 /** 793 * Retrieves the list of reasons that this account may be unusable. 794 * 795 * @return The list of reasons that this account may be unusable, or an empty 796 * list if the account is usable or no reasons are available. 797 */ 798 @NotNull() 799 public List<String> getUnusableReasons() 800 { 801 return unusableReasons; 802 } 803 804 805 806 /** 807 * Retrieves the number of seconds until the user's password expires. This 808 * will only available if the account is usable. 809 * 810 * @return The number of seconds until the user's password expires, or -1 if 811 * the user account is not usable, or if password expiration is not 812 * enabled in the directory server. 813 */ 814 public int getSecondsUntilExpiration() 815 { 816 return secondsUntilExpiration; 817 } 818 819 820 821 /** 822 * Indicates whether the user account has been inactivated by a server 823 * administrator. 824 * 825 * @return {@code true} if the user account has been inactivated by a server 826 * administrator, or {@code false} if not. 827 */ 828 public boolean isInactive() 829 { 830 return isInactive; 831 } 832 833 834 835 /** 836 * Indicates whether the user must change his or her password before being 837 * allowed to perform any other operations. 838 * 839 * @return {@code true} if the user must change his or her password before 840 * being allowed to perform any other operations, or {@code false} if 841 * not. 842 */ 843 public boolean mustChangePassword() 844 { 845 return mustChangePassword; 846 } 847 848 849 850 /** 851 * Indicates whether the user's password is expired. 852 * 853 * @return {@code true} if the user's password is expired, or {@code false} 854 * if not. 855 */ 856 public boolean passwordIsExpired() 857 { 858 return passwordIsExpired; 859 } 860 861 862 863 /** 864 * Retrieves the number of remaining grace logins for the user. This will 865 * only be available if the user account is not usable. 866 * 867 * @return The number of remaining grace logins for the user, or -1 if this 868 * is not available (e.g., because the account is usable or grace 869 * login functionality is disabled on the server). 870 */ 871 public int getRemainingGraceLogins() 872 { 873 return remainingGraceLogins; 874 } 875 876 877 878 /** 879 * Retrieves the length of time in seconds until the user's account is 880 * automatically unlocked. This will only be available if the user account is 881 * not usable. 882 * 883 * @return The length of time in seconds until the user's account is 884 * automatically unlocked, or -1 if this is not available (e.g., 885 * because the account is usable, or because the account is not 886 * locked, or because automatic unlocking is disabled on the server). 887 */ 888 public int getSecondsUntilUnlock() 889 { 890 return secondsUntilUnlock; 891 } 892 893 894 895 /** 896 * {@inheritDoc} 897 */ 898 @Override() 899 @NotNull() 900 public String getControlName() 901 { 902 return INFO_CONTROL_NAME_ACCOUNT_USABLE_RESPONSE.get(); 903 } 904 905 906 907 /** 908 * Retrieves a representation of this account usable response control as a 909 * JSON object. The JSON object uses the following fields: 910 * <UL> 911 * <LI> 912 * {@code oid} -- A mandatory string field whose value is the object 913 * identifier for this control. For the account usable response control, 914 * the OID is "1.3.6.1.4.1.42.2.27.9.5.8". 915 * </LI> 916 * <LI> 917 * {@code control-name} -- An optional string field whose value is a 918 * human-readable name for this control. This field is only intended for 919 * descriptive purposes, and when decoding a control, the {@code oid} 920 * field should be used to identify the type of control. 921 * </LI> 922 * <LI> 923 * {@code criticality} -- A mandatory Boolean field used to indicate 924 * whether this control is considered critical. 925 * </LI> 926 * <LI> 927 * {@code value-base64} -- An optional string field whose value is a 928 * base64-encoded representation of the raw value for this account usable 929 * response control. Exactly one of the {@code value-base64} and 930 * {@code value-json} fields must be present. 931 * </LI> 932 * <LI> 933 * {@code value-json} -- An optional JSON object field whose value is a 934 * user-friendly representation of the value for this account usable 935 * response control. Exactly one of the {@code value-base64} and 936 * {@code value-json} fields must be present, and if the 937 * {@code value-json} field is used, then it will use the following 938 * fields: 939 * <UL> 940 * <LI> 941 * {@code account-is-usable} -- A Boolean field that indicates whether 942 * the account is in a usable state. 943 * </LI> 944 * <LI> 945 * {@code seconds-until-password-expiration} -- An optional integer 946 * field whose value is the number of seconds until the user's 947 * password expires. 948 * </LI> 949 * <LI> 950 * {@code account-is-inactive} -- A Boolean field that indicates 951 * whether the account has been administratively disabled. 952 * </LI> 953 * <LI> 954 * {@code must-change-password} -- A Boolean field that indicates 955 * whether the user must change their password before they can request 956 * any other operations 957 * </LI> 958 * <LI> 959 * {@code password-is-expired} -- A Boolean field that indicates 960 * whether the user's password is expired. 961 * </LI> 962 * <LI> 963 * {@code remaining-grace-logins} -- An optional integer field whose 964 * value is the number of remaining grace logins for the user. 965 * </LI> 966 * <LI> 967 * {@code seconds-until-unlock} -- An optional integer field whose 968 * value is the number of seconds until the user's account will be 969 * automatically unlocked. 970 * </LI> 971 * </UL> 972 * </LI> 973 * </UL> 974 * 975 * @return A JSON object that contains a representation of this control. 976 */ 977 @Override() 978 @NotNull() 979 public JSONObject toJSONControl() 980 { 981 final Map<String,JSONValue> jsonValueFields = new LinkedHashMap<>(); 982 jsonValueFields.put(JSON_FIELD_ACCOUNT_IS_USABLE, 983 new JSONBoolean(isUsable)); 984 985 if (secondsUntilExpiration >= 0) 986 { 987 jsonValueFields.put(JSON_FIELD_SECONDS_UNTIL_PW_EXPIRATION, 988 new JSONNumber(secondsUntilExpiration)); 989 } 990 991 jsonValueFields.put(JSON_FIELD_ACCOUNT_IS_INACTIVE, 992 new JSONBoolean(isInactive)); 993 jsonValueFields.put(JSON_FIELD_MUST_CHANGE_PASSWORD, 994 new JSONBoolean(mustChangePassword)); 995 jsonValueFields.put(JSON_FIELD_PASSWORD_IS_EXPIRED, 996 new JSONBoolean(passwordIsExpired)); 997 998 if (remainingGraceLogins >= 0) 999 { 1000 jsonValueFields.put(JSON_FIELD_REMAINING_GRACE_LOGINS, 1001 new JSONNumber(remainingGraceLogins)); 1002 } 1003 1004 if (secondsUntilUnlock >= 0) 1005 { 1006 jsonValueFields.put(JSON_FIELD_SECONDS_UNTIL_UNLOCK, 1007 new JSONNumber(secondsUntilUnlock)); 1008 } 1009 1010 return new JSONObject( 1011 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 1012 ACCOUNT_USABLE_RESPONSE_OID), 1013 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 1014 INFO_CONTROL_NAME_ACCOUNT_USABLE_RESPONSE.get()), 1015 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 1016 isCritical()), 1017 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 1018 new JSONObject(jsonValueFields))); 1019 } 1020 1021 1022 1023 /** 1024 * Attempts to decode the provided object as a JSON representation of an 1025 * account usable response control. 1026 * 1027 * @param controlObject The JSON object to be decoded. It must not be 1028 * {@code null}. 1029 * @param strict Indicates whether to use strict mode when decoding 1030 * the provided JSON object. If this is {@code true}, 1031 * then this method will throw an exception if the 1032 * provided JSON object contains any unrecognized 1033 * fields. If this is {@code false}, then unrecognized 1034 * fields will be ignored. 1035 * 1036 * @return The account usable response control that was decoded from the 1037 * provided JSON object. 1038 * 1039 * @throws LDAPException If the provided JSON object cannot be parsed as a 1040 * valid account usable response control. 1041 */ 1042 @NotNull() 1043 public static AccountUsableResponseControl decodeJSONControl( 1044 @NotNull final JSONObject controlObject, 1045 final boolean strict) 1046 throws LDAPException 1047 { 1048 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 1049 controlObject, strict, true, true); 1050 1051 final ASN1OctetString rawValue = jsonControl.getRawValue(); 1052 if (rawValue != null) 1053 { 1054 return new AccountUsableResponseControl(jsonControl.getOID(), 1055 jsonControl.getCriticality(), rawValue); 1056 } 1057 1058 1059 Boolean isInactive = null; 1060 Boolean isUsable = null; 1061 Boolean mustChangePassword = null; 1062 Boolean passwordIsExpired = null; 1063 Integer remainingGraceLogins = null; 1064 Integer secondsUntilExpiration = null; 1065 Integer secondsUntilUnlock = null; 1066 final JSONObject valueObject = jsonControl.getValueObject(); 1067 1068 isUsable = valueObject.getFieldAsBoolean(JSON_FIELD_ACCOUNT_IS_USABLE); 1069 if (isUsable == null) 1070 { 1071 throw new LDAPException(ResultCode.DECODING_ERROR, 1072 ERR_ACCOUNT_USABLE_RESPONSE_JSON_MISSING_FIELD.get( 1073 controlObject.toSingleLineString(), 1074 JSON_FIELD_ACCOUNT_IS_USABLE)); 1075 } 1076 1077 secondsUntilExpiration = valueObject.getFieldAsInteger( 1078 JSON_FIELD_SECONDS_UNTIL_PW_EXPIRATION); 1079 1080 isInactive = valueObject.getFieldAsBoolean(JSON_FIELD_ACCOUNT_IS_INACTIVE); 1081 if (isInactive == null) 1082 { 1083 throw new LDAPException(ResultCode.DECODING_ERROR, 1084 ERR_ACCOUNT_USABLE_RESPONSE_JSON_MISSING_FIELD.get( 1085 controlObject.toSingleLineString(), 1086 JSON_FIELD_ACCOUNT_IS_INACTIVE)); 1087 } 1088 1089 mustChangePassword = 1090 valueObject.getFieldAsBoolean(JSON_FIELD_MUST_CHANGE_PASSWORD); 1091 if (mustChangePassword == null) 1092 { 1093 throw new LDAPException(ResultCode.DECODING_ERROR, 1094 ERR_ACCOUNT_USABLE_RESPONSE_JSON_MISSING_FIELD.get( 1095 controlObject.toSingleLineString(), 1096 JSON_FIELD_MUST_CHANGE_PASSWORD)); 1097 } 1098 1099 passwordIsExpired = 1100 valueObject.getFieldAsBoolean(JSON_FIELD_PASSWORD_IS_EXPIRED); 1101 if (passwordIsExpired == null) 1102 { 1103 throw new LDAPException(ResultCode.DECODING_ERROR, 1104 ERR_ACCOUNT_USABLE_RESPONSE_JSON_MISSING_FIELD.get( 1105 controlObject.toSingleLineString(), 1106 JSON_FIELD_PASSWORD_IS_EXPIRED)); 1107 } 1108 1109 remainingGraceLogins = valueObject.getFieldAsInteger( 1110 JSON_FIELD_REMAINING_GRACE_LOGINS); 1111 1112 secondsUntilUnlock = valueObject.getFieldAsInteger( 1113 JSON_FIELD_SECONDS_UNTIL_UNLOCK); 1114 1115 if (isUsable) 1116 { 1117 if (isInactive) 1118 { 1119 throw new LDAPException(ResultCode.DECODING_ERROR, 1120 ERR_ACCOUNT_USABLE_RESPONSE_JSON_USABLE_BOOLEAN_CONFLICT.get( 1121 controlObject.toSingleLineString(), 1122 JSON_FIELD_ACCOUNT_IS_USABLE, 1123 JSON_FIELD_ACCOUNT_IS_INACTIVE)); 1124 } 1125 else if (mustChangePassword) 1126 { 1127 throw new LDAPException(ResultCode.DECODING_ERROR, 1128 ERR_ACCOUNT_USABLE_RESPONSE_JSON_USABLE_BOOLEAN_CONFLICT.get( 1129 controlObject.toSingleLineString(), 1130 JSON_FIELD_ACCOUNT_IS_USABLE, 1131 JSON_FIELD_MUST_CHANGE_PASSWORD)); 1132 } 1133 else if (passwordIsExpired) 1134 { 1135 throw new LDAPException(ResultCode.DECODING_ERROR, 1136 ERR_ACCOUNT_USABLE_RESPONSE_JSON_USABLE_BOOLEAN_CONFLICT.get( 1137 controlObject.toSingleLineString(), 1138 JSON_FIELD_ACCOUNT_IS_USABLE, 1139 JSON_FIELD_PASSWORD_IS_EXPIRED)); 1140 } 1141 else if (remainingGraceLogins != null) 1142 { 1143 throw new LDAPException(ResultCode.DECODING_ERROR, 1144 ERR_ACCOUNT_USABLE_RESPONSE_JSON_USABLE_INT_CONFLICT.get( 1145 controlObject.toSingleLineString(), 1146 JSON_FIELD_ACCOUNT_IS_USABLE, 1147 JSON_FIELD_REMAINING_GRACE_LOGINS)); 1148 } 1149 else if (secondsUntilUnlock != null) 1150 { 1151 throw new LDAPException(ResultCode.DECODING_ERROR, 1152 ERR_ACCOUNT_USABLE_RESPONSE_JSON_USABLE_INT_CONFLICT.get( 1153 controlObject.toSingleLineString(), 1154 JSON_FIELD_ACCOUNT_IS_USABLE, 1155 JSON_FIELD_SECONDS_UNTIL_UNLOCK)); 1156 } 1157 } 1158 else if (secondsUntilExpiration != null) 1159 { 1160 throw new LDAPException(ResultCode.DECODING_ERROR, 1161 ERR_ACCOUNT_USABLE_RESPONSE_JSON_UNUSABLE_CONFLICT.get( 1162 controlObject.toSingleLineString(), 1163 JSON_FIELD_ACCOUNT_IS_USABLE, 1164 JSON_FIELD_SECONDS_UNTIL_PW_EXPIRATION)); 1165 } 1166 1167 1168 if (strict) 1169 { 1170 final List<String> unrecognizedFields = 1171 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 1172 valueObject, JSON_FIELD_ACCOUNT_IS_USABLE, 1173 JSON_FIELD_SECONDS_UNTIL_PW_EXPIRATION, 1174 JSON_FIELD_ACCOUNT_IS_INACTIVE, JSON_FIELD_MUST_CHANGE_PASSWORD, 1175 JSON_FIELD_PASSWORD_IS_EXPIRED, 1176 JSON_FIELD_REMAINING_GRACE_LOGINS, 1177 JSON_FIELD_SECONDS_UNTIL_UNLOCK); 1178 if (! unrecognizedFields.isEmpty()) 1179 { 1180 throw new LDAPException(ResultCode.DECODING_ERROR, 1181 ERR_ACCOUNT_USABLE_RESPONSE_JSON_CONTROL_UNRECOGNIZED_FIELD.get( 1182 controlObject.toSingleLineString(), 1183 unrecognizedFields.get(0))); 1184 } 1185 } 1186 1187 1188 if (isUsable) 1189 { 1190 return new AccountUsableResponseControl( 1191 (secondsUntilExpiration == null) ? -1 : secondsUntilExpiration); 1192 } 1193 else 1194 { 1195 return new AccountUsableResponseControl(isInactive, mustChangePassword, 1196 passwordIsExpired, 1197 (remainingGraceLogins == null) ? -1 : remainingGraceLogins, 1198 (secondsUntilUnlock == null) ? -1 : secondsUntilUnlock); 1199 } 1200 } 1201 1202 1203 1204 /** 1205 * {@inheritDoc} 1206 */ 1207 @Override() 1208 public void toString(@NotNull final StringBuilder buffer) 1209 { 1210 buffer.append("AccountUsableResponseControl(isUsable="); 1211 buffer.append(isUsable); 1212 1213 if (isUsable) 1214 { 1215 if (secondsUntilExpiration >= 0) 1216 { 1217 buffer.append(", secondsUntilExpiration="); 1218 buffer.append(secondsUntilExpiration); 1219 } 1220 } 1221 else 1222 { 1223 buffer.append(", isInactive="); 1224 buffer.append(isInactive); 1225 buffer.append(", mustChangePassword="); 1226 buffer.append(mustChangePassword); 1227 buffer.append(", passwordIsExpired="); 1228 buffer.append(passwordIsExpired); 1229 1230 if (remainingGraceLogins >= 0) 1231 { 1232 buffer.append(", remainingGraceLogins="); 1233 buffer.append(remainingGraceLogins); 1234 } 1235 1236 if (secondsUntilUnlock >= 0) 1237 { 1238 buffer.append(", secondsUntilUnlock="); 1239 buffer.append(secondsUntilUnlock); 1240 } 1241 } 1242 1243 buffer.append(')'); 1244 } 1245}