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