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.io.Serializable; 041import java.util.ArrayList; 042import java.util.Collections; 043import java.util.Iterator; 044import java.util.LinkedHashMap; 045import java.util.Map; 046 047import com.unboundid.asn1.ASN1Element; 048import com.unboundid.asn1.ASN1OctetString; 049import com.unboundid.asn1.ASN1Sequence; 050import com.unboundid.asn1.ASN1Set; 051import com.unboundid.ldap.sdk.LDAPException; 052import com.unboundid.ldap.sdk.ResultCode; 053import com.unboundid.util.Debug; 054import com.unboundid.util.NotMutable; 055import com.unboundid.util.StaticUtils; 056import com.unboundid.util.ThreadSafety; 057import com.unboundid.util.ThreadSafetyLevel; 058import com.unboundid.util.Validator; 059 060import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*; 061 062 063 064/** 065 * This class provides a data structure that describes a requirement that 066 * passwords must satisfy in order to be accepted by the server. 067 * <BR> 068 * <BLOCKQUOTE> 069 * <B>NOTE:</B> This class, and other classes within the 070 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 071 * supported for use against Ping Identity, UnboundID, and 072 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 073 * for proprietary functionality or for external specifications that are not 074 * considered stable or mature enough to be guaranteed to work in an 075 * interoperable way with other types of LDAP servers. 076 * </BLOCKQUOTE> 077 * <BR> 078 * A password quality requirement will always include a description, which 079 * should be a string that provides a user-friendly description of the 080 * constraints that a proposed password must satisfy in order to meet this 081 * requirement and be accepted by the server. It may optionally include 082 * additional information that could allow an application to attempt some kind 083 * of pre-validation in order to determine whether a proposed password might 084 * fall outside the constraints associated with this requirement and would 085 * therefore be rejected by the server. This could allow a client to provide 086 * better performance (by not having to submit a password to the server and wait 087 * for the response in order to detect certain kinds of problems) and a better 088 * user experience (for example, by interactively indicating whether the value 089 * is acceptable as the user is entering it). 090 * <BR><BR> 091 * If a password quality requirement object does provide client-side validation 092 * data, then it will include at least a validation type (which indicates the 093 * nature of the validation that will be performed), and an optional set of 094 * properties that provide additional information about the specific nature of 095 * the validation. For example, if the server is configured with a length-based 096 * password validator that requires passwords to be between eight and 20 097 * characters, then the requirement may have a validation type of "length" and 098 * two validation properties: "minimum-length" with a value of "8" and 099 * "maximum-length" with a value of "20". An application that supports this 100 * type of client-side validation could prevent a user from supplying a password 101 * that is too short or too long without the need to communicate with the 102 * server. 103 * <BR><BR> 104 * Note that not all types of password requirements will support client-side 105 * validation. For example, the server may be configured to use a dictionary 106 * with some of the most commonly-used passwords in an attempt to prevent 107 * users from selecting passwords that may be easily guessed, or the server 108 * may be configured with a password history to prevent users from selecting a 109 * password that they had already used. In these kinds of cases, the 110 * application will not have access to the information necessary to make the 111 * determination using client-side logic. The server is the ultimate authority 112 * as to whether a proposed password will be accepted, and even applications 113 * should be prepared to handle the case in which a password is rejected by the 114 * server even if client-side validation does not indicate that there are any 115 * problems with the password. There may also be cases in which the reason that 116 * an attempt to set a password fails for a reason that is not related to the 117 * quality of the provided password. 118 * <BR><BR> 119 * However, even in cases where an application may not be able to perform any 120 * client-side validation, the server may still offer a client-side validation 121 * type and validation properties. This is not intended to help the client 122 * determine whether a proposed password is acceptable, but could allow the 123 * client to convey information about the requirement to the user in a more 124 * flexible manner than simply providing the requirement description (e.g., it 125 * could allow the client to provide information about the requirement to the 126 * user in a different language than the server-provided description, or it 127 * could allow information about one requirement to be split into multiple 128 * elements, or multiple requirements combined into a single element. 129 * <BR><BR> 130 * If it appears in an LDAP protocol element (e.g., a get password quality 131 * requirements extended response, or a password validation details response 132 * control), it should have the following ASN.1 encoding: 133 * <PRE> 134 * PasswordQualityRequirement ::= SEQUENCE { 135 * description OCTET STRING, 136 * clientSideValidationInfo [0] SEQUENCE { 137 * validationType OCTET STRING, 138 * properties [0] SET OF SEQUENCE { 139 * name OCTET STRING, 140 * value OCTET STRING } OPTIONAL } OPTIONAL } 141 * </PRE> 142 */ 143@NotMutable() 144@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 145public final class PasswordQualityRequirement 146 implements Serializable 147{ 148 /** 149 * The BER type that will be used for the optional client-side validation info 150 * element of an encoded password quality requirement. 151 */ 152 private static final byte TYPE_CLIENT_SIDE_VALIDATION_INFO = (byte) 0xA1; 153 154 155 156 /** 157 * The BER type that will be used for the optional validation properties 158 * element of an encoded client-side validation info element. 159 */ 160 private static final byte TYPE_CLIENT_SIDE_VALIDATION_PROPERTIES = 161 (byte) 0xA1; 162 163 164 165 /** 166 * The serial version UID for this serializable class. 167 */ 168 private static final long serialVersionUID = 2956655422853571644L; 169 170 171 172 // A set of properties that may be used to indicate constraints that the 173 // server will impose when validating the password in accordance with this 174 // requirement. 175 private final Map<String,String> clientSideValidationProperties; 176 177 // The name of the client-side validation type for this requirement, if any. 178 private final String clientSideValidationType; 179 180 // A user-friendly description of the constraints that proposed passwords must 181 // satisfy in order to be accepted by the server. 182 private final String description; 183 184 185 186 /** 187 * Creates a new password quality requirement object without any support for 188 * client-side validation. 189 * 190 * @param description A user-friendly description of the constraints that a 191 * proposed password must satisfy in order to meet this 192 * requirement and be accepted by the server. This must 193 * not be {@code null}. 194 */ 195 public PasswordQualityRequirement(final String description) 196 { 197 this(description, null, null); 198 } 199 200 201 202 /** 203 * Creates a new password quality requirement object with optional support for 204 * client-side validation. 205 * 206 * @param description A user-friendly description of the 207 * constraints that a proposed 208 * password must satisfy in order to 209 * meet this requirement and be 210 * accepted by the server. This must 211 * not be {@code null}. 212 * @param clientSideValidationType An optional string that identifies 213 * the type of validation associated 214 * with this requirement. 215 * Applications that support 216 * client-side validation and 217 * recognize this validation type can 218 * attempt to use their own logic in 219 * attempt to determine whether a 220 * proposed password may be rejected 221 * by the server because it does not 222 * satisfy this requirement. This may 223 * be {@code null} if no client-side 224 * validation is available for this 225 * requirement. 226 * @param clientSideValidationProperties An optional map of property names 227 * and values that may provide 228 * additional information that can be 229 * used for client-side validation. 230 * The properties that may be included 231 * depend on the validation type. 232 * This must be empty or {@code null} 233 * if the provided validation type is 234 * {@code null}. It may also be empty 235 * or {@code null} if no additional 236 * properties are required for the 237 * associated type of client-side 238 * validation. 239 */ 240 public PasswordQualityRequirement(final String description, 241 final String clientSideValidationType, 242 final Map<String,String> clientSideValidationProperties) 243 { 244 Validator.ensureNotNull(description); 245 246 if (clientSideValidationType == null) 247 { 248 Validator.ensureTrue((clientSideValidationProperties == null) || 249 clientSideValidationProperties.isEmpty()); 250 } 251 252 this.description = description; 253 this.clientSideValidationType = clientSideValidationType; 254 255 if (clientSideValidationProperties == null) 256 { 257 this.clientSideValidationProperties = Collections.emptyMap(); 258 } 259 else 260 { 261 this.clientSideValidationProperties = Collections.unmodifiableMap( 262 new LinkedHashMap<>(clientSideValidationProperties)); 263 } 264 } 265 266 267 268 /** 269 * Retrieves a user-friendly description of the constraints that a proposed 270 * password must satisfy in order to meet this requirement and be accepted 271 * by the server. 272 * 273 * @return A user-friendly description for this password quality requirement. 274 */ 275 public String getDescription() 276 { 277 return description; 278 } 279 280 281 282 /** 283 * Retrieves a string that identifies the type of client-side validation that 284 * may be performed by applications in order to identify potential problems 285 * with a proposed password before sending it to the server. Client-side 286 * validation may not be available for all types of password quality 287 * requirements. 288 * 289 * @return The client side validation type for this password quality 290 * requirement, or {@code null} if client-side validation is not 291 * supported for this password quality requirement. 292 */ 293 public String getClientSideValidationType() 294 { 295 return clientSideValidationType; 296 } 297 298 299 300 /** 301 * Retrieves a set of properties that may be used in the course of performing 302 * client-side validation for a proposed password. The types of properties 303 * that may be included depend on the client-side validation type. 304 * 305 * @return A map of properties that may be used in the course of performing 306 * client-side validation, or an empty map if client-side validation 307 * is not available for this password quality requirement, or if no 308 * additional properties required for the associated type of 309 * client-side validation. 310 */ 311 public Map<String,String> getClientSideValidationProperties() 312 { 313 return clientSideValidationProperties; 314 } 315 316 317 318 /** 319 * Encodes this password quality requirement to an ASN.1 element that may be 320 * included in LDAP protocol elements that may need to include it (e.g., a 321 * get password quality requirements extended response or a password 322 * validation details response control). 323 * 324 * @return An ASN.1-encoded representation of this password quality 325 * requirement. 326 */ 327 public ASN1Element encode() 328 { 329 final ArrayList<ASN1Element> requirementElements = new ArrayList<>(2); 330 requirementElements.add(new ASN1OctetString(description)); 331 332 if (clientSideValidationType != null) 333 { 334 final ArrayList<ASN1Element> clientSideElements = new ArrayList<>(2); 335 clientSideElements.add(new ASN1OctetString(clientSideValidationType)); 336 337 if (! clientSideValidationProperties.isEmpty()) 338 { 339 final ArrayList<ASN1Element> propertyElements = 340 new ArrayList<>(clientSideValidationProperties.size()); 341 for (final Map.Entry<String,String> e : 342 clientSideValidationProperties.entrySet()) 343 { 344 propertyElements.add(new ASN1Sequence( 345 new ASN1OctetString(e.getKey()), 346 new ASN1OctetString(e.getValue()))); 347 } 348 clientSideElements.add(new ASN1Set( 349 TYPE_CLIENT_SIDE_VALIDATION_PROPERTIES, propertyElements)); 350 } 351 352 requirementElements.add(new ASN1Sequence(TYPE_CLIENT_SIDE_VALIDATION_INFO, 353 clientSideElements)); 354 } 355 356 return new ASN1Sequence(requirementElements); 357 } 358 359 360 361 /** 362 * Decodes the provided ASN.1 element as a password quality requirement. 363 * 364 * @param element The ASN.1 element to decode as a password quality 365 * requirement. It must not be {@code null}. 366 * 367 * @return The decoded password quality requirement. 368 * 369 * @throws LDAPException If a problem was encountered while attempting to 370 * decode the provided ASN.1 element as a password 371 * quality requirement. 372 */ 373 public static PasswordQualityRequirement decode(final ASN1Element element) 374 throws LDAPException 375 { 376 try 377 { 378 final ASN1Element[] requirementElements = 379 ASN1Sequence.decodeAsSequence(element).elements(); 380 381 final String description = ASN1OctetString.decodeAsOctetString( 382 requirementElements[0]).stringValue(); 383 384 String clientSideValidationType = null; 385 Map<String,String> clientSideValidationProperties = null; 386 for (int i=1; i < requirementElements.length; i++) 387 { 388 final ASN1Element requirementElement = requirementElements[i]; 389 switch (requirementElement.getType()) 390 { 391 case TYPE_CLIENT_SIDE_VALIDATION_INFO: 392 final ASN1Element[] csvInfoElements = 393 ASN1Sequence.decodeAsSequence(requirementElement).elements(); 394 clientSideValidationType = ASN1OctetString.decodeAsOctetString( 395 csvInfoElements[0]).stringValue(); 396 397 for (int j=1; j < csvInfoElements.length; j++) 398 { 399 final ASN1Element csvInfoElement = csvInfoElements[j]; 400 switch (csvInfoElement.getType()) 401 { 402 case TYPE_CLIENT_SIDE_VALIDATION_PROPERTIES: 403 final ASN1Element[] csvPropElements = 404 ASN1Sequence.decodeAsSequence(csvInfoElement).elements(); 405 clientSideValidationProperties = new LinkedHashMap<>( 406 StaticUtils.computeMapCapacity(csvPropElements.length)); 407 for (final ASN1Element csvPropElement : csvPropElements) 408 { 409 final ASN1Element[] propElements = 410 ASN1Sequence.decodeAsSequence( 411 csvPropElement).elements(); 412 final String name = ASN1OctetString.decodeAsOctetString( 413 propElements[0]).stringValue(); 414 final String value = ASN1OctetString.decodeAsOctetString( 415 propElements[1]).stringValue(); 416 clientSideValidationProperties.put(name, value); 417 } 418 break; 419 420 default: 421 throw new LDAPException(ResultCode.DECODING_ERROR, 422 ERR_PW_QUALITY_REQ_INVALID_CSV_ELEMENT_TYPE.get( 423 StaticUtils.toHex(csvInfoElement.getType()))); 424 } 425 } 426 427 break; 428 429 default: 430 throw new LDAPException(ResultCode.DECODING_ERROR, 431 ERR_PW_QUALITY_REQ_INVALID_REQ_ELEMENT_TYPE.get( 432 StaticUtils.toHex(requirementElement.getType()))); 433 } 434 } 435 436 return new PasswordQualityRequirement(description, 437 clientSideValidationType, clientSideValidationProperties); 438 } 439 catch (final LDAPException le) 440 { 441 Debug.debugException(le); 442 throw le; 443 } 444 catch (final Exception e) 445 { 446 Debug.debugException(e); 447 throw new LDAPException(ResultCode.DECODING_ERROR, 448 ERR_PW_QUALITY_REQ_DECODE_ERROR.get( 449 StaticUtils.getExceptionMessage(e)), 450 e); 451 } 452 } 453 454 455 456 /** 457 * Retrieves a string representation of this password quality requirement. 458 * 459 * @return A string representation of this password quality requirement. 460 */ 461 @Override() 462 public String toString() 463 { 464 final StringBuilder buffer = new StringBuilder(); 465 toString(buffer); 466 return buffer.toString(); 467 } 468 469 470 471 /** 472 * Appends a string representation of this password quality requirement to the 473 * provided buffer. 474 * 475 * @param buffer The buffer to which the information should be appended. 476 */ 477 public void toString(final StringBuilder buffer) 478 { 479 buffer.append("PasswordQualityRequirement(description='"); 480 buffer.append(description); 481 buffer.append('\''); 482 483 if (clientSideValidationType != null) 484 { 485 buffer.append(", clientSideValidationType='"); 486 buffer.append(clientSideValidationType); 487 buffer.append('\''); 488 489 if (! clientSideValidationProperties.isEmpty()) 490 { 491 buffer.append(", clientSideValidationProperties={"); 492 493 final Iterator<Map.Entry<String,String>> iterator = 494 clientSideValidationProperties.entrySet().iterator(); 495 while (iterator.hasNext()) 496 { 497 final Map.Entry<String,String> e = iterator.next(); 498 499 buffer.append('\''); 500 buffer.append(e.getKey()); 501 buffer.append("'='"); 502 buffer.append(e.getValue()); 503 buffer.append('\''); 504 505 if (iterator.hasNext()) 506 { 507 buffer.append(','); 508 } 509 } 510 511 buffer.append('}'); 512 } 513 } 514 515 buffer.append(')'); 516 } 517}