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.controls;
037
038
039
040import java.util.ArrayList;
041import java.util.Collection;
042import java.util.Collections;
043import java.util.Iterator;
044import java.util.LinkedHashMap;
045import java.util.List;
046import java.util.Map;
047
048import com.unboundid.asn1.ASN1Boolean;
049import com.unboundid.asn1.ASN1Element;
050import com.unboundid.asn1.ASN1Integer;
051import com.unboundid.asn1.ASN1Null;
052import com.unboundid.asn1.ASN1OctetString;
053import com.unboundid.asn1.ASN1Sequence;
054import com.unboundid.ldap.sdk.Control;
055import com.unboundid.ldap.sdk.DecodeableControl;
056import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
057import com.unboundid.ldap.sdk.LDAPException;
058import com.unboundid.ldap.sdk.LDAPResult;
059import com.unboundid.ldap.sdk.ResultCode;
060import com.unboundid.ldap.sdk.unboundidds.extensions.PasswordQualityRequirement;
061import com.unboundid.util.Debug;
062import com.unboundid.util.NotMutable;
063import com.unboundid.util.NotNull;
064import com.unboundid.util.Nullable;
065import com.unboundid.util.StaticUtils;
066import com.unboundid.util.ThreadSafety;
067import com.unboundid.util.ThreadSafetyLevel;
068import com.unboundid.util.json.JSONArray;
069import com.unboundid.util.json.JSONBoolean;
070import com.unboundid.util.json.JSONField;
071import com.unboundid.util.json.JSONNumber;
072import com.unboundid.util.json.JSONObject;
073import com.unboundid.util.json.JSONString;
074import com.unboundid.util.json.JSONValue;
075
076import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
077
078
079
080/**
081 * This class provides an implementation for a response control that can be
082 * returned by the server in the response for add, modify, and password modify
083 * requests that include the password validation details request control.  This
084 * response control will provide details about the password quality requirements
085 * that are in effect for the operation and whether the password included in the
086 * request satisfies each of those requirements.
087 * <BR>
088 * <BLOCKQUOTE>
089 *   <B>NOTE:</B>  This class, and other classes within the
090 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
091 *   supported for use against Ping Identity, UnboundID, and
092 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
093 *   for proprietary functionality or for external specifications that are not
094 *   considered stable or mature enough to be guaranteed to work in an
095 *   interoperable way with other types of LDAP servers.
096 * </BLOCKQUOTE>
097 * <BR>
098 * This response control has an OID of 1.3.6.1.4.1.30221.2.5.41, a criticality
099 * of {@code false}, and a value with the provided encoding:
100 * <PRE>
101 *   PasswordValidationDetailsResponse ::= SEQUENCE {
102 *        validationResult            CHOICE {
103 *             validationDetails             [0] SEQUENCE OF
104 *                  PasswordQualityRequirementValidationResult,
105 *             noPasswordProvided            [1] NULL,
106 *             multiplePasswordsProvided     [2] NULL,
107 *             noValidationAttempted         [3] NULL,
108 *             ... },
109 *        missingCurrentPassword     [3] BOOLEAN DEFAULT FALSE,
110 *        mustChangePassword         [4] BOOLEAN DEFAULT FALSE,
111 *        secondsUntilExpiration     [5] INTEGER OPTIONAL,
112 *        ... }
113 * </PRE>
114 */
115@NotMutable()
116@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
117public final class PasswordValidationDetailsResponseControl
118       extends Control
119       implements DecodeableControl
120{
121 /**
122  * The OID (1.3.6.1.4.1.30221.2.5.41) for the password validation details
123  * response control.
124  */
125 @NotNull public static final String PASSWORD_VALIDATION_DETAILS_RESPONSE_OID =
126      "1.3.6.1.4.1.30221.2.5.41";
127
128
129
130  /**
131   * The BER type for the missing current password element.
132   */
133  private static final byte TYPE_MISSING_CURRENT_PASSWORD = (byte) 0x83;
134
135
136
137  /**
138   * The BER type for the must change password element.
139   */
140  private static final byte TYPE_MUST_CHANGE_PW = (byte) 0x84;
141
142
143
144  /**
145   * The BER type for the seconds until expiration element.
146   */
147  private static final byte TYPE_SECONDS_UNTIL_EXPIRATION = (byte) 0x85;
148
149
150
151  /**
152   * The name of the field used to hold an additional information string in the
153   * JSON representation of this control.
154   */
155  @NotNull private static final String
156       JSON_FIELD_ADDITIONAL_INFORMATION = "additional-information";
157
158
159
160  /**
161   * The name of the field used to hold the set of client-side validation
162   * properties in the JSON representation of this control.
163   */
164  @NotNull private static final String
165       JSON_FIELD_CLIENT_SIDE_VALIDATION_PROPERTIES =
166            "client-side-validation-properties";
167
168
169
170  /**
171   * The name of the field used to hold a client-side validation type in the
172   * JSON representation of this control.
173   */
174  @NotNull private static final String JSON_FIELD_CLIENT_SIDE_VALIDATION_TYPE =
175       "client-side-validation-type";
176
177
178
179  /**
180   * The name of the field used to hold a description in the JSON representation
181   * of this control.
182   */
183  @NotNull private static final String JSON_FIELD_DESCRIPTION = "description";
184
185
186
187  /**
188   * The name of the field used to hold the missing current password flag in the
189   * JSON representation of this control.
190   */
191  @NotNull private static final String JSON_FIELD_MISSING_CURRENT_PASSWORD =
192       "missing-current-password";
193
194
195
196  /**
197   * The name of the field used to hold the must change password flag in the
198   * JSON representation of this control.
199   */
200  @NotNull private static final String JSON_FIELD_MUST_CHANGE_PASSWORD =
201       "must-change-password";
202
203
204
205  /**
206   * The name of the field used to hold a password quality requirement in the
207   * JSON representation of this control.
208   */
209  @NotNull private static final String JSON_FIELD_PASSWORD_QUALITY_REQUIREMENT =
210       "password-quality-requirement";
211
212
213
214  /**
215   * The name of the field used to hold a property name in the JSON
216   * representation of this control.
217   */
218  @NotNull private static final String JSON_FIELD_PROPERTY_NAME = "name";
219
220
221
222  /**
223   * The name of the field used to hold a property value in the JSON
224   * representation of this control.
225   */
226  @NotNull private static final String JSON_FIELD_PROPERTY_VALUE = "value";
227
228
229
230  /**
231   * The name of the field used to indicate whether a password quality
232   * requirement was satisfied in the JSON representation of this control.
233   */
234  @NotNull private static final String JSON_FIELD_REQUIREMENT_SATISFIED =
235       "requirement-satisfied";
236
237
238
239  /**
240   * The name of the field used to hold the response type in the JSON
241   * representation of this control.
242   */
243  @NotNull private static final String JSON_FIELD_RESPONSE_TYPE =
244       "response-type";
245
246
247
248  /**
249   * The name of the field used to hold the seconds until expiration in the JSON
250   * representation of this control.
251   */
252  @NotNull private static final String JSON_FIELD_SECONDS_UNTIL_EXPIRATION =
253       "seconds-until-expiration";
254
255
256
257  /**
258   * The name of the field used to hold validation details objects in the JSON
259   * representation of this control.
260   */
261  @NotNull private static final String JSON_FIELD_VALIDATION_DETAILS =
262       "validation-details";
263
264
265
266  /**
267   * The multiple-passwords-provided response type value in the JSON
268   * representation of this control.
269   */
270  @NotNull private static final String
271       JSON_RESPONSE_TYPE_MULTIPLE_PASSWORDS_PROVIDED =
272            "multiple-passwords-provided";
273
274
275
276  /**
277   * The no-password-provided response type value in the JSON representation of
278   * this control.
279   */
280  @NotNull private static final String JSON_RESPONSE_TYPE_NO_PASSWORD_PROVIDED =
281       "no-password-provided";
282
283
284
285  /**
286   * The no-validation-attempted response type value in the JSON representation
287   * of this control.
288   */
289  @NotNull private static final String
290       JSON_RESPONSE_TYPE_NO_VALIDATION_ATTEMPTED = "no-validation-attempted";
291
292
293
294  /**
295   * The validation-performed response type value in the JSON representation of
296   * this control.
297   */
298  @NotNull private static final String JSON_RESPONSE_TYPE_VALIDATION_PERFORMED =
299       "validation-performed";
300
301
302
303 /**
304  * The serial version UID for this serializable class.
305  */
306 private static final long serialVersionUID = -2205640814914704074L;
307
308
309
310  // Indicates whether the associated password self change operation failed
311  // (or would fail if attempted without validation errors) because the user is
312  // required to provide his/her current password when performing a self change
313  // but did not do so.
314  private final boolean missingCurrentPassword;
315
316  // Indicates whether the user will be required to change his/her password
317  // immediately after the associated add or administrative password reset is
318  // complete.
319  private final boolean mustChangePassword;
320
321  // The length of time in seconds that the new password will be considered
322  // valid.
323  @Nullable private final Integer secondsUntilExpiration;
324
325  // The list of the validation results for the associated operation.
326  @NotNull private final List<PasswordQualityRequirementValidationResult>
327      validationResults;
328
329  // The response type for this password validation details response control.
330  @NotNull private final PasswordValidationDetailsResponseType responseType;
331
332
333
334  /**
335   * Creates a new empty control instance that is intended to be used only for
336   * decoding controls via the {@code DecodeableControl} interface.
337   */
338  PasswordValidationDetailsResponseControl()
339  {
340    responseType = null;
341    validationResults = null;
342    missingCurrentPassword = true;
343    mustChangePassword = true;
344    secondsUntilExpiration = null;
345  }
346
347
348
349  /**
350   * Creates a password validation details response control with the provided
351   * information.
352   *
353   * @param  responseType            The response type for this password
354   *                                 validation details response control.  This
355   *                                 must not be {@code null}.
356   * @param  validationResults       A list of the results obtained when
357   *                                 validating the password against the
358   *                                 password quality requirements.  This must
359   *                                 be {@code null} or empty if the
360   *                                 {@code responseType} element has a value
361   *                                 other than {@code VALIDATION_DETAILS}.
362   * @param  missingCurrentPassword  Indicates whether the associated operation
363   *                                 is a self change that failed (or would have
364   *                                 failed if not for additional validation
365   *                                 failures) because the user did not provide
366   *                                 his/her current password as required.
367   * @param  mustChangePassword      Indicates whether the associated operation
368   *                                 is an add or administrative reset that will
369   *                                 require the user to change his/her password
370   *                                 immediately after authenticating before
371   *                                 allowing them to perform any other
372   *                                 operation in the server.
373   * @param  secondsUntilExpiration  The maximum length of time, in seconds,
374   *                                 that the newly-set password will be
375   *                                 considered valid.  This may be {@code null}
376   *                                 if the new password will be considered
377   *                                 valid indefinitely.
378   */
379  public PasswordValidationDetailsResponseControl(
380       @NotNull final PasswordValidationDetailsResponseType responseType,
381       @Nullable final Collection<PasswordQualityRequirementValidationResult>
382            validationResults,
383       final boolean missingCurrentPassword,
384       final boolean mustChangePassword,
385       @Nullable final Integer secondsUntilExpiration)
386  {
387    super(PASSWORD_VALIDATION_DETAILS_RESPONSE_OID, false,
388         encodeValue(responseType, validationResults, missingCurrentPassword,
389              mustChangePassword, secondsUntilExpiration));
390
391    this.responseType           = responseType;
392    this.missingCurrentPassword = missingCurrentPassword;
393    this.mustChangePassword     = mustChangePassword;
394    this.secondsUntilExpiration = secondsUntilExpiration;
395
396    if (validationResults == null)
397    {
398      this.validationResults = Collections.emptyList();
399    }
400    else
401    {
402      this.validationResults = Collections.unmodifiableList(
403           new ArrayList<>(validationResults));
404    }
405  }
406
407
408
409  /**
410   * Creates a new password validation details response control by decoding the
411   * provided generic control information.
412   *
413   * @param  oid         The OID for the control.
414   * @param  isCritical  Indicates whether the control should be considered
415   *                     critical.
416   * @param  value       The value for the control.
417   *
418   * @throws  LDAPException  If the provided information cannot be decoded to
419   *                         create a password validation details response
420   *                         control.
421   */
422  public PasswordValidationDetailsResponseControl(@NotNull final String oid,
423              final boolean isCritical,
424              @Nullable final ASN1OctetString value)
425         throws LDAPException
426  {
427    super(oid, isCritical, value);
428
429    if (value == null)
430    {
431      throw new LDAPException(ResultCode.DECODING_ERROR,
432           ERR_PW_VALIDATION_RESPONSE_NO_VALUE.get());
433    }
434
435    try
436    {
437      final ASN1Element[] elements =
438           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
439
440      responseType = PasswordValidationDetailsResponseType.forBERType(
441           elements[0].getType());
442      if (responseType == null)
443      {
444        throw new LDAPException(ResultCode.DECODING_ERROR,
445             ERR_PW_VALIDATION_RESPONSE_INVALID_RESPONSE_TYPE.get(
446                  StaticUtils.toHex(elements[0].getType())));
447      }
448
449      if (responseType ==
450          PasswordValidationDetailsResponseType.VALIDATION_DETAILS)
451      {
452        final ASN1Element[] resultElements =
453             ASN1Sequence.decodeAsSequence(elements[0]).elements();
454
455        final ArrayList<PasswordQualityRequirementValidationResult> resultList =
456             new ArrayList<>(resultElements.length);
457        for (final ASN1Element e : resultElements)
458        {
459          resultList.add(PasswordQualityRequirementValidationResult.decode(e));
460        }
461        validationResults = Collections.unmodifiableList(resultList);
462      }
463      else
464      {
465        validationResults = Collections.emptyList();
466      }
467
468      boolean missingCurrent = false;
469      boolean mustChange = false;
470      Integer secondsRemaining = null;
471      for (int i=1; i < elements.length; i++)
472      {
473        switch (elements[i].getType())
474        {
475          case TYPE_MISSING_CURRENT_PASSWORD:
476            missingCurrent =
477                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
478            break;
479
480          case TYPE_MUST_CHANGE_PW:
481            mustChange =
482                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
483            break;
484
485          case TYPE_SECONDS_UNTIL_EXPIRATION:
486            secondsRemaining =
487                 ASN1Integer.decodeAsInteger(elements[i]).intValue();
488            break;
489
490          default:
491            // We may update this control in the future to provide support for
492            // returning additional password-related information.  If we
493            // encounter an unrecognized element, just ignore it rather than
494            // throwing an exception.
495            break;
496        }
497      }
498
499      missingCurrentPassword = missingCurrent;
500      mustChangePassword     = mustChange;
501      secondsUntilExpiration = secondsRemaining;
502    }
503    catch (final LDAPException le)
504    {
505      Debug.debugException(le);
506      throw le;
507    }
508    catch (final Exception e)
509    {
510      Debug.debugException(e);
511      throw new LDAPException(ResultCode.DECODING_ERROR,
512           ERR_PW_VALIDATION_RESPONSE_ERROR_PARSING_VALUE.get(
513                StaticUtils.getExceptionMessage(e)),
514           e);
515    }
516  }
517
518
519
520  /**
521   * Encodes the provided information to an ASN.1 element suitable for use as
522   * the control value.
523   *
524   * @param  responseType            The response type for this password
525   *                                 validation details response control.  This
526   *                                 must not be {@code null}.
527   * @param  validationResults       A list of the results obtained when
528   *                                 validating the password against the
529   *                                 password quality requirements.  This must
530   *                                 be {@code null} or empty if the
531   *                                 {@code responseType} element has a value
532   *                                 other than {@code VALIDATION_DETAILS}.
533   * @param  missingCurrentPassword  Indicates whether the associated operation
534   *                                 is a self change that failed (or would have
535   *                                 failed if not for additional validation
536   *                                 failures) because the user did not provide
537   *                                 his/her current password as required.
538   * @param  mustChangePassword      Indicates whether the associated operation
539   *                                 is an add or administrative reset that will
540   *                                 require the user to change his/her password
541   *                                 immediately after authenticating before
542   *                                 allowing them to perform any other
543   *                                 operation in the server.
544   * @param  secondsUntilExpiration  The maximum length of time, in seconds,
545   *                                 that the newly-set password will be
546   *                                 considered valid.  This may be {@code null}
547   *                                 if the new password will be considered
548   *                                 valid indefinitely.
549   *
550   * @return  The encoded control value.
551   */
552  @NotNull()
553  private static ASN1OctetString encodeValue(
554       @NotNull final PasswordValidationDetailsResponseType responseType,
555       @Nullable final Collection<PasswordQualityRequirementValidationResult>
556            validationResults,
557       final boolean missingCurrentPassword,
558       final boolean mustChangePassword,
559       @Nullable final Integer secondsUntilExpiration)
560  {
561    final ArrayList<ASN1Element> elements = new ArrayList<>(4);
562
563    switch (responseType)
564    {
565      case VALIDATION_DETAILS:
566        if (validationResults == null)
567        {
568          elements.add(new ASN1Sequence(responseType.getBERType()));
569        }
570        else
571        {
572          final ArrayList<ASN1Element> resultElements =
573               new ArrayList<>(validationResults.size());
574          for (final PasswordQualityRequirementValidationResult r :
575               validationResults)
576          {
577            resultElements.add(r.encode());
578          }
579          elements.add(new ASN1Sequence(responseType.getBERType(),
580               resultElements));
581        }
582        break;
583
584      case NO_PASSWORD_PROVIDED:
585      case MULTIPLE_PASSWORDS_PROVIDED:
586      case NO_VALIDATION_ATTEMPTED:
587        elements.add(new ASN1Null(responseType.getBERType()));
588        break;
589    }
590
591    if (missingCurrentPassword)
592    {
593      elements.add(new ASN1Boolean(TYPE_MISSING_CURRENT_PASSWORD,
594           missingCurrentPassword));
595    }
596
597    if (mustChangePassword)
598    {
599      elements.add(new ASN1Boolean(TYPE_MUST_CHANGE_PW, mustChangePassword));
600    }
601
602    if (secondsUntilExpiration != null)
603    {
604      elements.add(new ASN1Integer(TYPE_SECONDS_UNTIL_EXPIRATION,
605           secondsUntilExpiration));
606    }
607
608    return new ASN1OctetString(new ASN1Sequence(elements).encode());
609  }
610
611
612
613  /**
614   * Retrieves the response type for this password validation details response
615   * control.
616   *
617   * @return  The response type for this password validation details response
618   *          control.
619   */
620  @NotNull()
621  public PasswordValidationDetailsResponseType getResponseType()
622  {
623    return responseType;
624  }
625
626
627
628  /**
629   * Retrieves a list of the results obtained when attempting to validate the
630   * proposed password against the password quality requirements in effect for
631   * the operation.
632   *
633   * @return  A list of the results obtained when attempting to validate the
634   *          proposed password against the password quality requirements in
635   *          effect for the operation, or an empty list if no validation
636   *          results are available.
637   */
638  @NotNull()
639  public List<PasswordQualityRequirementValidationResult> getValidationResults()
640  {
641    return validationResults;
642  }
643
644
645
646  /**
647   * Indicates whether the associated operation is a self password change that
648   * requires the user to provide his/her current password when setting a new
649   * password, but no current password was provided.
650   *
651   * @return  {@code true} if the associated operation is a self password change
652   *          that requires the user to provide his/her current password when
653   *          setting a new password but none was required, or {@code false} if
654   *          the associated operation was not a self change, or if the user's
655   *          current password was provided.
656   */
657  public boolean missingCurrentPassword()
658  {
659    return missingCurrentPassword;
660  }
661
662
663
664  /**
665   * Indicates whether the user will be required to immediately change his/her
666   * password after the associated add or administrative reset is complete.
667   *
668   * @return  {@code true} if the associated operation is an add or
669   *          administrative reset and the user will be required to change
670   *          his/her password before being allowed to perform any other
671   *          operation, or {@code false} if the associated operation was not am
672   *          add or an administrative reset, or if the user will not be
673   *          required to immediately change his/her password.
674   */
675  public boolean mustChangePassword()
676  {
677    return mustChangePassword;
678  }
679
680
681
682  /**
683   * Retrieves the maximum length of time, in seconds, that the newly-set
684   * password will be considered valid.  If {@link #mustChangePassword()}
685   * returns {@code true}, then this value will be the length of time that the
686   * user has to perform a self password change before the account becomes
687   * locked.  If {@code mustChangePassword()} returns {@code false}, then this
688   * value will be the length of time until the password expires.
689   *
690   * @return  The maximum length of time, in seconds, that the newly-set
691   *          password will be considered valid, or {@code null} if the new
692   *          password will be valid indefinitely.
693   */
694  @Nullable()
695  public Integer getSecondsUntilExpiration()
696  {
697    return secondsUntilExpiration;
698  }
699
700
701
702  /**
703   * {@inheritDoc}
704   */
705  @Override()
706  @NotNull()
707  public PasswordValidationDetailsResponseControl decodeControl(
708              @NotNull final String oid, final boolean isCritical,
709              @Nullable final ASN1OctetString value)
710         throws LDAPException
711  {
712    return new PasswordValidationDetailsResponseControl(oid, isCritical, value);
713  }
714
715
716
717  /**
718   * Extracts a password validation details response control from the provided
719   * result.
720   *
721   * @param  result  The result from which to retrieve the password validation
722   *                 details response control.
723   *
724   * @return  The password validation details response control contained in the
725   *          provided result, or {@code null} if the result did not contain a
726   *          password validation details response control.
727   *
728   * @throws  LDAPException  If a problem is encountered while attempting to
729   *                         decode the password validation details response
730   *                         control contained in the provided result.
731   */
732  @Nullable()
733  public static PasswordValidationDetailsResponseControl get(
734                     @NotNull final LDAPResult result)
735         throws LDAPException
736  {
737    final Control c =
738         result.getResponseControl(PASSWORD_VALIDATION_DETAILS_RESPONSE_OID);
739    if (c == null)
740    {
741      return null;
742    }
743
744    if (c instanceof PasswordValidationDetailsResponseControl)
745    {
746      return (PasswordValidationDetailsResponseControl) c;
747    }
748    else
749    {
750      return new PasswordValidationDetailsResponseControl(c.getOID(),
751           c.isCritical(), c.getValue());
752    }
753  }
754
755
756
757  /**
758   * Extracts a password validation details response control from the provided
759   * result.
760   *
761   * @param  exception  The exception that was thrown when trying to process the
762   *                    associated operation.
763   *
764   * @return  The password validation details response control contained in the
765   *          provided result, or {@code null} if the result did not contain a
766   *          password validation details response control.
767   *
768   * @throws  LDAPException  If a problem is encountered while attempting to
769   *                         decode the password validation details response
770   *                         control contained in the provided result.
771   */
772  @NotNull()
773  public static PasswordValidationDetailsResponseControl get(
774                     @NotNull final LDAPException exception)
775         throws LDAPException
776  {
777    return get(exception.toLDAPResult());
778  }
779
780
781
782  /**
783   * {@inheritDoc}
784   */
785  @Override()
786  @NotNull()
787  public String getControlName()
788  {
789    return INFO_CONTROL_NAME_PW_VALIDATION_RESPONSE.get();
790  }
791
792
793
794  /**
795   * Retrieves a representation of this password validation details response
796   * control as a JSON object.  The JSON object uses the following fields:
797   * <UL>
798   *   <LI>
799   *     {@code oid} -- A mandatory string field whose value is the object
800   *     identifier for this control.  For the password validation details
801   *     response control, the OID is "1.3.6.1.4.1.30221.2.5.41".
802   *   </LI>
803   *   <LI>
804   *     {@code control-name} -- An optional string field whose value is a
805   *     human-readable name for this control.  This field is only intended for
806   *     descriptive purposes, and when decoding a control, the {@code oid}
807   *     field should be used to identify the type of control.
808   *   </LI>
809   *   <LI>
810   *     {@code criticality} -- A mandatory Boolean field used to indicate
811   *     whether this control is considered critical.
812   *   </LI>
813   *   <LI>
814   *     {@code value-base64} -- An optional string field whose value is a
815   *     base64-encoded representation of the raw value for this password
816   *     validation details response control.  Exactly one of the
817   *     {@code value-base64} and {@code value-json} fields must be present.
818   *   </LI>
819   *   <LI>
820   *     {@code value-json} -- An optional JSON object field whose value is a
821   *     user-friendly representation of the value for this password validation
822   *     details response control.  Exactly one of the {@code value-base64} and
823   *     {@code value-json} fields must be present, and if the
824   *     {@code value-json} field is used, then it will use the following
825   *     fields:
826   *     <UL>
827   *       <LI>
828   *         {@code response-type} -- A string field that specifies the result
829   *         of the password validation processing for the attempt.  The value
830   *         will be one of "{@code validation-performed}",
831   *         "{@code no-password-provided}",
832   *         "{@code multiple-passwords-provided}", or
833   *         "{@code no-validation-attempted}".
834   *       </LI>
835   *       <LI>
836   *         {@code validation-details} -- An optional array field whose values
837   *         are JSON objects with information about the types of validation
838   *         performed for the new password.  The fields that may be used in
839   *         these JSON objects include:
840   *         <UL>
841   *           <LI>
842   *             {@code password-quality-requirement} -- A JSON object whose
843   *             value provides information about a password quality requirement
844   *             that was evaluated.  The fields used in these JSON objects
845   *             include:
846   *             <UL>
847   *               <LI>
848   *                 {@code description} -- A string field whose value is a
849   *                 user-friendly description of the password quality
850   *                 requirement.
851   *               </LI>
852   *               <LI>
853   *                 {@code client-side-validation-type} -- An optional string
854   *                 field whose value is an identifier that the client can use
855   *                 to programmatically determine the type of requirement.
856   *               </LI>
857   *               <LI>
858   *                 {@code client-side-validation-properties} -- An optional
859   *                 array field whose values are JSON objects with additional
860   *                 properties that the client can use in the course of
861   *                 programmatically determining whether a proposed password is
862   *                 likely to satisfy the requirement.  Each of these JSON
863   *                 objects will include a {@code name} field whose value is a
864   *                 string that specifies the property name, and a
865   *                 {@code value} field whose value is a string that specifies
866   *                 the property value.
867   *               </LI>
868   *             </UL>
869   *           </LI>
870   *           <LI>
871   *             {@code requirement-satisfied} -- A Boolean field that indicates
872   *             whether the provided new password satisfies the password
873   *             quality requirement.
874   *           </LI>
875   *           <LI>
876   *             {@code additional-information} -- An optional string field
877   *             whose value provides additional information about the
878   *             validation for the associated requirement.
879   *           </LI>
880   *         </UL>
881   *       </LI>
882   *       <LI>
883   *         {@code missing-current-password} -- A Boolean field that indicates
884   *         whether the server requires the user's current password to be
885   *         provided when choosing a new password, but that password was not
886   *         provided.
887   *       </LI>
888   *       <LI>
889   *         {@code must-change-password} -- A Boolean field that indicates
890   *         whether the user will be required to choose a new password before
891   *         they will be allowed to request any other operations.
892   *       </LI>
893   *       <LI>
894   *         {@code seconds-until-expiration} -- An optional integer field whose
895   *         value is the number of seconds until the new password will
896   *         expire.
897   *       </LI>
898   *     </UL>
899   *   </LI>
900   * </UL>
901   *
902   * @return  A JSON object that contains a representation of this control.
903   */
904  @Override()
905  @NotNull()
906  public JSONObject toJSONControl()
907  {
908    final Map<String,JSONValue> valueFields = new LinkedHashMap<>();
909
910    switch (responseType)
911    {
912      case VALIDATION_DETAILS:
913        valueFields.put(JSON_FIELD_RESPONSE_TYPE,
914             new JSONString(JSON_RESPONSE_TYPE_VALIDATION_PERFORMED));
915
916        final List<JSONValue> validationDetailsValues =
917             new ArrayList<>(validationResults.size());
918        for (final PasswordQualityRequirementValidationResult result :
919             validationResults)
920        {
921          validationDetailsValues.add(encodeValidationResultJSON(result));
922        }
923        valueFields.put(JSON_FIELD_VALIDATION_DETAILS,
924        new JSONArray(validationDetailsValues));
925        break;
926
927      case NO_PASSWORD_PROVIDED:
928        valueFields.put(JSON_FIELD_RESPONSE_TYPE,
929             new JSONString(JSON_RESPONSE_TYPE_NO_PASSWORD_PROVIDED));
930        break;
931
932      case MULTIPLE_PASSWORDS_PROVIDED:
933        valueFields.put(JSON_FIELD_RESPONSE_TYPE,
934             new JSONString(JSON_RESPONSE_TYPE_MULTIPLE_PASSWORDS_PROVIDED));
935        break;
936
937      case NO_VALIDATION_ATTEMPTED:
938        valueFields.put(JSON_FIELD_RESPONSE_TYPE,
939             new JSONString(JSON_RESPONSE_TYPE_NO_VALIDATION_ATTEMPTED));
940        break;
941    }
942
943    valueFields.put(JSON_FIELD_MISSING_CURRENT_PASSWORD,
944         new JSONBoolean(missingCurrentPassword));
945
946    valueFields.put(JSON_FIELD_MUST_CHANGE_PASSWORD,
947         new JSONBoolean(mustChangePassword));
948
949    if (secondsUntilExpiration != null)
950    {
951      valueFields.put(JSON_FIELD_SECONDS_UNTIL_EXPIRATION,
952           new JSONNumber(secondsUntilExpiration));
953    }
954
955    return new JSONObject(
956         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
957              PASSWORD_VALIDATION_DETAILS_RESPONSE_OID),
958         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
959              INFO_CONTROL_NAME_PW_VALIDATION_RESPONSE.get()),
960         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
961              isCritical()),
962         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
963              new JSONObject(valueFields)));
964  }
965
966
967
968  /**
969   * Encodes the provided password quality requirement validation result to a
970   * JSON object.
971   *
972   * @param  result  The result to be encoded.  It must not be {@code null}.
973   *
974   * @return  A JSON object containing the encoded result.
975   */
976  @NotNull()
977  private static JSONObject encodeValidationResultJSON(
978               @NotNull final PasswordQualityRequirementValidationResult result)
979  {
980    final PasswordQualityRequirement requirement =
981         result.getPasswordRequirement();
982    final Map<String,JSONValue> requirementFields = new LinkedHashMap<>();
983
984    requirementFields.put(JSON_FIELD_DESCRIPTION,
985         new JSONString(requirement.getDescription()));
986
987    final String clientSideValidationType =
988         requirement.getClientSideValidationType();
989    if (clientSideValidationType != null)
990    {
991      requirementFields.put(JSON_FIELD_CLIENT_SIDE_VALIDATION_TYPE,
992           new JSONString(clientSideValidationType));
993    }
994
995    final Map<String,String> clientSideValidationProperties =
996         requirement.getClientSideValidationProperties();
997    if (! clientSideValidationProperties.isEmpty())
998    {
999      final List<JSONValue> propertyValues =
1000           new ArrayList<>(clientSideValidationProperties.size());
1001      for (final Map.Entry<String,String> e :
1002           clientSideValidationProperties.entrySet())
1003      {
1004        propertyValues.add(new JSONObject(
1005             new JSONField(JSON_FIELD_PROPERTY_NAME, e.getKey()),
1006             new JSONField(JSON_FIELD_PROPERTY_VALUE, e.getValue())));
1007      }
1008
1009      requirementFields.put(JSON_FIELD_CLIENT_SIDE_VALIDATION_PROPERTIES,
1010           new JSONArray(propertyValues));
1011    }
1012
1013
1014    final Map<String,JSONValue> detailsFields = new LinkedHashMap<>();
1015
1016    detailsFields.put(JSON_FIELD_PASSWORD_QUALITY_REQUIREMENT,
1017         new JSONObject(requirementFields));
1018
1019    detailsFields.put(JSON_FIELD_REQUIREMENT_SATISFIED,
1020         new JSONBoolean(result.requirementSatisfied()));
1021
1022    final String additionalInformation = result.getAdditionalInfo();
1023    if (additionalInformation != null)
1024    {
1025      detailsFields.put(JSON_FIELD_ADDITIONAL_INFORMATION,
1026           new JSONString(additionalInformation));
1027    }
1028
1029    return new JSONObject(detailsFields);
1030  }
1031
1032
1033
1034  /**
1035   * Attempts to decode the provided object as a JSON representation of a
1036   * password validation details response control.
1037   *
1038   * @param  controlObject  The JSON object to be decoded.  It must not be
1039   *                        {@code null}.
1040   * @param  strict         Indicates whether to use strict mode when decoding
1041   *                        the provided JSON object.  If this is {@code true},
1042   *                        then this method will throw an exception if the
1043   *                        provided JSON object contains any unrecognized
1044   *                        fields.  If this is {@code false}, then unrecognized
1045   *                        fields will be ignored.
1046   *
1047   * @return  The password validation details response control that was decoded
1048   *          from the provided JSON object.
1049   *
1050   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
1051   *                         valid password validation details response control.
1052   */
1053  @NotNull()
1054  public static PasswordValidationDetailsResponseControl decodeJSONControl(
1055              @NotNull final JSONObject controlObject,
1056              final boolean strict)
1057         throws LDAPException
1058  {
1059    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
1060         controlObject, strict, true, true);
1061
1062    final ASN1OctetString rawValue = jsonControl.getRawValue();
1063    if (rawValue != null)
1064    {
1065      return new PasswordValidationDetailsResponseControl(jsonControl.getOID(),
1066           jsonControl.getCriticality(), rawValue);
1067    }
1068
1069
1070    final JSONObject valueObject = jsonControl.getValueObject();
1071
1072    final String responseTypeStr =
1073         valueObject.getFieldAsString(JSON_FIELD_RESPONSE_TYPE);
1074    if (responseTypeStr == null)
1075    {
1076      throw new LDAPException(ResultCode.DECODING_ERROR,
1077           ERR_PW_VALIDATION_RESPONSE_JSON_VALUE_MISSING_FIELD.get(
1078                controlObject.toSingleLineString(), JSON_FIELD_RESPONSE_TYPE));
1079    }
1080
1081    final PasswordValidationDetailsResponseType responseType;
1082    switch (responseTypeStr)
1083    {
1084      case JSON_RESPONSE_TYPE_VALIDATION_PERFORMED:
1085        responseType = PasswordValidationDetailsResponseType.VALIDATION_DETAILS;
1086        break;
1087      case JSON_RESPONSE_TYPE_NO_PASSWORD_PROVIDED:
1088        responseType =
1089             PasswordValidationDetailsResponseType.NO_PASSWORD_PROVIDED;
1090        break;
1091      case JSON_RESPONSE_TYPE_MULTIPLE_PASSWORDS_PROVIDED:
1092        responseType =
1093             PasswordValidationDetailsResponseType.MULTIPLE_PASSWORDS_PROVIDED;
1094        break;
1095      case JSON_RESPONSE_TYPE_NO_VALIDATION_ATTEMPTED:
1096        responseType =
1097             PasswordValidationDetailsResponseType.NO_VALIDATION_ATTEMPTED;
1098        break;
1099      default:
1100        throw new LDAPException(ResultCode.DECODING_ERROR,
1101             ERR_PW_VALIDATION_RESPONSE_JSON_UNKNOWN_RESPONSE_TYPE.get(
1102                  controlObject.toSingleLineString(), JSON_FIELD_RESPONSE_TYPE,
1103                  responseTypeStr));
1104    }
1105
1106
1107    final List<PasswordQualityRequirementValidationResult> validationResults;
1108    final List<JSONValue> validationDetailsValues =
1109         valueObject.getFieldAsArray(JSON_FIELD_VALIDATION_DETAILS);
1110    if (validationDetailsValues == null)
1111    {
1112      validationResults = Collections.emptyList();
1113    }
1114    else
1115    {
1116      validationResults = new ArrayList<>(validationDetailsValues.size());
1117      for (final JSONValue v : validationDetailsValues)
1118      {
1119        validationResults.add(decodeValidationResultJSON(controlObject, v,
1120             strict));
1121      }
1122    }
1123
1124
1125    final Boolean missingCurrentPassword =
1126         valueObject.getFieldAsBoolean(JSON_FIELD_MISSING_CURRENT_PASSWORD);
1127    if (missingCurrentPassword == null)
1128    {
1129      throw new LDAPException(ResultCode.DECODING_ERROR,
1130           ERR_PW_VALIDATION_RESPONSE_JSON_VALUE_MISSING_FIELD.get(
1131                controlObject.toSingleLineString(),
1132                JSON_FIELD_MISSING_CURRENT_PASSWORD));
1133    }
1134
1135
1136    final Boolean mustChangePassword =
1137         valueObject.getFieldAsBoolean(JSON_FIELD_MUST_CHANGE_PASSWORD);
1138    if (mustChangePassword == null)
1139    {
1140      throw new LDAPException(ResultCode.DECODING_ERROR,
1141           ERR_PW_VALIDATION_RESPONSE_JSON_VALUE_MISSING_FIELD.get(
1142                controlObject.toSingleLineString(),
1143                JSON_FIELD_MUST_CHANGE_PASSWORD));
1144    }
1145
1146
1147    final Integer secondsUntilExpiration =
1148         valueObject.getFieldAsInteger(JSON_FIELD_SECONDS_UNTIL_EXPIRATION);
1149
1150
1151    if (strict)
1152    {
1153      final List<String> unrecognizedFields =
1154           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
1155                valueObject, JSON_FIELD_RESPONSE_TYPE,
1156                JSON_FIELD_VALIDATION_DETAILS,
1157                JSON_FIELD_MISSING_CURRENT_PASSWORD,
1158                JSON_FIELD_MUST_CHANGE_PASSWORD,
1159                JSON_FIELD_SECONDS_UNTIL_EXPIRATION);
1160      if (! unrecognizedFields.isEmpty())
1161      {
1162        throw new LDAPException(ResultCode.DECODING_ERROR,
1163             ERR_PW_VALIDATION_RESPONSE_JSON_UNRECOGNIZED_FIELD.get(
1164                  controlObject.toSingleLineString(),
1165                  unrecognizedFields.get(0)));
1166      }
1167    }
1168
1169
1170    return new PasswordValidationDetailsResponseControl(responseType,
1171         validationResults, missingCurrentPassword, mustChangePassword,
1172         secondsUntilExpiration);
1173  }
1174
1175
1176
1177  /**
1178   * Decodes the provided JSON value as a password quality requirement
1179   * validation result.
1180   *
1181   * @param  controlObject  A JSON object containing an encoded representation
1182   *                        of the control being decoded.  It must not be
1183   *                        {@code null}.
1184   * @param  resultValue    The JSON value to be decoded as a password quality
1185   *                        requirement validation result.  It must not be
1186   *                        {@code null}.
1187   * @param  strict         Indicates whether to use strict mode when decoding
1188   *                        the provided JSON object.  If this is {@code true},
1189   *                        then this method will throw an exception if the
1190   *                        provided JSON value is an object that contains any
1191   *                        unrecognized fields.  If this is {@code false}, then
1192   *                        unrecognized fields will be ignored.
1193   *
1194   * @return  The password quality requirement validation result that was
1195   *          decoded.
1196   *
1197   * @throws  LDAPException  If the provided JSON value cannot be decoded as a
1198   *                         password quality requirement validation result.
1199   */
1200  @NotNull()
1201  private static PasswordQualityRequirementValidationResult
1202               decodeValidationResultJSON(
1203                    @NotNull final JSONObject controlObject,
1204                    @NotNull final JSONValue resultValue,
1205                    final boolean strict)
1206          throws LDAPException
1207  {
1208    if (! (resultValue instanceof JSONObject))
1209    {
1210      throw new LDAPException(ResultCode.DECODING_ERROR,
1211           ERR_PW_VALIDATION_RESPONSE_JSON_RESULT_NOT_OBJECT.get(
1212                controlObject.toSingleLineString(),
1213                JSON_FIELD_VALIDATION_DETAILS));
1214    }
1215
1216    final JSONObject resultObject = (JSONObject) resultValue;
1217
1218    final JSONObject requirementObject =
1219         resultObject.getFieldAsObject(JSON_FIELD_PASSWORD_QUALITY_REQUIREMENT);
1220    if (requirementObject == null)
1221    {
1222      throw new LDAPException(ResultCode.DECODING_ERROR,
1223           ERR_PW_VALIDATION_RESPONSE_JSON_RESULT_MISSING_FIELD.get(
1224                controlObject.toSingleLineString(),
1225                JSON_FIELD_VALIDATION_DETAILS,
1226                JSON_FIELD_PASSWORD_QUALITY_REQUIREMENT));
1227    }
1228
1229    final String requirementDescription =
1230         requirementObject.getFieldAsString(JSON_FIELD_DESCRIPTION);
1231    if (requirementDescription == null)
1232    {
1233      throw new LDAPException(ResultCode.DECODING_ERROR,
1234           ERR_PW_VALIDATION_RESPONSE_JSON_REQUIREMENT_MISSING_FIELD.get(
1235                controlObject.toSingleLineString(),
1236                JSON_FIELD_VALIDATION_DETAILS,
1237                JSON_FIELD_PASSWORD_QUALITY_REQUIREMENT,
1238                JSON_FIELD_DESCRIPTION));
1239    }
1240
1241    final String clientSideValidationType = requirementObject.getFieldAsString(
1242         JSON_FIELD_CLIENT_SIDE_VALIDATION_TYPE);
1243
1244    final Map<String,String> clientSideValidationProperties =
1245         new LinkedHashMap<>();
1246    final List<JSONValue> clientSideValidationPropertyValues =
1247         requirementObject.getFieldAsArray(
1248              JSON_FIELD_CLIENT_SIDE_VALIDATION_PROPERTIES);
1249    if (clientSideValidationPropertyValues != null)
1250    {
1251      for (final JSONValue v : clientSideValidationPropertyValues)
1252      {
1253        if (! (v instanceof JSONObject))
1254        {
1255          throw new LDAPException(ResultCode.DECODING_ERROR,
1256               ERR_PW_VALIDATION_RESPONSE_JSON_REQUIREMENT_PROP_NOT_OBJECT.get(
1257                controlObject.toSingleLineString(),
1258                JSON_FIELD_VALIDATION_DETAILS,
1259                JSON_FIELD_PASSWORD_QUALITY_REQUIREMENT,
1260                JSON_FIELD_CLIENT_SIDE_VALIDATION_PROPERTIES));
1261        }
1262
1263        final JSONObject propObject = (JSONObject) v;
1264        final String name =
1265             propObject.getFieldAsString(JSON_FIELD_PROPERTY_NAME);
1266        if (name == null)
1267        {
1268          throw new LDAPException(ResultCode.DECODING_ERROR,
1269               ERR_PW_VALIDATION_RESPONSE_JSON_REQUIREMENT_PROP_MISSING_FIELD.
1270                    get(controlObject.toSingleLineString(),
1271                         JSON_FIELD_VALIDATION_DETAILS,
1272                         JSON_FIELD_PASSWORD_QUALITY_REQUIREMENT,
1273                         JSON_FIELD_CLIENT_SIDE_VALIDATION_PROPERTIES,
1274                         JSON_FIELD_PROPERTY_NAME));
1275        }
1276
1277        final String value =
1278             propObject.getFieldAsString(JSON_FIELD_PROPERTY_VALUE);
1279        if (value == null)
1280        {
1281          throw new LDAPException(ResultCode.DECODING_ERROR,
1282               ERR_PW_VALIDATION_RESPONSE_JSON_REQUIREMENT_PROP_MISSING_FIELD.
1283                    get(controlObject.toSingleLineString(),
1284                         JSON_FIELD_VALIDATION_DETAILS,
1285                         JSON_FIELD_PASSWORD_QUALITY_REQUIREMENT,
1286                         JSON_FIELD_CLIENT_SIDE_VALIDATION_PROPERTIES,
1287                         JSON_FIELD_PROPERTY_VALUE));
1288        }
1289
1290        if (strict)
1291        {
1292          final List<String> unrecognizedFields =
1293               JSONControlDecodeHelper.getControlObjectUnexpectedFields(
1294                    propObject, JSON_FIELD_PROPERTY_NAME,
1295                    JSON_FIELD_PROPERTY_VALUE);
1296          if (! unrecognizedFields.isEmpty())
1297          {
1298            throw new LDAPException(ResultCode.DECODING_ERROR,
1299                 ERR_PW_VALIDATION_RESPONSE_JSON_UNRECOGNIZED_PROP_FIELD.get(
1300                      controlObject.toSingleLineString(),
1301                      JSON_FIELD_VALIDATION_DETAILS,
1302                      JSON_FIELD_PASSWORD_QUALITY_REQUIREMENT,
1303                      JSON_FIELD_CLIENT_SIDE_VALIDATION_PROPERTIES,
1304                      unrecognizedFields.get(0)));
1305          }
1306        }
1307
1308        clientSideValidationProperties.put(name, value);
1309      }
1310    }
1311
1312
1313    final Boolean requirementSatisfied =
1314         resultObject.getFieldAsBoolean(JSON_FIELD_REQUIREMENT_SATISFIED);
1315    if (requirementSatisfied == null)
1316    {
1317      throw new LDAPException(ResultCode.DECODING_ERROR,
1318           ERR_PW_VALIDATION_RESPONSE_JSON_RESULT_MISSING_FIELD.get(
1319                controlObject.toSingleLineString(),
1320                JSON_FIELD_VALIDATION_DETAILS,
1321                JSON_FIELD_PASSWORD_QUALITY_REQUIREMENT,
1322                JSON_FIELD_REQUIREMENT_SATISFIED));
1323    }
1324
1325
1326    if (strict)
1327    {
1328      final List<String> unrecognizedFields =
1329           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
1330                resultObject, JSON_FIELD_PASSWORD_QUALITY_REQUIREMENT,
1331                JSON_FIELD_REQUIREMENT_SATISFIED,
1332                JSON_FIELD_ADDITIONAL_INFORMATION);
1333      if (! unrecognizedFields.isEmpty())
1334      {
1335        throw new LDAPException(ResultCode.DECODING_ERROR,
1336             ERR_PW_VALIDATION_RESPONSE_JSON_RESULT_UNRECOGNIZED_FIELD.get(
1337                  controlObject.toSingleLineString(),
1338                  JSON_FIELD_VALIDATION_DETAILS,
1339                  JSON_FIELD_PASSWORD_QUALITY_REQUIREMENT,
1340                  unrecognizedFields.get(0)));
1341      }
1342    }
1343
1344
1345    final String additionalInformation =
1346         resultObject.getFieldAsString(JSON_FIELD_ADDITIONAL_INFORMATION);
1347
1348    final PasswordQualityRequirement requirement =
1349         new PasswordQualityRequirement(requirementDescription,
1350              clientSideValidationType, clientSideValidationProperties);
1351    return new PasswordQualityRequirementValidationResult(requirement,
1352         requirementSatisfied, additionalInformation);
1353  }
1354
1355
1356
1357  /**
1358   * {@inheritDoc}
1359   */
1360  @Override()
1361  public void toString(@NotNull final StringBuilder buffer)
1362  {
1363    buffer.append("PasswordValidationDetailsResponseControl(responseType='");
1364    buffer.append(responseType.name());
1365    buffer.append('\'');
1366
1367    if (responseType ==
1368        PasswordValidationDetailsResponseType.VALIDATION_DETAILS)
1369    {
1370      buffer.append(", validationDetails={");
1371
1372      final Iterator<PasswordQualityRequirementValidationResult> iterator =
1373           validationResults.iterator();
1374      while (iterator.hasNext())
1375      {
1376        iterator.next().toString(buffer);
1377        if (iterator.hasNext())
1378        {
1379          buffer.append(',');
1380        }
1381      }
1382
1383      buffer.append('}');
1384    }
1385
1386    buffer.append(", missingCurrentPassword=");
1387    buffer.append(missingCurrentPassword);
1388    buffer.append(", mustChangePassword=");
1389    buffer.append(mustChangePassword);
1390
1391    if (secondsUntilExpiration != null)
1392    {
1393      buffer.append(", secondsUntilExpiration=");
1394      buffer.append(secondsUntilExpiration);
1395    }
1396
1397    buffer.append("})");
1398  }
1399}