001/* 002 * Copyright 2007-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-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) 2007-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.LinkedHashMap; 042import java.util.List; 043import java.util.Map; 044 045import com.unboundid.asn1.ASN1Element; 046import com.unboundid.asn1.ASN1Enumerated; 047import com.unboundid.asn1.ASN1Exception; 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.LDAPResult; 056import com.unboundid.ldap.sdk.ResultCode; 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.JSONField; 065import com.unboundid.util.json.JSONObject; 066import com.unboundid.util.json.JSONString; 067import com.unboundid.util.json.JSONValue; 068 069import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 070 071 072 073/** 074 * This class provides an implementation of the password policy response control 075 * as described in draft-behera-ldap-password-policy. It may be used to provide 076 * information related to a user's password policy. It may include at most one 077 * warning from the set of {@link PasswordPolicyWarningType} values and at most 078 * one error from the set of {@link PasswordPolicyErrorType} values. See the 079 * documentation for those classes for more information on the information that 080 * may be included. See the {@link PasswordPolicyRequestControl} documentation 081 * for an example that demonstrates the use of the password policy request and 082 * response controls. 083 * <BR> 084 * <BLOCKQUOTE> 085 * <B>NOTE:</B> This class, and other classes within the 086 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 087 * supported for use against Ping Identity, UnboundID, and 088 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 089 * for proprietary functionality or for external specifications that are not 090 * considered stable or mature enough to be guaranteed to work in an 091 * interoperable way with other types of LDAP servers. 092 * </BLOCKQUOTE> 093 * <BR> 094 * The control has an OID of 1.3.6.1.4.1.42.2.27.8.5.1 and a criticality of 095 * false. It must have a value with the following encoding: 096 * <PRE> 097 * PasswordPolicyResponseValue ::= SEQUENCE { 098 * warning [0] CHOICE { 099 * timeBeforeExpiration [0] INTEGER (0 .. maxInt), 100 * graceAuthNsRemaining [1] INTEGER (0 .. maxInt) } OPTIONAL, 101 * error [1] ENUMERATED { 102 * passwordExpired (0), 103 * accountLocked (1), 104 * changeAfterReset (2), 105 * passwordModNotAllowed (3), 106 * mustSupplyOldPassword (4), 107 * insufficientPasswordQuality (5), 108 * passwordTooShort (6), 109 * passwordTooYoung (7), 110 * passwordInHistory (8) } OPTIONAL } 111 * </PRE> 112 */ 113@NotMutable() 114@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 115public final class PasswordPolicyResponseControl 116 extends Control 117 implements DecodeableControl 118{ 119 /** 120 * The OID (1.3.6.1.4.1.42.2.27.8.5.1) for the password policy response 121 * control. 122 */ 123 @NotNull public static final String PASSWORD_POLICY_RESPONSE_OID = 124 "1.3.6.1.4.1.42.2.27.8.5.1"; 125 126 127 128 /** 129 * The BER type for the password policy warning element. 130 */ 131 private static final byte TYPE_WARNING = (byte) 0xA0; 132 133 134 135 /** 136 * The BER type for the password policy error element. 137 */ 138 private static final byte TYPE_ERROR = (byte) 0x81; 139 140 141 142 /** 143 * The BER type for the "time before expiration" warning element. 144 */ 145 private static final byte TYPE_TIME_BEFORE_EXPIRATION = (byte) 0x80; 146 147 148 149 /** 150 * The BER type for the "grace logins remaining" warning element. 151 */ 152 private static final byte TYPE_GRACE_LOGINS_REMAINING = (byte) 0x81; 153 154 155 156 /** 157 * The name of the field used to hold the error type in the JSON 158 * representation of this control. 159 */ 160 @NotNull private static final String JSON_FIELD_ERROR_TYPE = "error-type"; 161 162 163 164 /** 165 * The name of the field used to hold the grace logins remaining in the JSON 166 * representation of this control. 167 */ 168 @NotNull private static final String JSON_FIELD_GRACE_LOGINS_REMAINING = 169 "grace-logins-remaining"; 170 171 172 173 /** 174 * The name of the field used to hold the seconds until expiration in the JSON 175 * representation of this control. 176 */ 177 @NotNull private static final String JSON_FIELD_SECONDS_UNTIL_EXPIRATION = 178 "seconds-until-expiration"; 179 180 181 182 /** 183 * The name of the field used to hold the warning in the JSON 184 * representation of this control. 185 */ 186 @NotNull private static final String JSON_FIELD_WARNING = "warning"; 187 188 189 190 /** 191 * The value to use for the account-locked error type in the JSON 192 * representation of this control. 193 */ 194 @NotNull private static final String JSON_ERROR_TYPE_ACCOUNT_LOCKED = 195 "account-locked"; 196 197 198 199 /** 200 * The value to use for the change-after-reset error type in the JSON 201 * representation of this control. 202 */ 203 @NotNull private static final String JSON_ERROR_TYPE_CHANGE_AFTER_RESET = 204 "change-after-reset"; 205 206 207 208 /** 209 * The value to use for the insufficient-password-quality error type in the 210 * JSON representation of this control. 211 */ 212 @NotNull private static final String 213 JSON_ERROR_TYPE_INSUFFICIENT_PASSWORD_QUALITY = 214 "insufficient-password-quality"; 215 216 217 218 /** 219 * The value to use for the must-supply-old-password error type in the JSON 220 * representation of this control. 221 */ 222 @NotNull private static final String 223 JSON_ERROR_TYPE_MUST_SUPPLY_OLD_PASSWORD = "must-supply-old-password"; 224 225 226 227 /** 228 * The value to use for the password-expired error type in the JSON 229 * representation of this control. 230 */ 231 @NotNull private static final String JSON_ERROR_TYPE_PASSWORD_EXPIRED = 232 "password-expired"; 233 234 235 236 /** 237 * The value to use for the password-in-history error type in the JSON 238 * representation of this control. 239 */ 240 @NotNull private static final String JSON_ERROR_TYPE_PASSWORD_IN_HISTORY = 241 "password-in-history"; 242 243 244 245 /** 246 * The value to use for the password-too-short error type in the JSON 247 * representation of this control. 248 */ 249 @NotNull private static final String JSON_ERROR_TYPE_PASSWORD_TOO_SHORT = 250 "password-too-short"; 251 252 253 254 /** 255 * The value to use for the password-too-young error type in the JSON 256 * representation of this control. 257 */ 258 @NotNull private static final String JSON_ERROR_TYPE_PASSWORD_TOO_YOUNG = 259 "password-too-young"; 260 261 262 263 /** 264 * The value to use for the password-mod-not-allowed error type in the JSON 265 * representation of this control. 266 */ 267 @NotNull private static final String 268 JSON_ERROR_TYPE_PASSWORD_MOD_NOT_ALLOWED = "password-mod-not-allowed"; 269 270 271 272 /** 273 * The serial version UID for this serializable class. 274 */ 275 private static final long serialVersionUID = 1835830253434331833L; 276 277 278 279 // The password policy warning value, if applicable. 280 private final int warningValue; 281 282 // The password policy error type, if applicable. 283 @Nullable private final PasswordPolicyErrorType errorType; 284 285 // The password policy warning type, if applicable. 286 @Nullable private final PasswordPolicyWarningType warningType; 287 288 289 290 /** 291 * Creates a new empty control instance that is intended to be used only for 292 * decoding controls via the {@code DecodeableControl} interface. 293 */ 294 PasswordPolicyResponseControl() 295 { 296 warningType = null; 297 errorType = null; 298 warningValue = -1; 299 } 300 301 302 303 /** 304 * Creates a new password policy response control with the provided 305 * information. It will not be critical. 306 * 307 * @param warningType The password policy warning type for this response 308 * control, or {@code null} if there should be no 309 * warning type. 310 * @param warningValue The value for the password policy warning type, or -1 311 * if there is no warning type. 312 * @param errorType The password policy error type for this response 313 * control, or {@code null} if there should be no error 314 * type. 315 */ 316 public PasswordPolicyResponseControl( 317 @Nullable final PasswordPolicyWarningType warningType, 318 final int warningValue, 319 @Nullable final PasswordPolicyErrorType errorType) 320 { 321 this(warningType, warningValue, errorType, false); 322 } 323 324 325 326 /** 327 * Creates a new password policy response control with the provided 328 * information. 329 * 330 * @param warningType The password policy warning type for this response 331 * control, or {@code null} if there should be no 332 * warning type. 333 * @param warningValue The value for the password policy warning type, or -1 334 * if there is no warning type. 335 * @param errorType The password policy error type for this response 336 * control, or {@code null} if there should be no error 337 * type. 338 * @param isCritical Indicates whether this control should be marked 339 * critical. Response controls should generally not be 340 * critical. 341 */ 342 public PasswordPolicyResponseControl( 343 @Nullable final PasswordPolicyWarningType warningType, 344 final int warningValue, 345 @Nullable final PasswordPolicyErrorType errorType, 346 final boolean isCritical) 347 { 348 super(PASSWORD_POLICY_RESPONSE_OID, isCritical, 349 encodeValue(warningType, warningValue, errorType)); 350 351 this.warningType = warningType; 352 this.errorType = errorType; 353 354 if (warningType == null) 355 { 356 this.warningValue = -1; 357 } 358 else 359 { 360 this.warningValue = warningValue; 361 } 362 } 363 364 365 366 /** 367 * Creates a new password policy response control with the provided 368 * information. 369 * 370 * @param oid The OID for the control. 371 * @param isCritical Indicates whether the control should be marked 372 * critical. 373 * @param value The encoded value for the control. This may be 374 * {@code null} if no value was provided. 375 * 376 * @throws LDAPException If the provided control cannot be decoded as a 377 * password policy response control. 378 */ 379 public PasswordPolicyResponseControl(@NotNull final String oid, 380 final boolean isCritical, 381 @Nullable final ASN1OctetString value) 382 throws LDAPException 383 { 384 super(oid, isCritical, value); 385 386 if (value == null) 387 { 388 throw new LDAPException(ResultCode.DECODING_ERROR, 389 ERR_PWP_RESPONSE_NO_VALUE.get()); 390 } 391 392 final ASN1Sequence valueSequence; 393 try 394 { 395 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 396 valueSequence = ASN1Sequence.decodeAsSequence(valueElement); 397 } 398 catch (final ASN1Exception ae) 399 { 400 Debug.debugException(ae); 401 throw new LDAPException(ResultCode.DECODING_ERROR, 402 ERR_PWP_RESPONSE_VALUE_NOT_SEQUENCE.get(ae), ae); 403 } 404 405 final ASN1Element[] valueElements = valueSequence.elements(); 406 if (valueElements.length > 2) 407 { 408 throw new LDAPException(ResultCode.DECODING_ERROR, 409 ERR_PWP_RESPONSE_INVALID_ELEMENT_COUNT.get( 410 valueElements.length)); 411 } 412 413 int wv = -1; 414 PasswordPolicyErrorType et = null; 415 PasswordPolicyWarningType wt = null; 416 for (final ASN1Element e : valueElements) 417 { 418 switch (e.getType()) 419 { 420 case TYPE_WARNING: 421 if (wt == null) 422 { 423 try 424 { 425 final ASN1Element warningElement = 426 ASN1Element.decode(e.getValue()); 427 wv = ASN1Integer.decodeAsInteger(warningElement).intValue(); 428 switch (warningElement.getType()) 429 { 430 case TYPE_TIME_BEFORE_EXPIRATION: 431 wt = PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION; 432 break; 433 434 case TYPE_GRACE_LOGINS_REMAINING: 435 wt = PasswordPolicyWarningType.GRACE_LOGINS_REMAINING; 436 break; 437 438 default: 439 throw new LDAPException(ResultCode.DECODING_ERROR, 440 ERR_PWP_RESPONSE_INVALID_WARNING_TYPE.get( 441 StaticUtils.toHex(warningElement.getType()))); 442 } 443 } 444 catch (final ASN1Exception ae) 445 { 446 Debug.debugException(ae); 447 throw new LDAPException(ResultCode.DECODING_ERROR, 448 ERR_PWP_RESPONSE_CANNOT_DECODE_WARNING.get(ae), ae); 449 } 450 } 451 else 452 { 453 throw new LDAPException(ResultCode.DECODING_ERROR, 454 ERR_PWP_RESPONSE_MULTIPLE_WARNING.get()); 455 } 456 break; 457 458 case TYPE_ERROR: 459 if (et == null) 460 { 461 try 462 { 463 final ASN1Enumerated errorElement = 464 ASN1Enumerated.decodeAsEnumerated(e); 465 et = PasswordPolicyErrorType.valueOf(errorElement.intValue()); 466 if (et == null) 467 { 468 throw new LDAPException(ResultCode.DECODING_ERROR, 469 ERR_PWP_RESPONSE_INVALID_ERROR_TYPE.get( 470 errorElement.intValue())); 471 } 472 } 473 catch (final ASN1Exception ae) 474 { 475 Debug.debugException(ae); 476 throw new LDAPException(ResultCode.DECODING_ERROR, 477 ERR_PWP_RESPONSE_CANNOT_DECODE_ERROR.get(ae), ae); 478 } 479 } 480 else 481 { 482 throw new LDAPException(ResultCode.DECODING_ERROR, 483 ERR_PWP_RESPONSE_MULTIPLE_ERROR.get()); 484 } 485 break; 486 487 default: 488 throw new LDAPException(ResultCode.DECODING_ERROR, 489 ERR_PWP_RESPONSE_INVALID_TYPE.get( 490 StaticUtils.toHex(e.getType()))); 491 } 492 } 493 494 warningType = wt; 495 warningValue = wv; 496 errorType = et; 497 } 498 499 500 501 /** 502 * {@inheritDoc} 503 */ 504 @Override() 505 @NotNull() 506 public PasswordPolicyResponseControl 507 decodeControl(@NotNull final String oid, final boolean isCritical, 508 @Nullable final ASN1OctetString value) 509 throws LDAPException 510 { 511 return new PasswordPolicyResponseControl(oid, isCritical, value); 512 } 513 514 515 516 /** 517 * Extracts a password policy response control from the provided result. 518 * 519 * @param result The result from which to retrieve the password policy 520 * response control. 521 * 522 * @return The password policy response control contained in the provided 523 * result, or {@code null} if the result did not contain a password 524 * policy response control. 525 * 526 * @throws LDAPException If a problem is encountered while attempting to 527 * decode the password policy response control 528 * contained in the provided result. 529 */ 530 @Nullable() 531 public static PasswordPolicyResponseControl get( 532 @NotNull final LDAPResult result) 533 throws LDAPException 534 { 535 final Control c = result.getResponseControl(PASSWORD_POLICY_RESPONSE_OID); 536 if (c == null) 537 { 538 return null; 539 } 540 541 if (c instanceof PasswordPolicyResponseControl) 542 { 543 return (PasswordPolicyResponseControl) c; 544 } 545 else 546 { 547 return new PasswordPolicyResponseControl(c.getOID(), c.isCritical(), 548 c.getValue()); 549 } 550 } 551 552 553 554 /** 555 * Encodes the provided information as appropriate for use as the value of a 556 * password policy response control. 557 * 558 * @param warningType The warning type to use for the warning element, or 559 * {@code null} if there is not to be a warning element. 560 * @param warningValue The value to use for the warning element. 561 * @param errorType The error type to use for the error element, or 562 * {@code null} if there is not to be an error element. 563 * 564 * @return The ASN.1 octet string containing the encoded control value. 565 */ 566 @NotNull() 567 private static ASN1OctetString encodeValue( 568 @Nullable final PasswordPolicyWarningType warningType, 569 final int warningValue, 570 @Nullable final PasswordPolicyErrorType errorType) 571 { 572 final ArrayList<ASN1Element> valueElements = new ArrayList<>(2); 573 574 if (warningType != null) 575 { 576 switch (warningType) 577 { 578 case TIME_BEFORE_EXPIRATION: 579 valueElements.add(new ASN1Element(TYPE_WARNING, 580 new ASN1Integer(TYPE_TIME_BEFORE_EXPIRATION, 581 warningValue).encode())); 582 break; 583 584 case GRACE_LOGINS_REMAINING: 585 valueElements.add(new ASN1Element(TYPE_WARNING, 586 new ASN1Integer(TYPE_GRACE_LOGINS_REMAINING, 587 warningValue).encode())); 588 break; 589 } 590 } 591 592 if (errorType != null) 593 { 594 valueElements.add(new ASN1Enumerated(TYPE_ERROR, errorType.intValue())); 595 } 596 597 return new ASN1OctetString(new ASN1Sequence(valueElements).encode()); 598 } 599 600 601 602 /** 603 * Retrieves the warning type for this password policy response control, if 604 * available. 605 * 606 * @return The warning type for this password policy response control, or 607 * {@code null} if there is no warning type. 608 */ 609 @Nullable() 610 public PasswordPolicyWarningType getWarningType() 611 { 612 return warningType; 613 } 614 615 616 617 /** 618 * Retrieves the warning value for this password policy response control, if 619 * available. 620 * 621 * @return The warning value for this password policy response control, or -1 622 * if there is no warning type. 623 */ 624 public int getWarningValue() 625 { 626 return warningValue; 627 } 628 629 630 631 /** 632 * Retrieves the error type for this password policy response control, if 633 * available. 634 * 635 * @return The error type for this password policy response control, or 636 * {@code null} if there is no error type. 637 */ 638 @Nullable() 639 public PasswordPolicyErrorType getErrorType() 640 { 641 return errorType; 642 } 643 644 645 646 /** 647 * {@inheritDoc} 648 */ 649 @Override() 650 @NotNull() 651 public String getControlName() 652 { 653 return INFO_CONTROL_NAME_PW_POLICY_RESPONSE.get(); 654 } 655 656 657 658 /** 659 * Retrieves a representation of this password policy response control as a 660 * JSON object. The JSON object uses the following fields: 661 * <UL> 662 * <LI> 663 * {@code oid} -- A mandatory string field whose value is the object 664 * identifier for this control. For the password policy response control, 665 * the OID is "1.3.6.1.4.1.42.2.27.8.5.1". 666 * </LI> 667 * <LI> 668 * {@code control-name} -- An optional string field whose value is a 669 * human-readable name for this control. This field is only intended for 670 * descriptive purposes, and when decoding a control, the {@code oid} 671 * field should be used to identify the type of control. 672 * </LI> 673 * <LI> 674 * {@code criticality} -- A mandatory Boolean field used to indicate 675 * whether this control is considered critical. 676 * </LI> 677 * <LI> 678 * {@code value-base64} -- An optional string field whose value is a 679 * base64-encoded representation of the raw value for this password policy 680 * response control. Exactly one of the {@code value-base64} and 681 * {@code value-json} fields must be present. 682 * </LI> 683 * <LI> 684 * {@code value-json} -- An optional JSON object field whose value is a 685 * user-friendly representation of the value for this password policy 686 * response control. Exactly one of the {@code value-base64} and 687 * {@code value-json} fields must be present, and if the 688 * {@code value-json} field is used, then it will use the following 689 * fields: 690 * <UL> 691 * <LI> 692 * {@code warning} -- An optional JSON object field whose value 693 * represents a warning about the user's password policy state. If 694 * present, the JSON object must contain exactly one of the following 695 * fields: 696 * <UL> 697 * <LI> 698 * {@code seconds-until-expiration} -- An integer field whose 699 * value is the number of seconds until the user's password 700 * expires. 701 * </LI> 702 * <LI> 703 * {@code grace-logins-remaining} -- An integer field whose value 704 * value is the number of grace login attempts that the user has 705 * left. 706 * </LI> 707 * </UL> 708 * </LI> 709 * <LI> 710 * {@code error-type} -- An optional string field whose value 711 * represents a password policy error condition that applies to the 712 * associated operation. If present, its value will be one of the 713 * following: 714 * <UL> 715 * <LI>{@code password-expired}</LI> 716 * <LI>{@code account-locked}</LI> 717 * <LI>{@code change-after-reset}</LI> 718 * <LI>{@code password-mod-not-allowed}</LI> 719 * <LI>{@code must-supply-old-password}</LI> 720 * <LI>{@code insufficient-password-quality}</LI> 721 * <LI>{@code password-too-short}</LI> 722 * <LI>{@code password-too-young}</LI> 723 * <LI>{@code password-in-history}</LI> 724 * </UL> 725 * </LI> 726 * </UL> 727 * </LI> 728 * </UL> 729 * 730 * @return A JSON object that contains a representation of this control. 731 */ 732 @Override() 733 @NotNull() 734 public JSONObject toJSONControl() 735 { 736 final Map<String,JSONValue> valueFields = new LinkedHashMap<>(); 737 738 if (warningType != null) 739 { 740 switch (warningType) 741 { 742 case TIME_BEFORE_EXPIRATION: 743 valueFields.put(JSON_FIELD_WARNING, new JSONObject( 744 new JSONField(JSON_FIELD_SECONDS_UNTIL_EXPIRATION, 745 warningValue))); 746 break; 747 case GRACE_LOGINS_REMAINING: 748 valueFields.put(JSON_FIELD_WARNING, new JSONObject( 749 new JSONField(JSON_FIELD_GRACE_LOGINS_REMAINING, warningValue))); 750 break; 751 } 752 } 753 754 if (errorType != null) 755 { 756 switch (errorType) 757 { 758 case PASSWORD_EXPIRED: 759 valueFields.put(JSON_FIELD_ERROR_TYPE, 760 new JSONString(JSON_ERROR_TYPE_PASSWORD_EXPIRED)); 761 break; 762 case ACCOUNT_LOCKED: 763 valueFields.put(JSON_FIELD_ERROR_TYPE, 764 new JSONString(JSON_ERROR_TYPE_ACCOUNT_LOCKED)); 765 break; 766 case CHANGE_AFTER_RESET: 767 valueFields.put(JSON_FIELD_ERROR_TYPE, 768 new JSONString(JSON_ERROR_TYPE_CHANGE_AFTER_RESET)); 769 break; 770 case PASSWORD_MOD_NOT_ALLOWED: 771 valueFields.put(JSON_FIELD_ERROR_TYPE, 772 new JSONString(JSON_ERROR_TYPE_PASSWORD_MOD_NOT_ALLOWED)); 773 break; 774 case MUST_SUPPLY_OLD_PASSWORD: 775 valueFields.put(JSON_FIELD_ERROR_TYPE, 776 new JSONString(JSON_ERROR_TYPE_MUST_SUPPLY_OLD_PASSWORD)); 777 break; 778 case INSUFFICIENT_PASSWORD_QUALITY: 779 valueFields.put(JSON_FIELD_ERROR_TYPE, 780 new JSONString(JSON_ERROR_TYPE_INSUFFICIENT_PASSWORD_QUALITY)); 781 break; 782 case PASSWORD_TOO_SHORT: 783 valueFields.put(JSON_FIELD_ERROR_TYPE, 784 new JSONString(JSON_ERROR_TYPE_PASSWORD_TOO_SHORT)); 785 break; 786 case PASSWORD_TOO_YOUNG: 787 valueFields.put(JSON_FIELD_ERROR_TYPE, 788 new JSONString(JSON_ERROR_TYPE_PASSWORD_TOO_YOUNG)); 789 break; 790 case PASSWORD_IN_HISTORY: 791 valueFields.put(JSON_FIELD_ERROR_TYPE, 792 new JSONString(JSON_ERROR_TYPE_PASSWORD_IN_HISTORY)); 793 break; 794 } 795 } 796 797 return new JSONObject( 798 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 799 PASSWORD_POLICY_RESPONSE_OID), 800 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 801 INFO_CONTROL_NAME_PW_POLICY_RESPONSE.get()), 802 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 803 isCritical()), 804 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 805 new JSONObject(valueFields))); 806 } 807 808 809 810 /** 811 * Attempts to decode the provided object as a JSON representation of a 812 * password policy response control. 813 * 814 * @param controlObject The JSON object to be decoded. It must not be 815 * {@code null}. 816 * @param strict Indicates whether to use strict mode when decoding 817 * the provided JSON object. If this is {@code true}, 818 * then this method will throw an exception if the 819 * provided JSON object contains any unrecognized 820 * fields. If this is {@code false}, then unrecognized 821 * fields will be ignored. 822 * 823 * @return The password policy response control that was decoded from the 824 * provided JSON object. 825 * 826 * @throws LDAPException If the provided JSON object cannot be parsed as a 827 * valid password policy response control. 828 */ 829 @NotNull() 830 public static PasswordPolicyResponseControl decodeJSONControl( 831 @NotNull final JSONObject controlObject, 832 final boolean strict) 833 throws LDAPException 834 { 835 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 836 controlObject, strict, true, true); 837 838 final ASN1OctetString rawValue = jsonControl.getRawValue(); 839 if (rawValue != null) 840 { 841 return new PasswordPolicyResponseControl(jsonControl.getOID(), 842 jsonControl.getCriticality(), rawValue); 843 } 844 845 846 final JSONObject valueObject = jsonControl.getValueObject(); 847 848 final PasswordPolicyWarningType warningType; 849 final int warningValue; 850 final JSONObject warningObject = 851 valueObject.getFieldAsObject(JSON_FIELD_WARNING); 852 if (warningObject == null) 853 { 854 warningType = null; 855 warningValue = -1; 856 } 857 else 858 { 859 final Integer secondsUntilExpiration = 860 warningObject.getFieldAsInteger(JSON_FIELD_SECONDS_UNTIL_EXPIRATION); 861 final Integer graceLoginsRemaining = 862 warningObject.getFieldAsInteger(JSON_FIELD_GRACE_LOGINS_REMAINING); 863 if (secondsUntilExpiration == null) 864 { 865 if (graceLoginsRemaining == null) 866 { 867 throw new LDAPException(ResultCode.DECODING_ERROR, 868 ERR_PWP_RESPONSE_JSON_NO_RECOGNIZED_WARNING_TYPE.get( 869 controlObject.toSingleLineString(), JSON_FIELD_WARNING)); 870 } 871 else 872 { 873 warningType = PasswordPolicyWarningType.GRACE_LOGINS_REMAINING; 874 warningValue = graceLoginsRemaining; 875 } 876 } 877 else if (graceLoginsRemaining == null) 878 { 879 warningType = PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION; 880 warningValue = secondsUntilExpiration; 881 } 882 else 883 { 884 throw new LDAPException(ResultCode.DECODING_ERROR, 885 ERR_PWP_RESPONSE_JSON_MULTIPLE_WARNING_TYPES.get( 886 controlObject.toSingleLineString(), 887 JSON_FIELD_WARNING)); 888 } 889 890 if (strict) 891 { 892 final List<String> unrecognizedFields = 893 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 894 warningObject, JSON_FIELD_SECONDS_UNTIL_EXPIRATION, 895 JSON_FIELD_GRACE_LOGINS_REMAINING); 896 if (! unrecognizedFields.isEmpty()) 897 { 898 throw new LDAPException(ResultCode.DECODING_ERROR, 899 ERR_PWP_RESPONSE_JSON_UNRECOGNIZED_WARNING_FIELD.get( 900 controlObject.toSingleLineString(), JSON_FIELD_WARNING, 901 unrecognizedFields.get(0))); 902 } 903 } 904 } 905 906 907 final PasswordPolicyErrorType errorType; 908 final String errorTypeString = 909 valueObject.getFieldAsString(JSON_FIELD_ERROR_TYPE); 910 if (errorTypeString == null) 911 { 912 errorType = null; 913 } 914 else 915 { 916 switch (errorTypeString) 917 { 918 case JSON_ERROR_TYPE_PASSWORD_EXPIRED: 919 errorType = PasswordPolicyErrorType.PASSWORD_EXPIRED; 920 break; 921 case JSON_ERROR_TYPE_ACCOUNT_LOCKED: 922 errorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; 923 break; 924 case JSON_ERROR_TYPE_CHANGE_AFTER_RESET: 925 errorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET; 926 break; 927 case JSON_ERROR_TYPE_PASSWORD_MOD_NOT_ALLOWED: 928 errorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED; 929 break; 930 case JSON_ERROR_TYPE_MUST_SUPPLY_OLD_PASSWORD: 931 errorType = PasswordPolicyErrorType.MUST_SUPPLY_OLD_PASSWORD; 932 break; 933 case JSON_ERROR_TYPE_INSUFFICIENT_PASSWORD_QUALITY: 934 errorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY; 935 break; 936 case JSON_ERROR_TYPE_PASSWORD_TOO_SHORT: 937 errorType = PasswordPolicyErrorType.PASSWORD_TOO_SHORT; 938 break; 939 case JSON_ERROR_TYPE_PASSWORD_TOO_YOUNG: 940 errorType = PasswordPolicyErrorType.PASSWORD_TOO_YOUNG; 941 break; 942 case JSON_ERROR_TYPE_PASSWORD_IN_HISTORY: 943 errorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY; 944 break; 945 default: 946 throw new LDAPException(ResultCode.DECODING_ERROR, 947 ERR_PWP_RESPONSE_JSON_UNRECOGNIZED_ERROR_TYPE.get( 948 controlObject.toSingleLineString(), JSON_FIELD_ERROR_TYPE, 949 errorTypeString)); 950 } 951 } 952 953 954 if (strict) 955 { 956 final List<String> unrecognizedFields = 957 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 958 valueObject, JSON_FIELD_WARNING, JSON_FIELD_ERROR_TYPE); 959 if (! unrecognizedFields.isEmpty()) 960 { 961 throw new LDAPException(ResultCode.DECODING_ERROR, 962 ERR_PWP_RESPONSE_JSON_UNRECOGNIZED_VALUE_FIELD.get( 963 controlObject.toSingleLineString(), 964 unrecognizedFields.get(0))); 965 } 966 } 967 968 969 return new PasswordPolicyResponseControl(warningType, warningValue, 970 errorType, jsonControl.getCriticality()); 971 } 972 973 974 975 /** 976 * {@inheritDoc} 977 */ 978 @Override() 979 public void toString(@NotNull final StringBuilder buffer) 980 { 981 982 buffer.append("PasswordPolicyResponseControl("); 983 984 boolean elementAdded = false; 985 if (warningType != null) 986 { 987 buffer.append("warningType='"); 988 buffer.append(warningType.getName()); 989 buffer.append("', warningValue="); 990 buffer.append(warningValue); 991 elementAdded = true; 992 } 993 994 if (errorType != null) 995 { 996 if (elementAdded) 997 { 998 buffer.append(", "); 999 } 1000 1001 buffer.append("errorType='"); 1002 buffer.append(errorType.getName()); 1003 buffer.append('\''); 1004 elementAdded = true; 1005 } 1006 1007 if (elementAdded) 1008 { 1009 buffer.append(", "); 1010 } 1011 1012 buffer.append("isCritical="); 1013 buffer.append(isCritical()); 1014 buffer.append(')'); 1015 } 1016}