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