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