001/* 002 * Copyright 2007-2017 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2015-2017 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 */ 069@NotMutable() 070@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 071public final class PasswordPolicyResponseControl 072 extends Control 073 implements DecodeableControl 074{ 075 /** 076 * The OID (1.3.6.1.4.1.42.2.27.8.5.1) for the password policy response 077 * control. 078 */ 079 public static final String PASSWORD_POLICY_RESPONSE_OID = 080 "1.3.6.1.4.1.42.2.27.8.5.1"; 081 082 083 084 /** 085 * The BER type for the password policy warning element. 086 */ 087 private static final byte TYPE_WARNING = (byte) 0xA0; 088 089 090 091 /** 092 * The BER type for the password policy error element. 093 */ 094 private static final byte TYPE_ERROR = (byte) 0x81; 095 096 097 098 /** 099 * The BER type for the "time before expiration" warning element. 100 */ 101 private static final byte TYPE_TIME_BEFORE_EXPIRATION = (byte) 0x80; 102 103 104 105 /** 106 * The BER type for the "grace logins remaining" warning element. 107 */ 108 private static final byte TYPE_GRACE_LOGINS_REMAINING = (byte) 0x81; 109 110 111 112 /** 113 * The serial version UID for this serializable class. 114 */ 115 private static final long serialVersionUID = 1835830253434331833L; 116 117 118 119 // The password policy warning value, if applicable. 120 private final int warningValue; 121 122 // The password policy error type, if applicable. 123 private final PasswordPolicyErrorType errorType; 124 125 // The password policy warning type, if applicable. 126 private final PasswordPolicyWarningType warningType; 127 128 129 130 /** 131 * Creates a new empty control instance that is intended to be used only for 132 * decoding controls via the {@code DecodeableControl} interface. 133 */ 134 PasswordPolicyResponseControl() 135 { 136 warningType = null; 137 errorType = null; 138 warningValue = -1; 139 } 140 141 142 143 /** 144 * Creates a new password policy response control with the provided 145 * information. It will not be critical. 146 * 147 * @param warningType The password policy warning type for this response 148 * control, or {@code null} if there should be no 149 * warning type. 150 * @param warningValue The value for the password policy warning type, or -1 151 * if there is no warning type. 152 * @param errorType The password policy error type for this response 153 * control, or {@code null} if there should be no error 154 * type. 155 */ 156 public PasswordPolicyResponseControl( 157 final PasswordPolicyWarningType warningType, 158 final int warningValue, final PasswordPolicyErrorType errorType) 159 { 160 this(warningType, warningValue, errorType, false); 161 } 162 163 164 165 /** 166 * Creates a new password policy response control with the provided 167 * information. 168 * 169 * @param warningType The password policy warning type for this response 170 * control, or {@code null} if there should be no 171 * warning type. 172 * @param warningValue The value for the password policy warning type, or -1 173 * if there is no warning type. 174 * @param errorType The password policy error type for this response 175 * control, or {@code null} if there should be no error 176 * type. 177 * @param isCritical Indicates whether this control should be marked 178 * critical. Response controls should generally not be 179 * critical. 180 */ 181 public PasswordPolicyResponseControl( 182 final PasswordPolicyWarningType warningType, 183 final int warningValue, final PasswordPolicyErrorType errorType, 184 final boolean isCritical) 185 { 186 super(PASSWORD_POLICY_RESPONSE_OID, isCritical, 187 encodeValue(warningType, warningValue, errorType)); 188 189 this.warningType = warningType; 190 this.errorType = errorType; 191 192 if (warningType == null) 193 { 194 this.warningValue = -1; 195 } 196 else 197 { 198 this.warningValue = warningValue; 199 } 200 } 201 202 203 204 /** 205 * Creates a new password policy response control with the provided 206 * information. 207 * 208 * @param oid The OID for the control. 209 * @param isCritical Indicates whether the control should be marked 210 * critical. 211 * @param value The encoded value for the control. This may be 212 * {@code null} if no value was provided. 213 * 214 * @throws LDAPException If the provided control cannot be decoded as a 215 * password policy response control. 216 */ 217 public PasswordPolicyResponseControl(final String oid, 218 final boolean isCritical, 219 final ASN1OctetString value) 220 throws LDAPException 221 { 222 super(oid, isCritical, value); 223 224 if (value == null) 225 { 226 throw new LDAPException(ResultCode.DECODING_ERROR, 227 ERR_PWP_RESPONSE_NO_VALUE.get()); 228 } 229 230 final ASN1Sequence valueSequence; 231 try 232 { 233 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 234 valueSequence = ASN1Sequence.decodeAsSequence(valueElement); 235 } 236 catch (final ASN1Exception ae) 237 { 238 debugException(ae); 239 throw new LDAPException(ResultCode.DECODING_ERROR, 240 ERR_PWP_RESPONSE_VALUE_NOT_SEQUENCE.get(ae), ae); 241 } 242 243 final ASN1Element[] valueElements = valueSequence.elements(); 244 if (valueElements.length > 2) 245 { 246 throw new LDAPException(ResultCode.DECODING_ERROR, 247 ERR_PWP_RESPONSE_INVALID_ELEMENT_COUNT.get( 248 valueElements.length)); 249 } 250 251 int wv = -1; 252 PasswordPolicyErrorType et = null; 253 PasswordPolicyWarningType wt = null; 254 for (final ASN1Element e : valueElements) 255 { 256 switch (e.getType()) 257 { 258 case TYPE_WARNING: 259 if (wt == null) 260 { 261 try 262 { 263 final ASN1Element warningElement = 264 ASN1Element.decode(e.getValue()); 265 wv = ASN1Integer.decodeAsInteger(warningElement).intValue(); 266 switch (warningElement.getType()) 267 { 268 case TYPE_TIME_BEFORE_EXPIRATION: 269 wt = PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION; 270 break; 271 272 case TYPE_GRACE_LOGINS_REMAINING: 273 wt = PasswordPolicyWarningType.GRACE_LOGINS_REMAINING; 274 break; 275 276 default: 277 throw new LDAPException(ResultCode.DECODING_ERROR, 278 ERR_PWP_RESPONSE_INVALID_WARNING_TYPE.get( 279 toHex(warningElement.getType()))); 280 } 281 } 282 catch (final ASN1Exception ae) 283 { 284 debugException(ae); 285 throw new LDAPException(ResultCode.DECODING_ERROR, 286 ERR_PWP_RESPONSE_CANNOT_DECODE_WARNING.get(ae), 287 ae); 288 } 289 } 290 else 291 { 292 throw new LDAPException(ResultCode.DECODING_ERROR, 293 ERR_PWP_RESPONSE_MULTIPLE_WARNING.get()); 294 } 295 break; 296 297 case TYPE_ERROR: 298 if (et == null) 299 { 300 try 301 { 302 final ASN1Enumerated errorElement = 303 ASN1Enumerated.decodeAsEnumerated(e); 304 et = PasswordPolicyErrorType.valueOf(errorElement.intValue()); 305 if (et == null) 306 { 307 throw new LDAPException(ResultCode.DECODING_ERROR, 308 ERR_PWP_RESPONSE_INVALID_ERROR_TYPE.get( 309 errorElement.intValue())); 310 } 311 } 312 catch (final ASN1Exception ae) 313 { 314 debugException(ae); 315 throw new LDAPException(ResultCode.DECODING_ERROR, 316 ERR_PWP_RESPONSE_CANNOT_DECODE_ERROR.get(ae), ae); 317 } 318 } 319 else 320 { 321 throw new LDAPException(ResultCode.DECODING_ERROR, 322 ERR_PWP_RESPONSE_MULTIPLE_ERROR.get()); 323 } 324 break; 325 326 default: 327 throw new LDAPException(ResultCode.DECODING_ERROR, 328 ERR_PWP_RESPONSE_INVALID_TYPE.get( 329 toHex(e.getType()))); 330 } 331 } 332 333 warningType = wt; 334 warningValue = wv; 335 errorType = et; 336 } 337 338 339 340 /** 341 * {@inheritDoc} 342 */ 343 @Override() 344 public PasswordPolicyResponseControl 345 decodeControl(final String oid, final boolean isCritical, 346 final ASN1OctetString value) 347 throws LDAPException 348 { 349 return new PasswordPolicyResponseControl(oid, isCritical, value); 350 } 351 352 353 354 /** 355 * Extracts a password policy response control from the provided result. 356 * 357 * @param result The result from which to retrieve the password policy 358 * response control. 359 * 360 * @return The password policy response control contained in the provided 361 * result, or {@code null} if the result did not contain a password 362 * policy response control. 363 * 364 * @throws LDAPException If a problem is encountered while attempting to 365 * decode the password policy response control 366 * contained in the provided result. 367 */ 368 public static PasswordPolicyResponseControl get(final LDAPResult result) 369 throws LDAPException 370 { 371 final Control c = result.getResponseControl(PASSWORD_POLICY_RESPONSE_OID); 372 if (c == null) 373 { 374 return null; 375 } 376 377 if (c instanceof PasswordPolicyResponseControl) 378 { 379 return (PasswordPolicyResponseControl) c; 380 } 381 else 382 { 383 return new PasswordPolicyResponseControl(c.getOID(), c.isCritical(), 384 c.getValue()); 385 } 386 } 387 388 389 390 /** 391 * Encodes the provided information as appropriate for use as the value of a 392 * password policy response control. 393 * 394 * @param warningType The warning type to use for the warning element, or 395 * {@code null} if there is not to be a warning element. 396 * @param warningValue The value to use for the warning element. 397 * @param errorType The error type to use for the error element, or 398 * {@code null} if there is not to be an error element. 399 * 400 * @return The ASN.1 octet string containing the encoded control value. 401 */ 402 private static ASN1OctetString 403 encodeValue(final PasswordPolicyWarningType warningType, 404 final int warningValue, 405 final PasswordPolicyErrorType errorType) 406 { 407 final ArrayList<ASN1Element> valueElements = new ArrayList<ASN1Element>(2); 408 409 if (warningType != null) 410 { 411 switch (warningType) 412 { 413 case TIME_BEFORE_EXPIRATION: 414 valueElements.add(new ASN1Element(TYPE_WARNING, 415 new ASN1Integer(TYPE_TIME_BEFORE_EXPIRATION, 416 warningValue).encode())); 417 break; 418 419 case GRACE_LOGINS_REMAINING: 420 valueElements.add(new ASN1Element(TYPE_WARNING, 421 new ASN1Integer(TYPE_GRACE_LOGINS_REMAINING, 422 warningValue).encode())); 423 break; 424 } 425 } 426 427 if (errorType != null) 428 { 429 valueElements.add(new ASN1Enumerated(TYPE_ERROR, errorType.intValue())); 430 } 431 432 return new ASN1OctetString(new ASN1Sequence(valueElements).encode()); 433 } 434 435 436 437 /** 438 * Retrieves the warning type for this password policy response control, if 439 * available. 440 * 441 * @return The warning type for this password policy response control, or 442 * {@code null} if there is no warning type. 443 */ 444 public PasswordPolicyWarningType getWarningType() 445 { 446 return warningType; 447 } 448 449 450 451 /** 452 * Retrieves the warning value for this password policy response control, if 453 * available. 454 * 455 * @return The warning value for this password policy response control, or -1 456 * if there is no warning type. 457 */ 458 public int getWarningValue() 459 { 460 return warningValue; 461 } 462 463 464 465 /** 466 * Retrieves the error type for this password policy response control, if 467 * available. 468 * 469 * @return The error type for this password policy response control, or 470 * {@code null} if there is no error type. 471 */ 472 public PasswordPolicyErrorType getErrorType() 473 { 474 return errorType; 475 } 476 477 478 479 /** 480 * {@inheritDoc} 481 */ 482 @Override() 483 public String getControlName() 484 { 485 return INFO_CONTROL_NAME_PW_POLICY_RESPONSE.get(); 486 } 487 488 489 490 /** 491 * {@inheritDoc} 492 */ 493 @Override() 494 public void toString(final StringBuilder buffer) 495 { 496 boolean elementAdded = false; 497 498 buffer.append("PasswordPolicyResponseControl("); 499 500 if (warningType != null) 501 { 502 buffer.append("warningType='"); 503 buffer.append(warningType.getName()); 504 buffer.append("', warningValue="); 505 buffer.append(warningValue); 506 elementAdded = true; 507 } 508 509 if (errorType != null) 510 { 511 if (elementAdded) 512 { 513 buffer.append(", "); 514 } 515 516 buffer.append("errorType='"); 517 buffer.append(errorType.getName()); 518 buffer.append('\''); 519 elementAdded = true; 520 } 521 522 if (elementAdded) 523 { 524 buffer.append(", "); 525 } 526 527 buffer.append("isCritical="); 528 buffer.append(isCritical()); 529 buffer.append(')'); 530 } 531}