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