001/*
002 * Copyright 2015-2023 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2015-2023 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-2023 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.Collections;
042import java.util.Iterator;
043import java.util.LinkedHashMap;
044import java.util.List;
045import java.util.Map;
046
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.BindResult;
052import com.unboundid.ldap.sdk.Control;
053import com.unboundid.ldap.sdk.DecodeableControl;
054import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
055import com.unboundid.ldap.sdk.LDAPException;
056import com.unboundid.ldap.sdk.ResultCode;
057import com.unboundid.ldap.sdk.unboundidds.extensions.
058            PasswordPolicyStateAccountUsabilityError;
059import com.unboundid.ldap.sdk.unboundidds.extensions.
060            PasswordPolicyStateAccountUsabilityNotice;
061import com.unboundid.ldap.sdk.unboundidds.extensions.
062            PasswordPolicyStateAccountUsabilityWarning;
063import com.unboundid.util.Debug;
064import com.unboundid.util.NotMutable;
065import com.unboundid.util.NotNull;
066import com.unboundid.util.Nullable;
067import com.unboundid.util.StaticUtils;
068import com.unboundid.util.ThreadSafety;
069import com.unboundid.util.ThreadSafetyLevel;
070import com.unboundid.util.json.JSONArray;
071import com.unboundid.util.json.JSONField;
072import com.unboundid.util.json.JSONNumber;
073import com.unboundid.util.json.JSONObject;
074import com.unboundid.util.json.JSONString;
075import com.unboundid.util.json.JSONValue;
076
077import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
078
079
080
081/**
082 * This class provides an implementation of a response control that can be
083 * included in a bind response with information about any password policy state
084 * notices, warnings, and/or errors for the user.
085 * <BR>
086 * <BLOCKQUOTE>
087 *   <B>NOTE:</B>  This class, and other classes within the
088 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
089 *   supported for use against Ping Identity, UnboundID, and
090 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
091 *   for proprietary functionality or for external specifications that are not
092 *   considered stable or mature enough to be guaranteed to work in an
093 *   interoperable way with other types of LDAP servers.
094 * </BLOCKQUOTE>
095 * <BR>
096 * This control has an OID of 1.3.6.1.4.1.30221.2.5.47, a criticality of
097 * {@code false}, and a value with the following encoding:
098 * <PRE>
099 *   GetPasswordPolicyStateIssuesResponse ::= SEQUENCE {
100 *        notices               [0] SEQUENCE OF SEQUENCE {
101 *             type        INTEGER,
102 *             name        OCTET STRING,
103 *             message     OCTET STRING OPTIONAL } OPTIONAL,
104 *        warnings              [1] SEQUENCE OF SEQUENCE {
105 *             type        INTEGER,
106 *             name        OCTET STRING,
107 *             message     OCTET STRING OPTIONAL } OPTIONAL,
108 *        errors                [2] SEQUENCE OF SEQUENCE {
109 *             type        INTEGER,
110 *             name        OCTET STRING,
111 *             message     OCTET STRING OPTIONAL } OPTIONAL,
112 *        authFailureReason     [3] SEQUENCE {
113 *             type        INTEGER,
114 *             name        OCTET STRING,
115 *             message     OCTET STRING OPTIONAL } OPTIONAL,
116 *        ... }
117 * </PRE>
118 */
119@NotMutable()
120@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
121public final class GetPasswordPolicyStateIssuesResponseControl
122       extends Control
123       implements DecodeableControl
124{
125  /**
126   * The OID (1.3.6.1.4.1.30221.2.5.47) for the get password policy state issues
127   * response control.
128   */
129  @NotNull public static final String
130       GET_PASSWORD_POLICY_STATE_ISSUES_RESPONSE_OID =
131            "1.3.6.1.4.1.30221.2.5.47";
132
133
134
135  /**
136   * The BER type to use for the value sequence element that holds the set of
137   * account usability notices.
138   */
139  private static final byte TYPE_NOTICES = (byte) 0xA0;
140
141
142
143  /**
144   * The BER type to use for the value sequence element that holds the set of
145   * account usability warnings.
146   */
147  private static final byte TYPE_WARNINGS = (byte) 0xA1;
148
149
150
151  /**
152   * The BER type to use for the value sequence element that holds the set of
153   * account usability errors.
154   */
155  private static final byte TYPE_ERRORS = (byte) 0xA2;
156
157
158
159  /**
160   * The BER type to use for the value sequence element that holds the
161   * authentication failure reason.
162   */
163  private static final byte TYPE_AUTH_FAILURE_REASON = (byte) 0xA3;
164
165
166
167  /**
168   * The name of the field used to represent the authentication failure reason
169   * in the JSON representation of this control.
170   */
171  @NotNull private static final String JSON_FIELD_AUTH_FAILURE_REASON =
172       "authentication-failure-reason";
173
174
175
176  /**
177   * The name of the field used to represent the set of password policy state
178   * errors in the JSON representation of this control.
179   */
180  @NotNull private static final String JSON_FIELD_ERRORS = "errors";
181
182
183
184  /**
185   * The name of the field used to represent the ID of a password policy state
186   * issue or auth failure reason in the JSON representation of this control.
187   */
188  @NotNull private static final String JSON_FIELD_ID = "id";
189
190
191
192  /**
193   * The name of the field used to represent the message for a password policy
194   * state issue or auth failure reason in the JSON representation of this
195   * control.
196   */
197  @NotNull private static final String JSON_FIELD_MESSAGE = "message";
198
199
200
201  /**
202   * The name of the field used to represent the name of a password policy state
203   * issue or auth failure reason in the JSON representation of this control.
204   */
205  @NotNull private static final String JSON_FIELD_NAME = "name";
206
207
208
209  /**
210   * The name of the field used to represent the set of password policy state
211   * notices in the JSON representation of this control.
212   */
213  @NotNull private static final String JSON_FIELD_NOTICES = "notices";
214
215
216
217  /**
218   * The name of the field used to represent the set of password policy state
219   * warnings in the JSON representation of this control.
220   */
221  @NotNull private static final String JSON_FIELD_WARNINGS = "warnings";
222
223
224
225  /**
226   * The serial version UID for this serializable class.
227   */
228  private static final long serialVersionUID = 7509027658735069270L;
229
230
231
232  // The authentication failure reason for the bind operation.
233  @Nullable private final AuthenticationFailureReason authFailureReason;
234
235  // The set of account usability errors.
236  @NotNull private final List<PasswordPolicyStateAccountUsabilityError> errors;
237
238  // The set of account usability notices.
239  @NotNull private final List<PasswordPolicyStateAccountUsabilityNotice>
240       notices;
241
242  // The set of account usability warnings.
243  @NotNull private final List<PasswordPolicyStateAccountUsabilityWarning>
244       warnings;
245
246
247
248  /**
249   * Creates a new empty control instance that is intended to be used only for
250   * decoding controls via the {@code DecodeableControl} interface.
251   */
252  GetPasswordPolicyStateIssuesResponseControl()
253  {
254    authFailureReason = null;
255    notices = Collections.emptyList();
256    warnings = Collections.emptyList();
257    errors = Collections.emptyList();
258  }
259
260
261
262  /**
263   * Creates a new instance of this control with the provided information.
264   *
265   * @param  notices   The set of password policy state usability notices to
266   *                   include.  It may be {@code null} or empty if there are
267   *                   no notices.
268   * @param  warnings  The set of password policy state usability warnings to
269   *                   include.  It may be {@code null} or empty if there are
270   *                   no warnings.
271   * @param  errors    The set of password policy state usability errors to
272   *                   include.  It may be {@code null} or empty if there are
273   *                   no errors.
274   */
275  public GetPasswordPolicyStateIssuesResponseControl(
276       @Nullable final List<PasswordPolicyStateAccountUsabilityNotice> notices,
277       @Nullable final List<PasswordPolicyStateAccountUsabilityWarning>
278            warnings,
279       @Nullable final List<PasswordPolicyStateAccountUsabilityError> errors)
280  {
281    this(notices, warnings, errors, null);
282  }
283
284
285
286  /**
287   * Creates a new instance of this control with the provided information.
288   *
289   * @param  notices            The set of password policy state usability
290   *                            notices to include.  It may be {@code null} or
291   *                            empty if there are no notices.
292   * @param  warnings           The set of password policy state usability
293   *                            warnings to include.  It may be {@code null} or
294   *                            empty if there are no warnings.
295   * @param  errors             The set of password policy state usability
296   *                            errors to include.  It may be {@code null} or
297   *                            empty if there are no errors.
298   * @param  authFailureReason  The authentication failure reason for the bind
299   *                            operation.  It may be {@code null} if there is
300   *                            no authentication failure reason.
301   */
302  public GetPasswordPolicyStateIssuesResponseControl(
303       @Nullable final List<PasswordPolicyStateAccountUsabilityNotice> notices,
304       @Nullable final List<PasswordPolicyStateAccountUsabilityWarning>
305            warnings,
306       @Nullable final List<PasswordPolicyStateAccountUsabilityError> errors,
307       @Nullable final AuthenticationFailureReason authFailureReason)
308  {
309    super(GET_PASSWORD_POLICY_STATE_ISSUES_RESPONSE_OID, false,
310         encodeValue(notices, warnings, errors, authFailureReason));
311
312    this.authFailureReason = authFailureReason;
313
314    if (notices == null)
315    {
316      this.notices = Collections.emptyList();
317    }
318    else
319    {
320      this.notices = Collections.unmodifiableList(new ArrayList<>(notices));
321    }
322
323    if (warnings == null)
324    {
325      this.warnings = Collections.emptyList();
326    }
327    else
328    {
329      this.warnings = Collections.unmodifiableList(new ArrayList<>(warnings));
330    }
331
332    if (errors == null)
333    {
334      this.errors = Collections.emptyList();
335    }
336    else
337    {
338      this.errors = Collections.unmodifiableList(new ArrayList<>(errors));
339    }
340  }
341
342
343
344  /**
345   * Creates a new instance of this control that is decoded from the provided
346   * generic control.
347   *
348   * @param  oid         The OID for the control.
349   * @param  isCritical  Indicates whether this control should be marked
350   *                     critical.
351   * @param  value       The encoded value for the control.
352   *
353   * @throws LDAPException  If a problem is encountered while attempting to
354   *                         decode the provided control as a get password
355   *                         policy state issues response control.
356   */
357  public GetPasswordPolicyStateIssuesResponseControl(@NotNull final String oid,
358              final boolean isCritical, @Nullable final ASN1OctetString value)
359         throws LDAPException
360  {
361    super(oid, isCritical, value);
362
363    if (value == null)
364    {
365      throw new LDAPException(ResultCode.DECODING_ERROR,
366           ERR_GET_PWP_STATE_ISSUES_RESPONSE_NO_VALUE.get());
367    }
368
369    AuthenticationFailureReason afr = null;
370    List<PasswordPolicyStateAccountUsabilityNotice> nList =
371         Collections.emptyList();
372    List<PasswordPolicyStateAccountUsabilityWarning> wList =
373         Collections.emptyList();
374    List<PasswordPolicyStateAccountUsabilityError> eList =
375         Collections.emptyList();
376
377    try
378    {
379      for (final ASN1Element e :
380           ASN1Sequence.decodeAsSequence(value.getValue()).elements())
381      {
382        switch (e.getType())
383        {
384          case TYPE_NOTICES:
385            nList = new ArrayList<>(10);
386            for (final ASN1Element ne :
387                 ASN1Sequence.decodeAsSequence(e).elements())
388            {
389              final ASN1Element[] noticeElements =
390                   ASN1Sequence.decodeAsSequence(ne).elements();
391              final int type = ASN1Integer.decodeAsInteger(
392                   noticeElements[0]).intValue();
393              final String name = ASN1OctetString.decodeAsOctetString(
394                   noticeElements[1]).stringValue();
395
396              final String message;
397              if (noticeElements.length == 3)
398              {
399                message = ASN1OctetString.decodeAsOctetString(
400                     noticeElements[2]).stringValue();
401              }
402              else
403              {
404                message = null;
405              }
406
407              nList.add(new PasswordPolicyStateAccountUsabilityNotice(type,
408                   name, message));
409            }
410            nList = Collections.unmodifiableList(nList);
411            break;
412
413          case TYPE_WARNINGS:
414            wList =
415                 new ArrayList<>(10);
416            for (final ASN1Element we :
417                 ASN1Sequence.decodeAsSequence(e).elements())
418            {
419              final ASN1Element[] warningElements =
420                   ASN1Sequence.decodeAsSequence(we).elements();
421              final int type = ASN1Integer.decodeAsInteger(
422                   warningElements[0]).intValue();
423              final String name = ASN1OctetString.decodeAsOctetString(
424                   warningElements[1]).stringValue();
425
426              final String message;
427              if (warningElements.length == 3)
428              {
429                message = ASN1OctetString.decodeAsOctetString(
430                     warningElements[2]).stringValue();
431              }
432              else
433              {
434                message = null;
435              }
436
437              wList.add(new PasswordPolicyStateAccountUsabilityWarning(type,
438                   name, message));
439            }
440            wList = Collections.unmodifiableList(wList);
441            break;
442
443          case TYPE_ERRORS:
444            eList = new ArrayList<>(10);
445            for (final ASN1Element ee :
446                 ASN1Sequence.decodeAsSequence(e).elements())
447            {
448              final ASN1Element[] errorElements =
449                   ASN1Sequence.decodeAsSequence(ee).elements();
450              final int type = ASN1Integer.decodeAsInteger(
451                   errorElements[0]).intValue();
452              final String name = ASN1OctetString.decodeAsOctetString(
453                   errorElements[1]).stringValue();
454
455              final String message;
456              if (errorElements.length == 3)
457              {
458                message = ASN1OctetString.decodeAsOctetString(
459                     errorElements[2]).stringValue();
460              }
461              else
462              {
463                message = null;
464              }
465
466              eList.add(new PasswordPolicyStateAccountUsabilityError(type,
467                   name, message));
468            }
469            eList = Collections.unmodifiableList(eList);
470            break;
471
472          case TYPE_AUTH_FAILURE_REASON:
473            final ASN1Element[] afrElements =
474                 ASN1Sequence.decodeAsSequence(e).elements();
475            final int afrType =
476                 ASN1Integer.decodeAsInteger(afrElements[0]).intValue();
477            final String afrName = ASN1OctetString.decodeAsOctetString(
478                 afrElements[1]).stringValue();
479
480            final String afrMessage;
481            if (afrElements.length == 3)
482            {
483              afrMessage = ASN1OctetString.decodeAsOctetString(
484                   afrElements[2]).stringValue();
485            }
486            else
487            {
488              afrMessage = null;
489            }
490            afr = new AuthenticationFailureReason(afrType, afrName, afrMessage);
491            break;
492
493          default:
494            throw new LDAPException(ResultCode.DECODING_ERROR,
495                 ERR_GET_PWP_STATE_ISSUES_RESPONSE_UNEXPECTED_TYPE.get(
496                      StaticUtils.toHex(e.getType())));
497        }
498      }
499    }
500    catch (final LDAPException le)
501    {
502      Debug.debugException(le);
503
504      throw le;
505    }
506    catch (final Exception e)
507    {
508      Debug.debugException(e);
509
510      throw new LDAPException(ResultCode.DECODING_ERROR,
511           ERR_GET_PWP_STATE_ISSUES_RESPONSE_CANNOT_DECODE.get(
512                StaticUtils.getExceptionMessage(e)),
513           e);
514    }
515
516    authFailureReason = afr;
517    notices           = nList;
518    warnings          = wList;
519    errors            = eList;
520  }
521
522
523
524  /**
525   * Encodes the provided information into an ASN.1 octet string suitable for
526   * use as the value of this control.
527   *
528   * @param  notices            The set of password policy state usability
529   *                            notices to include.  It may be {@code null} or
530   *                            empty if there are no notices.
531   * @param  warnings           The set of password policy state usability
532   *                            warnings to include.  It may be {@code null} or
533   *                            empty if there are no warnings.
534   * @param  errors             The set of password policy state usability
535   *                            errors to include.  It may be {@code null} or
536   *                            empty if there are no errors.
537   * @param  authFailureReason  The authentication failure reason for the bind
538   *                            operation.  It may be {@code null} if there is
539   *                            no authentication failure reason.
540   *
541   * @return  The ASN.1 octet string containing the encoded control value.
542   */
543  @NotNull()
544  private static ASN1OctetString encodeValue(
545       @Nullable final List<PasswordPolicyStateAccountUsabilityNotice> notices,
546       @Nullable final List<PasswordPolicyStateAccountUsabilityWarning>
547            warnings,
548       @Nullable final List<PasswordPolicyStateAccountUsabilityError> errors,
549       @Nullable final AuthenticationFailureReason authFailureReason)
550  {
551    final ArrayList<ASN1Element> elements = new ArrayList<>(4);
552    if ((notices != null) && (! notices.isEmpty()))
553    {
554      final ArrayList<ASN1Element> noticeElements =
555           new ArrayList<>(notices.size());
556      for (final PasswordPolicyStateAccountUsabilityNotice n : notices)
557      {
558        if (n.getMessage() == null)
559        {
560          noticeElements.add(new ASN1Sequence(
561               new ASN1Integer(n.getIntValue()),
562               new ASN1OctetString(n.getName())));
563        }
564        else
565        {
566          noticeElements.add(new ASN1Sequence(
567               new ASN1Integer(n.getIntValue()),
568               new ASN1OctetString(n.getName()),
569               new ASN1OctetString(n.getMessage())));
570        }
571      }
572
573      elements.add(new ASN1Sequence(TYPE_NOTICES, noticeElements));
574    }
575
576    if ((warnings != null) && (! warnings.isEmpty()))
577    {
578      final ArrayList<ASN1Element> warningElements =
579           new ArrayList<>(warnings.size());
580      for (final PasswordPolicyStateAccountUsabilityWarning w : warnings)
581      {
582        if (w.getMessage() == null)
583        {
584          warningElements.add(new ASN1Sequence(
585               new ASN1Integer(w.getIntValue()),
586               new ASN1OctetString(w.getName())));
587        }
588        else
589        {
590          warningElements.add(new ASN1Sequence(
591               new ASN1Integer(w.getIntValue()),
592               new ASN1OctetString(w.getName()),
593               new ASN1OctetString(w.getMessage())));
594        }
595      }
596
597      elements.add(new ASN1Sequence(TYPE_WARNINGS, warningElements));
598    }
599
600    if ((errors != null) && (! errors.isEmpty()))
601    {
602      final ArrayList<ASN1Element> errorElements =
603           new ArrayList<>(errors.size());
604      for (final PasswordPolicyStateAccountUsabilityError e : errors)
605      {
606        if (e.getMessage() == null)
607        {
608          errorElements.add(new ASN1Sequence(
609               new ASN1Integer(e.getIntValue()),
610               new ASN1OctetString(e.getName())));
611        }
612        else
613        {
614          errorElements.add(new ASN1Sequence(
615               new ASN1Integer(e.getIntValue()),
616               new ASN1OctetString(e.getName()),
617               new ASN1OctetString(e.getMessage())));
618        }
619      }
620
621      elements.add(new ASN1Sequence(TYPE_ERRORS, errorElements));
622    }
623
624    if (authFailureReason != null)
625    {
626      if (authFailureReason.getMessage() == null)
627      {
628        elements.add(new ASN1Sequence(TYPE_AUTH_FAILURE_REASON,
629             new ASN1Integer(authFailureReason.getIntValue()),
630             new ASN1OctetString(authFailureReason.getName())));
631      }
632      else
633      {
634        elements.add(new ASN1Sequence(TYPE_AUTH_FAILURE_REASON,
635             new ASN1Integer(authFailureReason.getIntValue()),
636             new ASN1OctetString(authFailureReason.getName()),
637             new ASN1OctetString(authFailureReason.getMessage())));
638      }
639    }
640
641    return new ASN1OctetString(new ASN1Sequence(elements).encode());
642  }
643
644
645
646  /**
647   * {@inheritDoc}
648   */
649  @Override()
650  @NotNull()
651  public GetPasswordPolicyStateIssuesResponseControl decodeControl(
652              @NotNull final String oid, final boolean isCritical,
653              @Nullable final ASN1OctetString value)
654          throws LDAPException
655  {
656    return new GetPasswordPolicyStateIssuesResponseControl(oid, isCritical,
657         value);
658  }
659
660
661
662  /**
663   * Retrieves the set of account usability notices for the user.
664   *
665   * @return  The set of account usability notices for the user, or an empty
666   *          list if there are no notices.
667   */
668  @NotNull()
669  public List<PasswordPolicyStateAccountUsabilityNotice> getNotices()
670  {
671    return notices;
672  }
673
674
675
676  /**
677   * Retrieves the set of account usability warnings for the user.
678   *
679   * @return  The set of account usability warnings for the user, or an empty
680   *          list if there are no warnings.
681   */
682  @NotNull()
683  public List<PasswordPolicyStateAccountUsabilityWarning> getWarnings()
684  {
685    return warnings;
686  }
687
688
689
690  /**
691   * Retrieves the set of account usability errors for the user.
692   *
693   * @return  The set of account usability errors for the user, or an empty
694   *          list if there are no errors.
695   */
696  @NotNull()
697  public List<PasswordPolicyStateAccountUsabilityError> getErrors()
698  {
699    return errors;
700  }
701
702
703
704  /**
705   * Retrieves the authentication failure reason for the bind operation, if
706   * available.
707   *
708   * @return  The authentication failure reason for the bind operation, or
709   *          {@code null} if none was provided.
710   */
711  @Nullable()
712  public AuthenticationFailureReason getAuthenticationFailureReason()
713  {
714    return authFailureReason;
715  }
716
717
718
719  /**
720   * Extracts a get password policy state issues response control from the
721   * provided bind result.
722   *
723   * @param  bindResult  The bind result from which to retrieve the get password
724   *                     policy state issues response control.
725   *
726   * @return  The get password policy state issues response control contained in
727   *          the provided bind result, or {@code null} if the bind result did
728   *          not contain a get password policy state issues response control.
729   *
730   * @throws  LDAPException  If a problem is encountered while attempting to
731   *                         decode the get password policy state issues
732   *                         response control contained in the provided bind
733   *                         result.
734   */
735  @Nullable()
736  public static GetPasswordPolicyStateIssuesResponseControl get(
737                     @NotNull final BindResult bindResult)
738         throws LDAPException
739  {
740    final Control c = bindResult.getResponseControl(
741         GET_PASSWORD_POLICY_STATE_ISSUES_RESPONSE_OID);
742    if (c == null)
743    {
744      return null;
745    }
746
747    if (c instanceof GetPasswordPolicyStateIssuesResponseControl)
748    {
749      return (GetPasswordPolicyStateIssuesResponseControl) c;
750    }
751    else
752    {
753      return new GetPasswordPolicyStateIssuesResponseControl(c.getOID(),
754           c.isCritical(), c.getValue());
755    }
756  }
757
758
759
760  /**
761   * Extracts a get password policy state issues response control from the
762   * provided LDAP exception.
763   *
764   * @param  ldapException  The LDAP exception from which to retrieve the get
765   *                        password policy state issues response control.
766   *
767   * @return  The get password policy state issues response control contained in
768   *          the provided LDAP exception, or {@code null} if the exception did
769   *          not contain a get password policy state issues response control.
770   *
771   * @throws  LDAPException  If a problem is encountered while attempting to
772   *                         decode the get password policy state issues
773   *                         response control contained in the provided LDAP
774   *                         exception.
775   */
776  @Nullable()
777  public static GetPasswordPolicyStateIssuesResponseControl get(
778                     @NotNull final LDAPException ldapException)
779         throws LDAPException
780  {
781    final Control c = ldapException.getResponseControl(
782         GET_PASSWORD_POLICY_STATE_ISSUES_RESPONSE_OID);
783    if (c == null)
784    {
785      return null;
786    }
787
788    if (c instanceof GetPasswordPolicyStateIssuesResponseControl)
789    {
790      return (GetPasswordPolicyStateIssuesResponseControl) c;
791    }
792    else
793    {
794      return new GetPasswordPolicyStateIssuesResponseControl(c.getOID(),
795           c.isCritical(), c.getValue());
796    }
797  }
798
799
800
801  /**
802   * {@inheritDoc}
803   */
804  @Override()
805  @NotNull()
806  public String getControlName()
807  {
808    return INFO_CONTROL_NAME_GET_PWP_STATE_ISSUES_RESPONSE.get();
809  }
810
811
812
813  /**
814   * Retrieves a representation of this get password policy state issues
815   * response control as a JSON object.  The JSON object uses the following
816   * fields:
817   * <UL>
818   *   <LI>
819   *     {@code oid} -- A mandatory string field whose value is the object
820   *     identifier for this control.  For the get password policy state issues
821   *     response control, the OID is "1.3.6.1.4.1.30221.2.5.47".
822   *   </LI>
823   *   <LI>
824   *     {@code control-name} -- An optional string field whose value is a
825   *     human-readable name for this control.  This field is only intended for
826   *     descriptive purposes, and when decoding a control, the {@code oid}
827   *     field should be used to identify the type of control.
828   *   </LI>
829   *   <LI>
830   *     {@code criticality} -- A mandatory Boolean field used to indicate
831   *     whether this control is considered critical.
832   *   </LI>
833   *   <LI>
834   *     {@code value-base64} -- An optional string field whose value is a
835   *     base64-encoded representation of the raw value for this get password
836   *     policy state issues response control.  Exactly one of the
837   *     {@code value-base64} and {@code value-json} fields must be present.
838   *   </LI>
839   *   <LI>
840   *     {@code value-json} -- An optional JSON object field whose value is a
841   *     user-friendly representation of the value for this get password policy
842   *     state issues response control.  Exactly one of the {@code value-base64}
843   *     and {@code value-json} fields must be present, and if the
844   *     {@code value-json} field is used, then it will use the following
845   *     fields:
846   *     <UL>
847   *       <LI>
848   *         {@code notices} -- An optional array field containing JSON objects
849   *         with information about any
850   *         {@link PasswordPolicyStateAccountUsabilityNotice} values for the
851   *         user.  Each JSON object will use the following fields:
852   *         <UL>
853   *           <LI>
854   *             {@code id} -- An integer field whose value is a numeric
855   *             identifier for the account usability notice.
856   *           </LI>
857   *           <LI>
858   *             {@code name} -- A string field whose value is the name for the
859   *             account usability notice.
860   *           </LI>
861   *           <LI>
862   *             {@code message} -- An optional string field whose value is a
863   *             human-readable message with additional information about the
864   *             account usability notice.
865   *           </LI>
866   *         </UL>
867   *       </LI>
868   *       <LI>
869   *         {@code warnings} -- An optional array field containing JSON objects
870   *         with information about any
871   *         {@link PasswordPolicyStateAccountUsabilityWarning} values for the
872   *         user.  Each JSON object will use the following fields:
873   *         <UL>
874   *           <LI>
875   *             {@code id} -- An integer field whose value is a numeric
876   *             identifier for the account usability warning.
877   *           </LI>
878   *           <LI>
879   *             {@code name} -- A string field whose value is the name for the
880   *             account usability warning.
881   *           </LI>
882   *           <LI>
883   *             {@code message} -- An optional string field whose value is a
884   *             human-readable message with additional information about the
885   *             account usability warning.
886   *           </LI>
887   *         </UL>
888   *       </LI>
889   *       <LI>
890   *         {@code errors} -- An optional array field containing JSON objects
891   *         with information about any
892   *         {@link PasswordPolicyStateAccountUsabilityError} values for the
893   *         user.  Each JSON object will use the following fields:
894   *         <UL>
895   *           <LI>
896   *             {@code id} -- An integer field whose value is a numeric
897   *             identifier for the account usability error.
898   *           </LI>
899   *           <LI>
900   *             {@code name} -- A string field whose value is the name for the
901   *             account usability error.
902   *           </LI>
903   *           <LI>
904   *             {@code message} -- An optional string field whose value is a
905   *             human-readable message with additional information about the
906   *             account usability error.
907   *           </LI>
908   *         </UL>
909   *       </LI>
910   *       <LI>
911   *         {@code authentication-failure-reason} -- An optional JSON object
912   *         field that represents an {@link AuthenticationFailureReason} with
913   *         information about the reason that the authentication attempt
914   *         failed.  If present, this JSON object will use the following
915   *         fields:
916   *         <UL>
917   *           <LI>
918   *             {@code id} -- An integer field whose value is a numeric
919   *             identifier for the authentication failure reason.
920   *           </LI>
921   *           <LI>
922   *             {@code name} -- A string field whose value is the name for the
923   *             authentication failure reason.
924   *           </LI>
925   *           <LI>
926   *             {@code message} -- An optional string field whose value is a
927   *             human-readable message with additional information about the
928   *             authentication failure.
929   *           </LI>
930   *         </UL>
931   *       </LI>
932   *     </UL>
933   *   </LI>
934   * </UL>
935   *
936   * @return  A JSON object that contains a representation of this control.
937   */
938  @Override()
939  @NotNull()
940  public JSONObject toJSONControl()
941  {
942    final Map<String,JSONValue> valueFields = new LinkedHashMap<>();
943
944    if (! notices.isEmpty())
945    {
946      final List<JSONValue> arrayValues = new ArrayList<>(notices.size());
947      for (final PasswordPolicyStateAccountUsabilityNotice notice : notices)
948      {
949        arrayValues.add(encodeItem(notice.getIntValue(), notice.getName(),
950             notice.getMessage()));
951      }
952      valueFields.put(JSON_FIELD_NOTICES, new JSONArray(arrayValues));
953    }
954
955    if (! warnings.isEmpty())
956    {
957      final List<JSONValue> arrayValues = new ArrayList<>(warnings.size());
958      for (final PasswordPolicyStateAccountUsabilityWarning warning : warnings)
959      {
960        arrayValues.add(encodeItem(warning.getIntValue(), warning.getName(),
961             warning.getMessage()));
962      }
963      valueFields.put(JSON_FIELD_WARNINGS, new JSONArray(arrayValues));
964    }
965
966    if (! errors.isEmpty())
967    {
968      final List<JSONValue> arrayValues = new ArrayList<>(notices.size());
969      for (final PasswordPolicyStateAccountUsabilityError error : errors)
970      {
971        arrayValues.add(encodeItem(error.getIntValue(), error.getName(),
972             error.getMessage()));
973      }
974      valueFields.put(JSON_FIELD_ERRORS, new JSONArray(arrayValues));
975    }
976
977    if (authFailureReason != null)
978    {
979      valueFields.put(JSON_FIELD_AUTH_FAILURE_REASON,
980           encodeItem(authFailureReason.getIntValue(),
981                authFailureReason.getName(), authFailureReason.getMessage()));
982    }
983
984    return new JSONObject(
985         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
986              GET_PASSWORD_POLICY_STATE_ISSUES_RESPONSE_OID),
987         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
988              INFO_CONTROL_NAME_GET_PWP_STATE_ISSUES_RESPONSE.get()),
989         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
990              isCritical()),
991         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
992              new JSONObject(valueFields)));
993  }
994
995
996
997  /**
998   * Retrieves a JSON object that contains an encoded representation of a
999   * password policy state issue or authentication failure reason with the
1000   * provided information.
1001   *
1002   * @param  id       The ID for the item.
1003   * @param  name     The name for the item.  It must not be {@code null}.
1004   * @param  message  The message for the item.  It may be {@code null} if no
1005   *                  message is available.
1006   *
1007   * @return  A JSON object that contains an encoded representation of the
1008   *          provided information.
1009   */
1010  @NotNull()
1011  private static JSONObject encodeItem(final int id,
1012                                       @NotNull final String name,
1013                                       @Nullable final String message)
1014  {
1015    final Map<String,JSONValue> fields = new LinkedHashMap<>();
1016    fields.put(JSON_FIELD_ID, new JSONNumber(id));
1017    fields.put(JSON_FIELD_NAME, new JSONString(name));
1018
1019    if (message != null)
1020    {
1021      fields.put(JSON_FIELD_MESSAGE, new JSONString(message));
1022    }
1023
1024    return new JSONObject(fields);
1025  }
1026
1027
1028
1029  /**
1030   * Attempts to decode the provided object as a JSON representation of a get
1031   * password policy state issues response control.
1032   *
1033   * @param  controlObject  The JSON object to be decoded.  It must not be
1034   *                        {@code null}.
1035   * @param  strict         Indicates whether to use strict mode when decoding
1036   *                        the provided JSON object.  If this is {@code true},
1037   *                        then this method will throw an exception if the
1038   *                        provided JSON object contains any unrecognized
1039   *                        fields.  If this is {@code false}, then unrecognized
1040   *                        fields will be ignored.
1041   *
1042   * @return  The get password policy state issues response control that was
1043   *          decoded from the provided JSON object.
1044   *
1045   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
1046   *                         valid get password policy state issues response
1047   *                         control.
1048   */
1049  @NotNull()
1050  public static GetPasswordPolicyStateIssuesResponseControl decodeJSONControl(
1051              @NotNull final JSONObject controlObject,
1052              final boolean strict)
1053         throws LDAPException
1054  {
1055    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
1056         controlObject, strict, true, true);
1057
1058    final ASN1OctetString rawValue = jsonControl.getRawValue();
1059    if (rawValue != null)
1060    {
1061      return new GetPasswordPolicyStateIssuesResponseControl(
1062           jsonControl.getOID(), jsonControl.getCriticality(), rawValue);
1063    }
1064
1065
1066    final JSONObject valueObject = jsonControl.getValueObject();
1067
1068    final List<PasswordPolicyStateAccountUsabilityNotice> notices =
1069         new ArrayList<>();
1070    final List<JSONValue> noticeValues =
1071         valueObject.getFieldAsArray(JSON_FIELD_NOTICES);
1072    if (noticeValues != null)
1073    {
1074      for (final JSONValue v : noticeValues)
1075      {
1076        if (v instanceof JSONObject)
1077        {
1078          final JSONObject o = (JSONObject) v;
1079
1080          final Integer id = o.getFieldAsInteger(JSON_FIELD_ID);
1081          if (id == null)
1082          {
1083            throw new LDAPException(ResultCode.DECODING_ERROR,
1084                 ERR_GET_PWP_STATE_ISSUES_RESPONSE_JSON_MISSING_ITEM_FIELD.get(
1085                      controlObject.toSingleLineString(),
1086                      JSON_FIELD_NOTICES, JSON_FIELD_ID));
1087          }
1088
1089          final String name = o.getFieldAsString(JSON_FIELD_NAME);
1090          if (name == null)
1091          {
1092            throw new LDAPException(ResultCode.DECODING_ERROR,
1093                 ERR_GET_PWP_STATE_ISSUES_RESPONSE_JSON_MISSING_ITEM_FIELD.get(
1094                      controlObject.toSingleLineString(),
1095                      JSON_FIELD_NOTICES, JSON_FIELD_NAME));
1096          }
1097
1098          final String message = o.getFieldAsString(JSON_FIELD_MESSAGE);
1099          notices.add(new PasswordPolicyStateAccountUsabilityNotice(id, name,
1100               message));
1101        }
1102        else
1103        {
1104          throw new LDAPException(ResultCode.DECODING_ERROR,
1105               ERR_GET_PWP_STATE_ISSUES_RESPONSE_JSON_VALUE_NOT_OBJECT.get(
1106                    controlObject.toSingleLineString(),
1107                    JSON_FIELD_NOTICES));
1108        }
1109      }
1110    }
1111
1112
1113    final List<PasswordPolicyStateAccountUsabilityWarning > warnings =
1114         new ArrayList<>();
1115    final List<JSONValue> warningValues =
1116         valueObject.getFieldAsArray(JSON_FIELD_WARNINGS);
1117    if (warningValues != null)
1118    {
1119      for (final JSONValue v : warningValues)
1120      {
1121        if (v instanceof JSONObject)
1122        {
1123          final JSONObject o = (JSONObject) v;
1124
1125          final Integer id = o.getFieldAsInteger(JSON_FIELD_ID);
1126          if (id == null)
1127          {
1128            throw new LDAPException(ResultCode.DECODING_ERROR,
1129                 ERR_GET_PWP_STATE_ISSUES_RESPONSE_JSON_MISSING_ITEM_FIELD.get(
1130                      controlObject.toSingleLineString(),
1131                      JSON_FIELD_WARNINGS, JSON_FIELD_ID));
1132          }
1133
1134          final String name = o.getFieldAsString(JSON_FIELD_NAME);
1135          if (name == null)
1136          {
1137            throw new LDAPException(ResultCode.DECODING_ERROR,
1138                 ERR_GET_PWP_STATE_ISSUES_RESPONSE_JSON_MISSING_ITEM_FIELD.get(
1139                      controlObject.toSingleLineString(),
1140                      JSON_FIELD_WARNINGS, JSON_FIELD_NAME));
1141          }
1142
1143          final String message = o.getFieldAsString(JSON_FIELD_MESSAGE);
1144          warnings.add(new PasswordPolicyStateAccountUsabilityWarning(id, name,
1145               message));
1146        }
1147        else
1148        {
1149          throw new LDAPException(ResultCode.DECODING_ERROR,
1150               ERR_GET_PWP_STATE_ISSUES_RESPONSE_JSON_VALUE_NOT_OBJECT.get(
1151                    controlObject.toSingleLineString(),
1152                    JSON_FIELD_WARNINGS));
1153        }
1154      }
1155    }
1156
1157
1158    final List<PasswordPolicyStateAccountUsabilityError > errors =
1159         new ArrayList<>();
1160    final List<JSONValue> errorValues =
1161         valueObject.getFieldAsArray(JSON_FIELD_ERRORS);
1162    if (errorValues != null)
1163    {
1164      for (final JSONValue v : errorValues)
1165      {
1166        if (v instanceof JSONObject)
1167        {
1168          final JSONObject o = (JSONObject) v;
1169
1170          final Integer id = o.getFieldAsInteger(JSON_FIELD_ID);
1171          if (id == null)
1172          {
1173            throw new LDAPException(ResultCode.DECODING_ERROR,
1174                 ERR_GET_PWP_STATE_ISSUES_RESPONSE_JSON_MISSING_ITEM_FIELD.get(
1175                      controlObject.toSingleLineString(),
1176                      JSON_FIELD_ERRORS, JSON_FIELD_ID));
1177          }
1178
1179          final String name = o.getFieldAsString(JSON_FIELD_NAME);
1180          if (name == null)
1181          {
1182            throw new LDAPException(ResultCode.DECODING_ERROR,
1183                 ERR_GET_PWP_STATE_ISSUES_RESPONSE_JSON_MISSING_ITEM_FIELD.get(
1184                      controlObject.toSingleLineString(),
1185                      JSON_FIELD_ERRORS, JSON_FIELD_NAME));
1186          }
1187
1188          final String message = o.getFieldAsString(JSON_FIELD_MESSAGE);
1189          errors.add(new PasswordPolicyStateAccountUsabilityError(id, name,
1190               message));
1191        }
1192        else
1193        {
1194          throw new LDAPException(ResultCode.DECODING_ERROR,
1195               ERR_GET_PWP_STATE_ISSUES_RESPONSE_JSON_VALUE_NOT_OBJECT.get(
1196                    controlObject.toSingleLineString(),
1197                    JSON_FIELD_ERRORS));
1198        }
1199      }
1200    }
1201
1202
1203    final AuthenticationFailureReason authFailureReason;
1204    final JSONObject authFailureReasonObject =
1205         valueObject.getFieldAsObject(JSON_FIELD_AUTH_FAILURE_REASON);
1206    if (authFailureReasonObject == null)
1207    {
1208      authFailureReason = null;
1209    }
1210    else
1211    {
1212      final Integer id =
1213           authFailureReasonObject.getFieldAsInteger(JSON_FIELD_ID);
1214      if (id == null)
1215      {
1216        throw new LDAPException(ResultCode.DECODING_ERROR,
1217             ERR_GET_PWP_STATE_ISSUES_RESPONSE_JSON_MISSING_ITEM_FIELD.get(
1218                  controlObject.toSingleLineString(),
1219                  JSON_FIELD_AUTH_FAILURE_REASON, JSON_FIELD_ID));
1220      }
1221
1222      final String name =
1223           authFailureReasonObject.getFieldAsString(JSON_FIELD_NAME);
1224      if (name == null)
1225      {
1226        throw new LDAPException(ResultCode.DECODING_ERROR,
1227             ERR_GET_PWP_STATE_ISSUES_RESPONSE_JSON_MISSING_ITEM_FIELD.get(
1228                  controlObject.toSingleLineString(),
1229                  JSON_FIELD_ERRORS, JSON_FIELD_NAME));
1230      }
1231
1232      final String message =
1233           authFailureReasonObject.getFieldAsString(JSON_FIELD_MESSAGE);
1234      authFailureReason = new AuthenticationFailureReason(id, name, message);
1235    }
1236
1237
1238    if (strict)
1239    {
1240      final List<String> unrecognizedFields =
1241           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
1242                valueObject, JSON_FIELD_NOTICES, JSON_FIELD_WARNINGS,
1243                JSON_FIELD_ERRORS, JSON_FIELD_AUTH_FAILURE_REASON);
1244      if (! unrecognizedFields.isEmpty())
1245      {
1246        throw new LDAPException(ResultCode.DECODING_ERROR,
1247             ERR_GET_PWP_STATE_ISSUES_RESPONSE_JSON_CONTROL_UNRECOGNIZED_FIELD.
1248                  get(controlObject.toSingleLineString(),
1249                       unrecognizedFields.get(0)));
1250      }
1251    }
1252
1253
1254    return new GetPasswordPolicyStateIssuesResponseControl(notices, warnings,
1255         errors, authFailureReason);
1256  }
1257
1258
1259
1260  /**
1261   * {@inheritDoc}
1262   */
1263  @Override()
1264  public void toString(@NotNull final StringBuilder buffer)
1265  {
1266    buffer.append("GetPasswordPolicyStateIssuesResponseControl(notices={ ");
1267
1268    final Iterator<PasswordPolicyStateAccountUsabilityNotice> noticeIterator =
1269         notices.iterator();
1270    while (noticeIterator.hasNext())
1271    {
1272      buffer.append(noticeIterator.next().toString());
1273      if (noticeIterator.hasNext())
1274      {
1275        buffer.append(", ");
1276      }
1277    }
1278    buffer.append("}, warnings={ ");
1279
1280    final Iterator<PasswordPolicyStateAccountUsabilityWarning> warningIterator =
1281         warnings.iterator();
1282    while (warningIterator.hasNext())
1283    {
1284      buffer.append(warningIterator.next().toString());
1285      if (warningIterator.hasNext())
1286      {
1287        buffer.append(", ");
1288      }
1289    }
1290    buffer.append("}, errors={ ");
1291
1292    final Iterator<PasswordPolicyStateAccountUsabilityError> errorIterator =
1293         errors.iterator();
1294    while (errorIterator.hasNext())
1295    {
1296      buffer.append(errorIterator.next().toString());
1297      if (errorIterator.hasNext())
1298      {
1299        buffer.append(", ");
1300      }
1301    }
1302    buffer.append('}');
1303
1304    if (authFailureReason != null)
1305    {
1306      buffer.append(", authFailureReason=");
1307      buffer.append(authFailureReason.toString());
1308    }
1309
1310    buffer.append(')');
1311  }
1312}