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 }