001/* 002 * Copyright 2007-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2015-2018 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.NotMutable; 039import com.unboundid.util.ThreadSafety; 040import com.unboundid.util.ThreadSafetyLevel; 041 042import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 043import static com.unboundid.util.Debug.*; 044import static com.unboundid.util.StaticUtils.*; 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 Alcatel-Lucent 8661 063 * server products. These classes provide support for proprietary 064 * functionality or for external specifications that are not considered stable 065 * or mature enough to be guaranteed to work in an interoperable way with 066 * 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 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 toHex(warningElement.getType()))); 299 } 300 } 301 catch (final ASN1Exception ae) 302 { 303 debugException(ae); 304 throw new LDAPException(ResultCode.DECODING_ERROR, 305 ERR_PWP_RESPONSE_CANNOT_DECODE_WARNING.get(ae), 306 ae); 307 } 308 } 309 else 310 { 311 throw new LDAPException(ResultCode.DECODING_ERROR, 312 ERR_PWP_RESPONSE_MULTIPLE_WARNING.get()); 313 } 314 break; 315 316 case TYPE_ERROR: 317 if (et == null) 318 { 319 try 320 { 321 final ASN1Enumerated errorElement = 322 ASN1Enumerated.decodeAsEnumerated(e); 323 et = PasswordPolicyErrorType.valueOf(errorElement.intValue()); 324 if (et == null) 325 { 326 throw new LDAPException(ResultCode.DECODING_ERROR, 327 ERR_PWP_RESPONSE_INVALID_ERROR_TYPE.get( 328 errorElement.intValue())); 329 } 330 } 331 catch (final ASN1Exception ae) 332 { 333 debugException(ae); 334 throw new LDAPException(ResultCode.DECODING_ERROR, 335 ERR_PWP_RESPONSE_CANNOT_DECODE_ERROR.get(ae), ae); 336 } 337 } 338 else 339 { 340 throw new LDAPException(ResultCode.DECODING_ERROR, 341 ERR_PWP_RESPONSE_MULTIPLE_ERROR.get()); 342 } 343 break; 344 345 default: 346 throw new LDAPException(ResultCode.DECODING_ERROR, 347 ERR_PWP_RESPONSE_INVALID_TYPE.get( 348 toHex(e.getType()))); 349 } 350 } 351 352 warningType = wt; 353 warningValue = wv; 354 errorType = et; 355 } 356 357 358 359 /** 360 * {@inheritDoc} 361 */ 362 @Override() 363 public PasswordPolicyResponseControl 364 decodeControl(final String oid, final boolean isCritical, 365 final ASN1OctetString value) 366 throws LDAPException 367 { 368 return new PasswordPolicyResponseControl(oid, isCritical, value); 369 } 370 371 372 373 /** 374 * Extracts a password policy response control from the provided result. 375 * 376 * @param result The result from which to retrieve the password policy 377 * response control. 378 * 379 * @return The password policy response control contained in the provided 380 * result, or {@code null} if the result did not contain a password 381 * policy response control. 382 * 383 * @throws LDAPException If a problem is encountered while attempting to 384 * decode the password policy response control 385 * contained in the provided result. 386 */ 387 public static PasswordPolicyResponseControl get(final LDAPResult result) 388 throws LDAPException 389 { 390 final Control c = result.getResponseControl(PASSWORD_POLICY_RESPONSE_OID); 391 if (c == null) 392 { 393 return null; 394 } 395 396 if (c instanceof PasswordPolicyResponseControl) 397 { 398 return (PasswordPolicyResponseControl) c; 399 } 400 else 401 { 402 return new PasswordPolicyResponseControl(c.getOID(), c.isCritical(), 403 c.getValue()); 404 } 405 } 406 407 408 409 /** 410 * Encodes the provided information as appropriate for use as the value of a 411 * password policy response control. 412 * 413 * @param warningType The warning type to use for the warning element, or 414 * {@code null} if there is not to be a warning element. 415 * @param warningValue The value to use for the warning element. 416 * @param errorType The error type to use for the error element, or 417 * {@code null} if there is not to be an error element. 418 * 419 * @return The ASN.1 octet string containing the encoded control value. 420 */ 421 private static ASN1OctetString 422 encodeValue(final PasswordPolicyWarningType warningType, 423 final int warningValue, 424 final PasswordPolicyErrorType errorType) 425 { 426 final ArrayList<ASN1Element> valueElements = new ArrayList<ASN1Element>(2); 427 428 if (warningType != null) 429 { 430 switch (warningType) 431 { 432 case TIME_BEFORE_EXPIRATION: 433 valueElements.add(new ASN1Element(TYPE_WARNING, 434 new ASN1Integer(TYPE_TIME_BEFORE_EXPIRATION, 435 warningValue).encode())); 436 break; 437 438 case GRACE_LOGINS_REMAINING: 439 valueElements.add(new ASN1Element(TYPE_WARNING, 440 new ASN1Integer(TYPE_GRACE_LOGINS_REMAINING, 441 warningValue).encode())); 442 break; 443 } 444 } 445 446 if (errorType != null) 447 { 448 valueElements.add(new ASN1Enumerated(TYPE_ERROR, errorType.intValue())); 449 } 450 451 return new ASN1OctetString(new ASN1Sequence(valueElements).encode()); 452 } 453 454 455 456 /** 457 * Retrieves the warning type for this password policy response control, if 458 * available. 459 * 460 * @return The warning type for this password policy response control, or 461 * {@code null} if there is no warning type. 462 */ 463 public PasswordPolicyWarningType getWarningType() 464 { 465 return warningType; 466 } 467 468 469 470 /** 471 * Retrieves the warning value for this password policy response control, if 472 * available. 473 * 474 * @return The warning value for this password policy response control, or -1 475 * if there is no warning type. 476 */ 477 public int getWarningValue() 478 { 479 return warningValue; 480 } 481 482 483 484 /** 485 * Retrieves the error type for this password policy response control, if 486 * available. 487 * 488 * @return The error type for this password policy response control, or 489 * {@code null} if there is no error type. 490 */ 491 public PasswordPolicyErrorType getErrorType() 492 { 493 return errorType; 494 } 495 496 497 498 /** 499 * {@inheritDoc} 500 */ 501 @Override() 502 public String getControlName() 503 { 504 return INFO_CONTROL_NAME_PW_POLICY_RESPONSE.get(); 505 } 506 507 508 509 /** 510 * {@inheritDoc} 511 */ 512 @Override() 513 public void toString(final StringBuilder buffer) 514 { 515 boolean elementAdded = false; 516 517 buffer.append("PasswordPolicyResponseControl("); 518 519 if (warningType != null) 520 { 521 buffer.append("warningType='"); 522 buffer.append(warningType.getName()); 523 buffer.append("', warningValue="); 524 buffer.append(warningValue); 525 elementAdded = true; 526 } 527 528 if (errorType != null) 529 { 530 if (elementAdded) 531 { 532 buffer.append(", "); 533 } 534 535 buffer.append("errorType='"); 536 buffer.append(errorType.getName()); 537 buffer.append('\''); 538 elementAdded = true; 539 } 540 541 if (elementAdded) 542 { 543 buffer.append(", "); 544 } 545 546 buffer.append("isCritical="); 547 buffer.append(isCritical()); 548 buffer.append(')'); 549 } 550}