001/* 002 * Copyright 2007-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2015-2019 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk.unboundidds.controls; 022 023 024 025import java.util.ArrayList; 026 027import com.unboundid.asn1.ASN1Element; 028import com.unboundid.asn1.ASN1Enumerated; 029import com.unboundid.asn1.ASN1Exception; 030import com.unboundid.asn1.ASN1Integer; 031import com.unboundid.asn1.ASN1OctetString; 032import com.unboundid.asn1.ASN1Sequence; 033import com.unboundid.ldap.sdk.Control; 034import com.unboundid.ldap.sdk.DecodeableControl; 035import com.unboundid.ldap.sdk.LDAPException; 036import com.unboundid.ldap.sdk.LDAPResult; 037import com.unboundid.ldap.sdk.ResultCode; 038import com.unboundid.util.Debug; 039import com.unboundid.util.NotMutable; 040import com.unboundid.util.StaticUtils; 041import com.unboundid.util.ThreadSafety; 042import com.unboundid.util.ThreadSafetyLevel; 043 044import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 045 046 047 048/** 049 * This class provides an implementation of the password policy response control 050 * as described in draft-behera-ldap-password-policy. It may be used to provide 051 * information related to a user's password policy. It may include at most one 052 * warning from the set of {@link PasswordPolicyWarningType} values and at most 053 * one error from the set of {@link PasswordPolicyErrorType} values. See the 054 * documentation for those classes for more information on the information that 055 * may be included. See the {@link PasswordPolicyRequestControl} documentation 056 * for an example that demonstrates the use of the password policy request and 057 * response controls. 058 * <BR> 059 * <BLOCKQUOTE> 060 * <B>NOTE:</B> This class, and other classes within the 061 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 062 * supported for use against Ping Identity, UnboundID, and 063 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 064 * for proprietary functionality or for external specifications that are not 065 * considered stable or mature enough to be guaranteed to work in an 066 * interoperable way with other types of LDAP servers. 067 * </BLOCKQUOTE> 068 * <BR> 069 * The control has an OID of 1.3.6.1.4.1.42.2.27.8.5.1 and a criticality of 070 * false. It must have a value with the following encoding: 071 * <PRE> 072 * PasswordPolicyResponseValue ::= SEQUENCE { 073 * warning [0] CHOICE { 074 * timeBeforeExpiration [0] INTEGER (0 .. maxInt), 075 * graceAuthNsRemaining [1] INTEGER (0 .. maxInt) } OPTIONAL, 076 * error [1] ENUMERATED { 077 * passwordExpired (0), 078 * accountLocked (1), 079 * changeAfterReset (2), 080 * passwordModNotAllowed (3), 081 * mustSupplyOldPassword (4), 082 * insufficientPasswordQuality (5), 083 * passwordTooShort (6), 084 * passwordTooYoung (7), 085 * passwordInHistory (8) } OPTIONAL } 086 * </PRE> 087 */ 088@NotMutable() 089@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 090public final class PasswordPolicyResponseControl 091 extends Control 092 implements DecodeableControl 093{ 094 /** 095 * The OID (1.3.6.1.4.1.42.2.27.8.5.1) for the password policy response 096 * control. 097 */ 098 public static final String PASSWORD_POLICY_RESPONSE_OID = 099 "1.3.6.1.4.1.42.2.27.8.5.1"; 100 101 102 103 /** 104 * The BER type for the password policy warning element. 105 */ 106 private static final byte TYPE_WARNING = (byte) 0xA0; 107 108 109 110 /** 111 * The BER type for the password policy error element. 112 */ 113 private static final byte TYPE_ERROR = (byte) 0x81; 114 115 116 117 /** 118 * The BER type for the "time before expiration" warning element. 119 */ 120 private static final byte TYPE_TIME_BEFORE_EXPIRATION = (byte) 0x80; 121 122 123 124 /** 125 * The BER type for the "grace logins remaining" warning element. 126 */ 127 private static final byte TYPE_GRACE_LOGINS_REMAINING = (byte) 0x81; 128 129 130 131 /** 132 * The serial version UID for this serializable class. 133 */ 134 private static final long serialVersionUID = 1835830253434331833L; 135 136 137 138 // The password policy warning value, if applicable. 139 private final int warningValue; 140 141 // The password policy error type, if applicable. 142 private final PasswordPolicyErrorType errorType; 143 144 // The password policy warning type, if applicable. 145 private final PasswordPolicyWarningType warningType; 146 147 148 149 /** 150 * Creates a new empty control instance that is intended to be used only for 151 * decoding controls via the {@code DecodeableControl} interface. 152 */ 153 PasswordPolicyResponseControl() 154 { 155 warningType = null; 156 errorType = null; 157 warningValue = -1; 158 } 159 160 161 162 /** 163 * Creates a new password policy response control with the provided 164 * information. It will not be critical. 165 * 166 * @param warningType The password policy warning type for this response 167 * control, or {@code null} if there should be no 168 * warning type. 169 * @param warningValue The value for the password policy warning type, or -1 170 * if there is no warning type. 171 * @param errorType The password policy error type for this response 172 * control, or {@code null} if there should be no error 173 * type. 174 */ 175 public PasswordPolicyResponseControl( 176 final PasswordPolicyWarningType warningType, 177 final int warningValue, final PasswordPolicyErrorType errorType) 178 { 179 this(warningType, warningValue, errorType, false); 180 } 181 182 183 184 /** 185 * Creates a new password policy response control with the provided 186 * information. 187 * 188 * @param warningType The password policy warning type for this response 189 * control, or {@code null} if there should be no 190 * warning type. 191 * @param warningValue The value for the password policy warning type, or -1 192 * if there is no warning type. 193 * @param errorType The password policy error type for this response 194 * control, or {@code null} if there should be no error 195 * type. 196 * @param isCritical Indicates whether this control should be marked 197 * critical. Response controls should generally not be 198 * critical. 199 */ 200 public PasswordPolicyResponseControl( 201 final PasswordPolicyWarningType warningType, 202 final int warningValue, final PasswordPolicyErrorType errorType, 203 final boolean isCritical) 204 { 205 super(PASSWORD_POLICY_RESPONSE_OID, isCritical, 206 encodeValue(warningType, warningValue, errorType)); 207 208 this.warningType = warningType; 209 this.errorType = errorType; 210 211 if (warningType == null) 212 { 213 this.warningValue = -1; 214 } 215 else 216 { 217 this.warningValue = warningValue; 218 } 219 } 220 221 222 223 /** 224 * Creates a new password policy response control with the provided 225 * information. 226 * 227 * @param oid The OID for the control. 228 * @param isCritical Indicates whether the control should be marked 229 * critical. 230 * @param value The encoded value for the control. This may be 231 * {@code null} if no value was provided. 232 * 233 * @throws LDAPException If the provided control cannot be decoded as a 234 * password policy response control. 235 */ 236 public PasswordPolicyResponseControl(final String oid, 237 final boolean isCritical, 238 final ASN1OctetString value) 239 throws LDAPException 240 { 241 super(oid, isCritical, value); 242 243 if (value == null) 244 { 245 throw new LDAPException(ResultCode.DECODING_ERROR, 246 ERR_PWP_RESPONSE_NO_VALUE.get()); 247 } 248 249 final ASN1Sequence valueSequence; 250 try 251 { 252 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 253 valueSequence = ASN1Sequence.decodeAsSequence(valueElement); 254 } 255 catch (final ASN1Exception ae) 256 { 257 Debug.debugException(ae); 258 throw new LDAPException(ResultCode.DECODING_ERROR, 259 ERR_PWP_RESPONSE_VALUE_NOT_SEQUENCE.get(ae), ae); 260 } 261 262 final ASN1Element[] valueElements = valueSequence.elements(); 263 if (valueElements.length > 2) 264 { 265 throw new LDAPException(ResultCode.DECODING_ERROR, 266 ERR_PWP_RESPONSE_INVALID_ELEMENT_COUNT.get( 267 valueElements.length)); 268 } 269 270 int wv = -1; 271 PasswordPolicyErrorType et = null; 272 PasswordPolicyWarningType wt = null; 273 for (final ASN1Element e : valueElements) 274 { 275 switch (e.getType()) 276 { 277 case TYPE_WARNING: 278 if (wt == null) 279 { 280 try 281 { 282 final ASN1Element warningElement = 283 ASN1Element.decode(e.getValue()); 284 wv = ASN1Integer.decodeAsInteger(warningElement).intValue(); 285 switch (warningElement.getType()) 286 { 287 case TYPE_TIME_BEFORE_EXPIRATION: 288 wt = PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION; 289 break; 290 291 case TYPE_GRACE_LOGINS_REMAINING: 292 wt = PasswordPolicyWarningType.GRACE_LOGINS_REMAINING; 293 break; 294 295 default: 296 throw new LDAPException(ResultCode.DECODING_ERROR, 297 ERR_PWP_RESPONSE_INVALID_WARNING_TYPE.get( 298 StaticUtils.toHex(warningElement.getType()))); 299 } 300 } 301 catch (final ASN1Exception ae) 302 { 303 Debug.debugException(ae); 304 throw new LDAPException(ResultCode.DECODING_ERROR, 305 ERR_PWP_RESPONSE_CANNOT_DECODE_WARNING.get(ae), ae); 306 } 307 } 308 else 309 { 310 throw new LDAPException(ResultCode.DECODING_ERROR, 311 ERR_PWP_RESPONSE_MULTIPLE_WARNING.get()); 312 } 313 break; 314 315 case TYPE_ERROR: 316 if (et == null) 317 { 318 try 319 { 320 final ASN1Enumerated errorElement = 321 ASN1Enumerated.decodeAsEnumerated(e); 322 et = PasswordPolicyErrorType.valueOf(errorElement.intValue()); 323 if (et == null) 324 { 325 throw new LDAPException(ResultCode.DECODING_ERROR, 326 ERR_PWP_RESPONSE_INVALID_ERROR_TYPE.get( 327 errorElement.intValue())); 328 } 329 } 330 catch (final ASN1Exception ae) 331 { 332 Debug.debugException(ae); 333 throw new LDAPException(ResultCode.DECODING_ERROR, 334 ERR_PWP_RESPONSE_CANNOT_DECODE_ERROR.get(ae), ae); 335 } 336 } 337 else 338 { 339 throw new LDAPException(ResultCode.DECODING_ERROR, 340 ERR_PWP_RESPONSE_MULTIPLE_ERROR.get()); 341 } 342 break; 343 344 default: 345 throw new LDAPException(ResultCode.DECODING_ERROR, 346 ERR_PWP_RESPONSE_INVALID_TYPE.get( 347 StaticUtils.toHex(e.getType()))); 348 } 349 } 350 351 warningType = wt; 352 warningValue = wv; 353 errorType = et; 354 } 355 356 357 358 /** 359 * {@inheritDoc} 360 */ 361 @Override() 362 public PasswordPolicyResponseControl 363 decodeControl(final String oid, final boolean isCritical, 364 final ASN1OctetString value) 365 throws LDAPException 366 { 367 return new PasswordPolicyResponseControl(oid, isCritical, value); 368 } 369 370 371 372 /** 373 * Extracts a password policy response control from the provided result. 374 * 375 * @param result The result from which to retrieve the password policy 376 * response control. 377 * 378 * @return The password policy response control contained in the provided 379 * result, or {@code null} if the result did not contain a password 380 * policy response control. 381 * 382 * @throws LDAPException If a problem is encountered while attempting to 383 * decode the password policy response control 384 * contained in the provided result. 385 */ 386 public static PasswordPolicyResponseControl get(final LDAPResult result) 387 throws LDAPException 388 { 389 final Control c = result.getResponseControl(PASSWORD_POLICY_RESPONSE_OID); 390 if (c == null) 391 { 392 return null; 393 } 394 395 if (c instanceof PasswordPolicyResponseControl) 396 { 397 return (PasswordPolicyResponseControl) c; 398 } 399 else 400 { 401 return new PasswordPolicyResponseControl(c.getOID(), c.isCritical(), 402 c.getValue()); 403 } 404 } 405 406 407 408 /** 409 * Encodes the provided information as appropriate for use as the value of a 410 * password policy response control. 411 * 412 * @param warningType The warning type to use for the warning element, or 413 * {@code null} if there is not to be a warning element. 414 * @param warningValue The value to use for the warning element. 415 * @param errorType The error type to use for the error element, or 416 * {@code null} if there is not to be an error element. 417 * 418 * @return The ASN.1 octet string containing the encoded control value. 419 */ 420 private static ASN1OctetString 421 encodeValue(final PasswordPolicyWarningType warningType, 422 final int warningValue, 423 final PasswordPolicyErrorType errorType) 424 { 425 final ArrayList<ASN1Element> valueElements = new ArrayList<>(2); 426 427 if (warningType != null) 428 { 429 switch (warningType) 430 { 431 case TIME_BEFORE_EXPIRATION: 432 valueElements.add(new ASN1Element(TYPE_WARNING, 433 new ASN1Integer(TYPE_TIME_BEFORE_EXPIRATION, 434 warningValue).encode())); 435 break; 436 437 case GRACE_LOGINS_REMAINING: 438 valueElements.add(new ASN1Element(TYPE_WARNING, 439 new ASN1Integer(TYPE_GRACE_LOGINS_REMAINING, 440 warningValue).encode())); 441 break; 442 } 443 } 444 445 if (errorType != null) 446 { 447 valueElements.add(new ASN1Enumerated(TYPE_ERROR, errorType.intValue())); 448 } 449 450 return new ASN1OctetString(new ASN1Sequence(valueElements).encode()); 451 } 452 453 454 455 /** 456 * Retrieves the warning type for this password policy response control, if 457 * available. 458 * 459 * @return The warning type for this password policy response control, or 460 * {@code null} if there is no warning type. 461 */ 462 public PasswordPolicyWarningType getWarningType() 463 { 464 return warningType; 465 } 466 467 468 469 /** 470 * Retrieves the warning value for this password policy response control, if 471 * available. 472 * 473 * @return The warning value for this password policy response control, or -1 474 * if there is no warning type. 475 */ 476 public int getWarningValue() 477 { 478 return warningValue; 479 } 480 481 482 483 /** 484 * Retrieves the error type for this password policy response control, if 485 * available. 486 * 487 * @return The error type for this password policy response control, or 488 * {@code null} if there is no error type. 489 */ 490 public PasswordPolicyErrorType getErrorType() 491 { 492 return errorType; 493 } 494 495 496 497 /** 498 * {@inheritDoc} 499 */ 500 @Override() 501 public String getControlName() 502 { 503 return INFO_CONTROL_NAME_PW_POLICY_RESPONSE.get(); 504 } 505 506 507 508 /** 509 * {@inheritDoc} 510 */ 511 @Override() 512 public void toString(final StringBuilder buffer) 513 { 514 515 buffer.append("PasswordPolicyResponseControl("); 516 517 boolean elementAdded = false; 518 if (warningType != null) 519 { 520 buffer.append("warningType='"); 521 buffer.append(warningType.getName()); 522 buffer.append("', warningValue="); 523 buffer.append(warningValue); 524 elementAdded = true; 525 } 526 527 if (errorType != null) 528 { 529 if (elementAdded) 530 { 531 buffer.append(", "); 532 } 533 534 buffer.append("errorType='"); 535 buffer.append(errorType.getName()); 536 buffer.append('\''); 537 elementAdded = true; 538 } 539 540 if (elementAdded) 541 { 542 buffer.append(", "); 543 } 544 545 buffer.append("isCritical="); 546 buffer.append(isCritical()); 547 buffer.append(')'); 548 } 549}