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