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