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