001/* 002 * Copyright 2015-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.extensions; 022 023 024 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.Iterator; 029import java.util.List; 030 031import com.unboundid.asn1.ASN1Boolean; 032import com.unboundid.asn1.ASN1Element; 033import com.unboundid.asn1.ASN1Integer; 034import com.unboundid.asn1.ASN1OctetString; 035import com.unboundid.asn1.ASN1Sequence; 036import com.unboundid.ldap.sdk.Control; 037import com.unboundid.ldap.sdk.ExtendedResult; 038import com.unboundid.ldap.sdk.LDAPException; 039import com.unboundid.ldap.sdk.ResultCode; 040import com.unboundid.util.Debug; 041import com.unboundid.util.NotMutable; 042import com.unboundid.util.StaticUtils; 043import com.unboundid.util.ThreadSafety; 044import com.unboundid.util.ThreadSafetyLevel; 045import com.unboundid.util.Validator; 046 047import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*; 048 049 050 051/** 052 * This class provides an implementation of an extended result that can provide 053 * information about the requirements that the server will enforce for 054 * operations that change or replace a user's password, including adding a new 055 * user, a user changing his/her own password, and an administrator resetting 056 * another user's password. 057 * <BR> 058 * <BLOCKQUOTE> 059 * <B>NOTE:</B> This class, and other classes within the 060 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 061 * supported for use against Ping Identity, UnboundID, and Alcatel-Lucent 8661 062 * server products. These classes provide support for proprietary 063 * functionality or for external specifications that are not considered stable 064 * or mature enough to be guaranteed to work in an interoperable way with 065 * other types of LDAP servers. 066 * </BLOCKQUOTE> 067 * <BR> 068 * If the get password quality request was processed successfully, then the 069 * result will include an OID of 1.3.6.1.4.1.30221.2.6.44 and a value with the 070 * following encoding: 071 * <PRE> 072 * GetPasswordQualityRequirementsResultValue ::= SEQUENCE { 073 * requirements SEQUENCE OF PasswordQualityRequirement, 074 * currentPasswordRequired [0] BOOLEAN OPTIONAL, 075 * mustChangePassword [1] BOOLEAN OPTIONAL, 076 * secondsUntilExpiration [2] INTEGER OPTIONAL, 077 * ... } 078 * </PRE> 079 */ 080@NotMutable() 081@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 082public final class GetPasswordQualityRequirementsExtendedResult 083 extends ExtendedResult 084{ 085 /** 086 * The OID (1.3.6.1.4.1.30221.2.6.44) for the get password quality 087 * requirements extended result. 088 */ 089 public static final String OID_GET_PASSWORD_QUALITY_REQUIREMENTS_RESULT = 090 "1.3.6.1.4.1.30221.2.6.44"; 091 092 093 094 /** 095 * The BER type for the current password required element. 096 */ 097 private static final byte TYPE_CURRENT_PW_REQUIRED = (byte) 0x80; 098 099 100 101 /** 102 * The BER type for the must change password element. 103 */ 104 private static final byte TYPE_MUST_CHANGE_PW = (byte) 0x81; 105 106 107 108 /** 109 * The BER type for the seconds until expiration element. 110 */ 111 private static final byte TYPE_SECONDS_UNTIL_EXPIRATION = (byte) 0x82; 112 113 114 115 /** 116 * The serial version UID for this serializable class. 117 */ 118 private static final long serialVersionUID = -4990045432443188148L; 119 120 121 122 // Indicates whether the user will be required to provide his/her current 123 // password when performing the associated self password change. 124 private final Boolean currentPasswordRequired; 125 126 // Indicates whether the user will be required to change his/her password 127 // after performing the associated add or administrative reset. 128 private final Boolean mustChangePassword; 129 130 // The length of time in seconds that the resulting password will be 131 // considered valid. 132 private final Integer secondsUntilExpiration; 133 134 // The list of password quality requirements that the server will enforce for 135 // the associated operation. 136 private final List<PasswordQualityRequirement> passwordRequirements; 137 138 139 140 141 /** 142 * Creates a new get password quality requirements extended result with the 143 * provided information. 144 * 145 * @param messageID The message ID for the LDAP message that 146 * is associated with this LDAP result. 147 * @param resultCode The result code for the response. This 148 * must not be {@code null}. 149 * @param diagnosticMessage The diagnostic message for the response. 150 * This may be {@code null} if no diagnostic 151 * message is needed. 152 * @param matchedDN The matched DN for the response. This may 153 * be {@code null} if no matched DN is 154 * needed. 155 * @param referralURLs The set of referral URLs from the 156 * response. This may be {@code null} or 157 * empty if no referral URLs are needed. 158 * @param passwordRequirements The password quality requirements for this 159 * result. This must be {@code null} or 160 * empty if this result is for an operation 161 * that was not processed successfully. It 162 * may be {@code null} or empty if the 163 * server will not enforce any password 164 * quality requirements for the target 165 * operation. 166 * @param currentPasswordRequired Indicates whether the user will be 167 * required to provide his/her current 168 * password when performing a self change. 169 * This must be {@code null} if this result 170 * is for an operation that was not processed 171 * successfully or if the target operation is 172 * not a self change. 173 * @param mustChangePassword Indicates whether the user will be 174 * required to change their password after 175 * the associated add or administrative 176 * reset before that user will be allowed to 177 * issue any other requests. This must be 178 * {@code null} if this result is for an 179 * operation that was not processed 180 * successfully or if the target operation is 181 * not an add or an administrative reset. 182 * @param secondsUntilExpiration Indicates the maximum length of time, in 183 * seconds, that the password set in the 184 * target operation will be valid. If 185 * {@code mustChangePassword} is {@code true} 186 * then this will indicate the length of time 187 * that the user has to change his/her 188 * password after the add/reset. If 189 * {@code mustChangePassword} is {@code null} 190 * or {@code false} then this will indicate 191 * the length of time until the password 192 * expires. This must be {@code null} if 193 * this result is for an operation that was 194 * not processed successfully, or if the new 195 * password will be valid indefinitely. 196 * @param controls The set of controls to include in the 197 * result. It may be {@code null} or empty 198 * if no controls are needed. 199 */ 200 public GetPasswordQualityRequirementsExtendedResult(final int messageID, 201 final ResultCode resultCode, final String diagnosticMessage, 202 final String matchedDN, final String[] referralURLs, 203 final Collection<PasswordQualityRequirement> passwordRequirements, 204 final Boolean currentPasswordRequired, 205 final Boolean mustChangePassword, 206 final Integer secondsUntilExpiration, 207 final Control... controls) 208 { 209 super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs, 210 ((resultCode == ResultCode.SUCCESS) 211 ? OID_GET_PASSWORD_QUALITY_REQUIREMENTS_RESULT 212 : null), 213 encodeValue(resultCode, passwordRequirements, currentPasswordRequired, 214 mustChangePassword, secondsUntilExpiration), 215 controls); 216 217 if ((passwordRequirements == null) || passwordRequirements.isEmpty()) 218 { 219 this.passwordRequirements = Collections.emptyList(); 220 } 221 else 222 { 223 this.passwordRequirements = Collections.unmodifiableList( 224 new ArrayList<PasswordQualityRequirement>(passwordRequirements)); 225 } 226 227 this.currentPasswordRequired = currentPasswordRequired; 228 this.mustChangePassword = mustChangePassword; 229 this.secondsUntilExpiration = secondsUntilExpiration; 230 } 231 232 233 234 /** 235 * Creates a new get password quality requirements extended result from the 236 * provided generic result. 237 * 238 * @param r The generic extended result to parse as a get password quality 239 * requirements result. 240 * 241 * @throws LDAPException If the provided generic extended result cannot be 242 * parsed as a get password quality requirements 243 * result. 244 */ 245 public GetPasswordQualityRequirementsExtendedResult(final ExtendedResult r) 246 throws LDAPException 247 { 248 super(r); 249 250 final ASN1OctetString value = r.getValue(); 251 if (value == null) 252 { 253 passwordRequirements = Collections.emptyList(); 254 currentPasswordRequired = null; 255 mustChangePassword = null; 256 secondsUntilExpiration = null; 257 return; 258 } 259 260 try 261 { 262 final ASN1Element[] elements = 263 ASN1Sequence.decodeAsSequence(value.getValue()).elements(); 264 265 final ASN1Element[] requirementElements = 266 ASN1Sequence.decodeAsSequence(elements[0]).elements(); 267 final ArrayList<PasswordQualityRequirement> requirementList = 268 new ArrayList<PasswordQualityRequirement>( 269 requirementElements.length); 270 for (final ASN1Element e : requirementElements) 271 { 272 requirementList.add(PasswordQualityRequirement.decode(e)); 273 } 274 passwordRequirements = Collections.unmodifiableList(requirementList); 275 276 Boolean cpr = null; 277 Boolean mcp = null; 278 Integer sue = null; 279 for (int i=1; i < elements.length; i++) 280 { 281 switch (elements[i].getType()) 282 { 283 case TYPE_CURRENT_PW_REQUIRED: 284 cpr = ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); 285 break; 286 287 case TYPE_MUST_CHANGE_PW: 288 mcp = ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); 289 break; 290 291 case TYPE_SECONDS_UNTIL_EXPIRATION: 292 sue = ASN1Integer.decodeAsInteger(elements[i]).intValue(); 293 break; 294 295 default: 296 // We may update this extended operation in the future to provide 297 // support for returning additional password-related information. 298 // If we encounter an unrecognized element, just ignore it rather 299 // than throwing an exception. 300 break; 301 } 302 } 303 304 currentPasswordRequired = cpr; 305 mustChangePassword = mcp; 306 secondsUntilExpiration = sue; 307 } 308 catch (final Exception e) 309 { 310 Debug.debugException(e); 311 throw new LDAPException(ResultCode.DECODING_ERROR, 312 ERR_GET_PW_QUALITY_REQS_RESULT_CANNOT_DECODE.get( 313 StaticUtils.getExceptionMessage(e)), 314 e); 315 } 316 } 317 318 319 320 /** 321 * Encodes the provided information into an ASN.1 octet string suitable for 322 * use as the value for this extended result, if appropriate. 323 * 324 * @param resultCode The result code for the response. This 325 * must not be {@code null}. 326 * @param passwordRequirements The password quality requirements for this 327 * result. This must be {@code null} or 328 * empty if this result is for an operation 329 * that was not processed successfully. It 330 * may be {@code null} or empty if the 331 * server will not enforce any password 332 * quality requirements for the target 333 * operation. 334 * @param currentPasswordRequired Indicates whether the user will be 335 * required to provide his/her current 336 * password when performing a self change. 337 * This must be {@code null} if this result 338 * is for an operation that was not processed 339 * successfully or if the target operation is 340 * not a self change. 341 * @param mustChangePassword Indicates whether the user will be 342 * required to change their password after 343 * the associated add or administrative 344 * reset before that user will be allowed to 345 * issue any other requests. This must be 346 * {@code null} if this result is for an 347 * operation that was not processed 348 * successfully or if the target operation is 349 * not an add or an administrative reset. 350 * @param secondsUntilExpiration Indicates the maximum length of time, in 351 * seconds, that the password set in the 352 * target operation will be valid. If 353 * {@code mustChangePassword} is {@code true} 354 * then this will indicate the length of time 355 * that the user has to change his/her 356 * password after the add/reset. If 357 * {@code mustChangePassword} is {@code null} 358 * or {@code false} then this will indicate 359 * the length of time until the password 360 * expires. This must be {@code null} if 361 * this result is for an operation that was 362 * not processed successfully, or if the new 363 * password will be valid indefinitely. 364 * 365 * @return The ASN.1 element with the encoded result value, or {@code null} 366 * if the result should not have a value. 367 */ 368 private static ASN1OctetString encodeValue(final ResultCode resultCode, 369 final Collection<PasswordQualityRequirement> passwordRequirements, 370 final Boolean currentPasswordRequired, final Boolean mustChangePassword, 371 final Integer secondsUntilExpiration) 372 { 373 if (resultCode != ResultCode.SUCCESS) 374 { 375 Validator.ensureTrue((passwordRequirements == null) || 376 passwordRequirements.isEmpty()); 377 Validator.ensureTrue(currentPasswordRequired == null); 378 Validator.ensureTrue(mustChangePassword == null); 379 Validator.ensureTrue(secondsUntilExpiration == null); 380 381 return null; 382 } 383 384 final ArrayList<ASN1Element> valueSequence = new ArrayList<ASN1Element>(4); 385 386 if (passwordRequirements == null) 387 { 388 valueSequence.add(new ASN1Sequence()); 389 } 390 else 391 { 392 final ArrayList<ASN1Element> requirementElements = 393 new ArrayList<ASN1Element>(passwordRequirements.size()); 394 for (final PasswordQualityRequirement r : passwordRequirements) 395 { 396 requirementElements.add(r.encode()); 397 } 398 valueSequence.add(new ASN1Sequence(requirementElements)); 399 } 400 401 if (currentPasswordRequired != null) 402 { 403 valueSequence.add(new ASN1Boolean(TYPE_CURRENT_PW_REQUIRED, 404 currentPasswordRequired)); 405 } 406 407 if (mustChangePassword != null) 408 { 409 valueSequence.add(new ASN1Boolean(TYPE_MUST_CHANGE_PW, 410 mustChangePassword)); 411 } 412 413 if (secondsUntilExpiration != null) 414 { 415 valueSequence.add(new ASN1Integer(TYPE_SECONDS_UNTIL_EXPIRATION, 416 secondsUntilExpiration)); 417 } 418 419 return new ASN1OctetString(new ASN1Sequence(valueSequence).encode()); 420 } 421 422 423 424 /** 425 * Retrieves the list of password quality requirements that specify the 426 * constraints that a proposed password must satisfy in order to be accepted 427 * by the server in an operation of the type specified in the get password 428 * quality requirements request. 429 * 430 * @return A list of the password quality requirements returned by the 431 * server, or an empty list if this result is for a non-successful 432 * get password quality requirements operation or if the server 433 * will not impose any password quality requirements for the 434 * specified operation type. 435 */ 436 public List<PasswordQualityRequirement> getPasswordRequirements() 437 { 438 return passwordRequirements; 439 } 440 441 442 443 /** 444 * Retrieves a flag that indicates whether the target user will be required to 445 * provide his/her current password in order to set a new password with a self 446 * change. 447 * 448 * @return A value of {@code Boolean.TRUE} if the target operation is a self 449 * change and the user will be required to provide his/her current 450 * password when setting a new one, {@code Boolean.FALSE} if the 451 * target operation is a self change and the user will not be 452 * required to provide his/her current password, or {@code null} if 453 * the target operation is not a self change or if this result is for 454 * a non-successful get password quality requirements operation. 455 */ 456 public Boolean getCurrentPasswordRequired() 457 { 458 return currentPasswordRequired; 459 } 460 461 462 463 /** 464 * Retrieves a flag that indicates whether the target user will be required to 465 * immediately change his/her own password after the associated add or 466 * administrative reset operation before that user will be allowed to issue 467 * any other types of requests. 468 * 469 * @return A value of {@code Boolean.TRUE} if the target operation is an add 470 * or administrative reset and the user will be required to 471 * immediately perform a self change to select a new password before 472 * being allowed to perform any other kinds of operations, 473 * {@code Boolean.FALSE} if the target operation is an add or 474 * administrative reset but the user will not be required to 475 * immediately select a new password with a self change, or 476 * {@code null} if the target operation is not an add or 477 * administrative reset, or if this result is for a non-successful 478 * get password quality requirements operation. 479 */ 480 public Boolean getMustChangePassword() 481 { 482 return mustChangePassword; 483 } 484 485 486 487 /** 488 * Retrieves the length of time, in seconds, that the new password will be 489 * considered valid after the change is applied. If the associated operation 490 * is an add or an administrative reset and {@link #getMustChangePassword()} 491 * returns {@code Boolean.TRUE}, then this will indicate the length of time 492 * that the user has to choose a new password with a self change before the 493 * account becomes locked. If the associated operation is a self change, or 494 * if {@code getMustChangePassword} returns {@code Boolean.FALSE}, then this 495 * will indicate the maximum length of time that the newly-selected password 496 * may be used until it expires. 497 * 498 * @return The length of time, in seconds, that the new password will be 499 * considered valid after the change is applied, or {@code null} if 500 * this result is for a non-successful get password quality 501 * requirements operation or if the newly-selected password can be 502 * used indefinitely. 503 */ 504 public Integer getSecondsUntilExpiration() 505 { 506 return secondsUntilExpiration; 507 } 508 509 510 511 /** 512 * {@inheritDoc} 513 */ 514 @Override() 515 public String getExtendedResultName() 516 { 517 return INFO_EXTENDED_RESULT_NAME_GET_PW_QUALITY_REQS.get(); 518 } 519 520 521 522 /** 523 * {@inheritDoc} 524 */ 525 @Override() 526 public void toString(final StringBuilder buffer) 527 { 528 buffer.append("GetPasswordQualityRequirementsExtendedResult(resultCode="); 529 buffer.append(getResultCode()); 530 531 final int messageID = getMessageID(); 532 if (messageID >= 0) 533 { 534 buffer.append(", messageID="); 535 buffer.append(messageID); 536 } 537 538 buffer.append(", requirements{"); 539 540 final Iterator<PasswordQualityRequirement> requirementsIterator = 541 passwordRequirements.iterator(); 542 while (requirementsIterator.hasNext()) 543 { 544 requirementsIterator.next().toString(buffer); 545 if (requirementsIterator.hasNext()) 546 { 547 buffer.append(','); 548 } 549 } 550 551 buffer.append('}'); 552 553 if (currentPasswordRequired != null) 554 { 555 buffer.append(", currentPasswordRequired="); 556 buffer.append(currentPasswordRequired); 557 } 558 559 if (mustChangePassword != null) 560 { 561 buffer.append(", mustChangePassword="); 562 buffer.append(mustChangePassword); 563 } 564 565 if (secondsUntilExpiration != null) 566 { 567 buffer.append(", secondsUntilExpiration="); 568 buffer.append(secondsUntilExpiration); 569 } 570 571 final String diagnosticMessage = getDiagnosticMessage(); 572 if (diagnosticMessage != null) 573 { 574 buffer.append(", diagnosticMessage='"); 575 buffer.append(diagnosticMessage); 576 buffer.append('\''); 577 } 578 579 final String matchedDN = getMatchedDN(); 580 if (matchedDN != null) 581 { 582 buffer.append(", matchedDN='"); 583 buffer.append(matchedDN); 584 buffer.append('\''); 585 } 586 587 final String[] referralURLs = getReferralURLs(); 588 if (referralURLs.length > 0) 589 { 590 buffer.append(", referralURLs={"); 591 for (int i=0; i < referralURLs.length; i++) 592 { 593 if (i > 0) 594 { 595 buffer.append(", "); 596 } 597 598 buffer.append('\''); 599 buffer.append(referralURLs[i]); 600 buffer.append('\''); 601 } 602 buffer.append('}'); 603 } 604 605 final Control[] responseControls = getResponseControls(); 606 if (responseControls.length > 0) 607 { 608 buffer.append(", responseControls={"); 609 for (int i=0; i < responseControls.length; i++) 610 { 611 if (i > 0) 612 { 613 buffer.append(", "); 614 } 615 616 buffer.append(responseControls[i]); 617 } 618 buffer.append('}'); 619 } 620 621 buffer.append(')'); 622 } 623}