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