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