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