001/*
002 * Copyright 2015-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-2018 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk.unboundidds.controls;
022
023
024
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Iterator;
028import java.util.List;
029
030import com.unboundid.asn1.ASN1Element;
031import com.unboundid.asn1.ASN1Integer;
032import com.unboundid.asn1.ASN1OctetString;
033import com.unboundid.asn1.ASN1Sequence;
034import com.unboundid.ldap.sdk.BindResult;
035import com.unboundid.ldap.sdk.Control;
036import com.unboundid.ldap.sdk.DecodeableControl;
037import com.unboundid.ldap.sdk.LDAPException;
038import com.unboundid.ldap.sdk.ResultCode;
039import com.unboundid.ldap.sdk.unboundidds.extensions.
040            PasswordPolicyStateAccountUsabilityError;
041import com.unboundid.ldap.sdk.unboundidds.extensions.
042            PasswordPolicyStateAccountUsabilityNotice;
043import com.unboundid.ldap.sdk.unboundidds.extensions.
044            PasswordPolicyStateAccountUsabilityWarning;
045import com.unboundid.util.Debug;
046import com.unboundid.util.NotMutable;
047import com.unboundid.util.StaticUtils;
048import com.unboundid.util.ThreadSafety;
049import com.unboundid.util.ThreadSafetyLevel;
050
051import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
052
053
054
055/**
056 * This class provides an implementation of a response control that can be
057 * included in a bind response with information about any password policy state
058 * notices, warnings, and/or errors for the user.
059 * <BR>
060 * <BLOCKQUOTE>
061 *   <B>NOTE:</B>  This class, and other classes within the
062 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
063 *   supported for use against Ping Identity, UnboundID, and
064 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
065 *   for proprietary functionality or for external specifications that are not
066 *   considered stable or mature enough to be guaranteed to work in an
067 *   interoperable way with other types of LDAP servers.
068 * </BLOCKQUOTE>
069 * <BR>
070 * This control has an OID of 1.3.6.1.4.1.30221.2.5.47, a criticality of
071 * {@code false}, and a value with the following encoding:
072 * <PRE>
073 *   GetPasswordPolicyStateIssuesResponse ::= SEQUENCE {
074 *        notices               [0] SEQUENCE OF SEQUENCE {
075 *             type        INTEGER,
076 *             name        OCTET STRING,
077 *             message     OCTET STRING OPTIONAL } OPTIONAL,
078 *        warnings              [1] SEQUENCE OF SEQUENCE {
079 *             type        INTEGER,
080 *             name        OCTET STRING,
081 *             message     OCTET STRING OPTIONAL } OPTIONAL,
082 *        errors                [2] SEQUENCE OF SEQUENCE {
083 *             type        INTEGER,
084 *             name        OCTET STRING,
085 *             message     OCTET STRING OPTIONAL } OPTIONAL,
086 *        authFailureReason     [3] SEQUENCE {
087 *             type        INTEGER,
088 *             name        OCTET STRING,
089 *             message     OCTET STRING OPTIONAL } OPTIONAL,
090 *        ... }
091 * </PRE>
092 */
093@NotMutable()
094@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
095public final class GetPasswordPolicyStateIssuesResponseControl
096       extends Control
097       implements DecodeableControl
098{
099  /**
100   * The OID (1.3.6.1.4.1.30221.2.5.47) for the get password policy state issues
101   * response control.
102   */
103  public static final String GET_PASSWORD_POLICY_STATE_ISSUES_RESPONSE_OID =
104       "1.3.6.1.4.1.30221.2.5.47";
105
106
107
108  /**
109   * The BER type to use for the value sequence element that holds the set of
110   * account usability notices.
111   */
112  private static final byte TYPE_NOTICES = (byte) 0xA0;
113
114
115
116  /**
117   * The BER type to use for the value sequence element that holds the set of
118   * account usability warnings.
119   */
120  private static final byte TYPE_WARNINGS = (byte) 0xA1;
121
122
123
124  /**
125   * The BER type to use for the value sequence element that holds the set of
126   * account usability errors.
127   */
128  private static final byte TYPE_ERRORS = (byte) 0xA2;
129
130
131
132  /**
133   * The BER type to use for the value sequence element that holds the
134   * authentication failure reason.
135   */
136  private static final byte TYPE_AUTH_FAILURE_REASON = (byte) 0xA3;
137
138
139
140  /**
141   * The serial version UID for this serializable class.
142   */
143  private static final long serialVersionUID = 7509027658735069270L;
144
145
146
147  // The authentication failure reason for the bind operation.
148  private final AuthenticationFailureReason authFailureReason;
149
150  // The set of account usability errors.
151  private final List<PasswordPolicyStateAccountUsabilityError> errors;
152
153  // The set of account usability notices.
154  private final List<PasswordPolicyStateAccountUsabilityNotice> notices;
155
156  // The set of account usability warnings.
157  private final List<PasswordPolicyStateAccountUsabilityWarning> warnings;
158
159
160
161  /**
162   * Creates a new empty control instance that is intended to be used only for
163   * decoding controls via the {@code DecodeableControl} interface.
164   */
165  GetPasswordPolicyStateIssuesResponseControl()
166  {
167    authFailureReason = null;
168    notices = Collections.emptyList();
169    warnings = Collections.emptyList();
170    errors = Collections.emptyList();
171  }
172
173
174
175  /**
176   * Creates a new instance of this control with the provided information.
177   *
178   * @param  notices   The set of password policy state usability notices to
179   *                   include.  It may be {@code null} or empty if there are
180   *                   no notices.
181   * @param  warnings  The set of password policy state usability warnings to
182   *                   include.  It may be {@code null} or empty if there are
183   *                   no warnings.
184   * @param  errors    The set of password policy state usability errors to
185   *                   include.  It may be {@code null} or empty if there are
186   *                   no errors.
187   */
188  public GetPasswordPolicyStateIssuesResponseControl(
189       final List<PasswordPolicyStateAccountUsabilityNotice> notices,
190       final List<PasswordPolicyStateAccountUsabilityWarning> warnings,
191       final List<PasswordPolicyStateAccountUsabilityError> errors)
192  {
193    this(notices, warnings, errors, null);
194  }
195
196
197
198  /**
199   * Creates a new instance of this control with the provided information.
200   *
201   * @param  notices            The set of password policy state usability
202   *                            notices to include.  It may be {@code null} or
203   *                            empty if there are no notices.
204   * @param  warnings           The set of password policy state usability
205   *                            warnings to include.  It may be {@code null} or
206   *                            empty if there are no warnings.
207   * @param  errors             The set of password policy state usability
208   *                            errors to include.  It may be {@code null} or
209   *                            empty if there are no errors.
210   * @param  authFailureReason  The authentication failure reason for the bind
211   *                            operation.  It may be {@code null} if there is
212   *                            no authentication failure reason.
213   */
214  public GetPasswordPolicyStateIssuesResponseControl(
215       final List<PasswordPolicyStateAccountUsabilityNotice> notices,
216       final List<PasswordPolicyStateAccountUsabilityWarning> warnings,
217       final List<PasswordPolicyStateAccountUsabilityError> errors,
218       final AuthenticationFailureReason authFailureReason)
219  {
220    super(GET_PASSWORD_POLICY_STATE_ISSUES_RESPONSE_OID, false,
221         encodeValue(notices, warnings, errors, authFailureReason));
222
223    this.authFailureReason = authFailureReason;
224
225    if (notices == null)
226    {
227      this.notices = Collections.emptyList();
228    }
229    else
230    {
231      this.notices = Collections.unmodifiableList(new ArrayList<>(notices));
232    }
233
234    if (warnings == null)
235    {
236      this.warnings = Collections.emptyList();
237    }
238    else
239    {
240      this.warnings = Collections.unmodifiableList(new ArrayList<>(warnings));
241    }
242
243    if (errors == null)
244    {
245      this.errors = Collections.emptyList();
246    }
247    else
248    {
249      this.errors = Collections.unmodifiableList(new ArrayList<>(errors));
250    }
251  }
252
253
254
255  /**
256   * Creates a new instance of this control that is decoded from the provided
257   * generic control.
258   *
259   * @param  oid         The OID for the control.
260   * @param  isCritical  Indicates whether this control should be marked
261   *                     critical.
262   * @param  value       The encoded value for the control.
263   *
264   * @throws LDAPException  If a problem is encountered while attempting to
265   *                         decode the provided control as a get password
266   *                         policy state issues response control.
267   */
268  public GetPasswordPolicyStateIssuesResponseControl(final String oid,
269              final boolean isCritical, final ASN1OctetString value)
270         throws LDAPException
271  {
272    super(oid, isCritical, value);
273
274    if (value == null)
275    {
276      throw new LDAPException(ResultCode.DECODING_ERROR,
277           ERR_GET_PWP_STATE_ISSUES_RESPONSE_NO_VALUE.get());
278    }
279
280    AuthenticationFailureReason afr = null;
281    List<PasswordPolicyStateAccountUsabilityNotice> nList =
282         Collections.emptyList();
283    List<PasswordPolicyStateAccountUsabilityWarning> wList =
284         Collections.emptyList();
285    List<PasswordPolicyStateAccountUsabilityError> eList =
286         Collections.emptyList();
287
288    try
289    {
290      for (final ASN1Element e :
291           ASN1Sequence.decodeAsSequence(value.getValue()).elements())
292      {
293        switch (e.getType())
294        {
295          case TYPE_NOTICES:
296            nList = new ArrayList<>(10);
297            for (final ASN1Element ne :
298                 ASN1Sequence.decodeAsSequence(e).elements())
299            {
300              final ASN1Element[] noticeElements =
301                   ASN1Sequence.decodeAsSequence(ne).elements();
302              final int type = ASN1Integer.decodeAsInteger(
303                   noticeElements[0]).intValue();
304              final String name = ASN1OctetString.decodeAsOctetString(
305                   noticeElements[1]).stringValue();
306
307              final String message;
308              if (noticeElements.length == 3)
309              {
310                message = ASN1OctetString.decodeAsOctetString(
311                     noticeElements[2]).stringValue();
312              }
313              else
314              {
315                message = null;
316              }
317
318              nList.add(new PasswordPolicyStateAccountUsabilityNotice(type,
319                   name, message));
320            }
321            nList = Collections.unmodifiableList(nList);
322            break;
323
324          case TYPE_WARNINGS:
325            wList =
326                 new ArrayList<>(10);
327            for (final ASN1Element we :
328                 ASN1Sequence.decodeAsSequence(e).elements())
329            {
330              final ASN1Element[] warningElements =
331                   ASN1Sequence.decodeAsSequence(we).elements();
332              final int type = ASN1Integer.decodeAsInteger(
333                   warningElements[0]).intValue();
334              final String name = ASN1OctetString.decodeAsOctetString(
335                   warningElements[1]).stringValue();
336
337              final String message;
338              if (warningElements.length == 3)
339              {
340                message = ASN1OctetString.decodeAsOctetString(
341                     warningElements[2]).stringValue();
342              }
343              else
344              {
345                message = null;
346              }
347
348              wList.add(new PasswordPolicyStateAccountUsabilityWarning(type,
349                   name, message));
350            }
351            wList = Collections.unmodifiableList(wList);
352            break;
353
354          case TYPE_ERRORS:
355            eList = new ArrayList<>(10);
356            for (final ASN1Element ee :
357                 ASN1Sequence.decodeAsSequence(e).elements())
358            {
359              final ASN1Element[] errorElements =
360                   ASN1Sequence.decodeAsSequence(ee).elements();
361              final int type = ASN1Integer.decodeAsInteger(
362                   errorElements[0]).intValue();
363              final String name = ASN1OctetString.decodeAsOctetString(
364                   errorElements[1]).stringValue();
365
366              final String message;
367              if (errorElements.length == 3)
368              {
369                message = ASN1OctetString.decodeAsOctetString(
370                     errorElements[2]).stringValue();
371              }
372              else
373              {
374                message = null;
375              }
376
377              eList.add(new PasswordPolicyStateAccountUsabilityError(type,
378                   name, message));
379            }
380            eList = Collections.unmodifiableList(eList);
381            break;
382
383          case TYPE_AUTH_FAILURE_REASON:
384            final ASN1Element[] afrElements =
385                 ASN1Sequence.decodeAsSequence(e).elements();
386            final int afrType =
387                 ASN1Integer.decodeAsInteger(afrElements[0]).intValue();
388            final String afrName = ASN1OctetString.decodeAsOctetString(
389                 afrElements[1]).stringValue();
390
391            final String afrMessage;
392            if (afrElements.length == 3)
393            {
394              afrMessage = ASN1OctetString.decodeAsOctetString(
395                   afrElements[2]).stringValue();
396            }
397            else
398            {
399              afrMessage = null;
400            }
401            afr = new AuthenticationFailureReason(afrType, afrName, afrMessage);
402            break;
403
404          default:
405            throw new LDAPException(ResultCode.DECODING_ERROR,
406                 ERR_GET_PWP_STATE_ISSUES_RESPONSE_UNEXPECTED_TYPE.get(
407                      StaticUtils.toHex(e.getType())));
408        }
409      }
410    }
411    catch (final LDAPException le)
412    {
413      Debug.debugException(le);
414
415      throw le;
416    }
417    catch (final Exception e)
418    {
419      Debug.debugException(e);
420
421      throw new LDAPException(ResultCode.DECODING_ERROR,
422           ERR_GET_PWP_STATE_ISSUES_RESPONSE_CANNOT_DECODE.get(
423                StaticUtils.getExceptionMessage(e)),
424           e);
425    }
426
427    authFailureReason = afr;
428    notices           = nList;
429    warnings          = wList;
430    errors            = eList;
431  }
432
433
434
435  /**
436   * Encodes the provided information into an ASN.1 octet string suitable for
437   * use as the value of this control.
438   *
439   * @param  notices            The set of password policy state usability
440   *                            notices to include.  It may be {@code null} or
441   *                            empty if there are no notices.
442   * @param  warnings           The set of password policy state usability
443   *                            warnings to include.  It may be {@code null} or
444   *                            empty if there are no warnings.
445   * @param  errors             The set of password policy state usability
446   *                            errors to include.  It may be {@code null} or
447   *                            empty if there are no errors.
448   * @param  authFailureReason  The authentication failure reason for the bind
449   *                            operation.  It may be {@code null} if there is
450   *                            no authentication failure reason.
451   *
452   * @return  The ASN.1 octet string containing the encoded control value.
453   */
454  private static ASN1OctetString encodeValue(
455              final List<PasswordPolicyStateAccountUsabilityNotice> notices,
456              final List<PasswordPolicyStateAccountUsabilityWarning> warnings,
457              final List<PasswordPolicyStateAccountUsabilityError> errors,
458              final AuthenticationFailureReason authFailureReason)
459  {
460    final ArrayList<ASN1Element> elements = new ArrayList<>(4);
461    if ((notices != null) && (! notices.isEmpty()))
462    {
463      final ArrayList<ASN1Element> noticeElements =
464           new ArrayList<>(notices.size());
465      for (final PasswordPolicyStateAccountUsabilityNotice n : notices)
466      {
467        if (n.getMessage() == null)
468        {
469          noticeElements.add(new ASN1Sequence(
470               new ASN1Integer(n.getIntValue()),
471               new ASN1OctetString(n.getName())));
472        }
473        else
474        {
475          noticeElements.add(new ASN1Sequence(
476               new ASN1Integer(n.getIntValue()),
477               new ASN1OctetString(n.getName()),
478               new ASN1OctetString(n.getMessage())));
479        }
480      }
481
482      elements.add(new ASN1Sequence(TYPE_NOTICES, noticeElements));
483    }
484
485    if ((warnings != null) && (! warnings.isEmpty()))
486    {
487      final ArrayList<ASN1Element> warningElements =
488           new ArrayList<>(warnings.size());
489      for (final PasswordPolicyStateAccountUsabilityWarning w : warnings)
490      {
491        if (w.getMessage() == null)
492        {
493          warningElements.add(new ASN1Sequence(
494               new ASN1Integer(w.getIntValue()),
495               new ASN1OctetString(w.getName())));
496        }
497        else
498        {
499          warningElements.add(new ASN1Sequence(
500               new ASN1Integer(w.getIntValue()),
501               new ASN1OctetString(w.getName()),
502               new ASN1OctetString(w.getMessage())));
503        }
504      }
505
506      elements.add(new ASN1Sequence(TYPE_WARNINGS, warningElements));
507    }
508
509    if ((errors != null) && (! errors.isEmpty()))
510    {
511      final ArrayList<ASN1Element> errorElements =
512           new ArrayList<>(errors.size());
513      for (final PasswordPolicyStateAccountUsabilityError e : errors)
514      {
515        if (e.getMessage() == null)
516        {
517          errorElements.add(new ASN1Sequence(
518               new ASN1Integer(e.getIntValue()),
519               new ASN1OctetString(e.getName())));
520        }
521        else
522        {
523          errorElements.add(new ASN1Sequence(
524               new ASN1Integer(e.getIntValue()),
525               new ASN1OctetString(e.getName()),
526               new ASN1OctetString(e.getMessage())));
527        }
528      }
529
530      elements.add(new ASN1Sequence(TYPE_ERRORS, errorElements));
531    }
532
533    if (authFailureReason != null)
534    {
535      if (authFailureReason.getMessage() == null)
536      {
537        elements.add(new ASN1Sequence(TYPE_AUTH_FAILURE_REASON,
538             new ASN1Integer(authFailureReason.getIntValue()),
539             new ASN1OctetString(authFailureReason.getName())));
540      }
541      else
542      {
543        elements.add(new ASN1Sequence(TYPE_AUTH_FAILURE_REASON,
544             new ASN1Integer(authFailureReason.getIntValue()),
545             new ASN1OctetString(authFailureReason.getName()),
546             new ASN1OctetString(authFailureReason.getMessage())));
547      }
548    }
549
550    return new ASN1OctetString(new ASN1Sequence(elements).encode());
551  }
552
553
554
555  /**
556   * {@inheritDoc}
557   */
558  @Override()
559  public GetPasswordPolicyStateIssuesResponseControl decodeControl(
560              final String oid, final boolean isCritical,
561              final ASN1OctetString value)
562          throws LDAPException
563  {
564    return new GetPasswordPolicyStateIssuesResponseControl(oid, isCritical,
565         value);
566  }
567
568
569
570  /**
571   * Retrieves the set of account usability notices for the user.
572   *
573   * @return  The set of account usability notices for the user, or an empty
574   *          list if there are no notices.
575   */
576  public List<PasswordPolicyStateAccountUsabilityNotice> getNotices()
577  {
578    return notices;
579  }
580
581
582
583  /**
584   * Retrieves the set of account usability warnings for the user.
585   *
586   * @return  The set of account usability warnings for the user, or an empty
587   *          list if there are no warnings.
588   */
589  public List<PasswordPolicyStateAccountUsabilityWarning> getWarnings()
590  {
591    return warnings;
592  }
593
594
595
596  /**
597   * Retrieves the set of account usability errors for the user.
598   *
599   * @return  The set of account usability errors for the user, or an empty
600   *          list if there are no errors.
601   */
602  public List<PasswordPolicyStateAccountUsabilityError> getErrors()
603  {
604    return errors;
605  }
606
607
608
609  /**
610   * Retrieves the authentication failure reason for the bind operation, if
611   * available.
612   *
613   * @return  The authentication failure reason for the bind operation, or
614   *          {@code null} if none was provided.
615   */
616  public AuthenticationFailureReason getAuthenticationFailureReason()
617  {
618    return authFailureReason;
619  }
620
621
622
623  /**
624   * Extracts a get password policy state issues response control from the
625   * provided bind result.
626   *
627   * @param  bindResult  The bind result from which to retrieve the get password
628   *                     policy state issues response control.
629   *
630   * @return  The get password policy state issues response control contained in
631   *          the provided bind result, or {@code null} if the bind result did
632   *          not contain a get password policy state issues response control.
633   *
634   * @throws  LDAPException  If a problem is encountered while attempting to
635   *                         decode the get password policy state issues
636   *                         response control contained in the provided bind
637   *                         result.
638   */
639  public static GetPasswordPolicyStateIssuesResponseControl get(
640                     final BindResult bindResult)
641         throws LDAPException
642  {
643    final Control c = bindResult.getResponseControl(
644         GET_PASSWORD_POLICY_STATE_ISSUES_RESPONSE_OID);
645    if (c == null)
646    {
647      return null;
648    }
649
650    if (c instanceof GetPasswordPolicyStateIssuesResponseControl)
651    {
652      return (GetPasswordPolicyStateIssuesResponseControl) c;
653    }
654    else
655    {
656      return new GetPasswordPolicyStateIssuesResponseControl(c.getOID(),
657           c.isCritical(), c.getValue());
658    }
659  }
660
661
662
663  /**
664   * Extracts a get password policy state issues response control from the
665   * provided LDAP exception.
666   *
667   * @param  ldapException  The LDAP exception from which to retrieve the get
668   *                        password policy state issues response control.
669   *
670   * @return  The get password policy state issues response control contained in
671   *          the provided LDAP exception, or {@code null} if the exception did
672   *          not contain a get password policy state issues response control.
673   *
674   * @throws  LDAPException  If a problem is encountered while attempting to
675   *                         decode the get password policy state issues
676   *                         response control contained in the provided LDAP
677   *                         exception.
678   */
679  public static GetPasswordPolicyStateIssuesResponseControl get(
680                     final LDAPException ldapException)
681         throws LDAPException
682  {
683    final Control c = ldapException.getResponseControl(
684         GET_PASSWORD_POLICY_STATE_ISSUES_RESPONSE_OID);
685    if (c == null)
686    {
687      return null;
688    }
689
690    if (c instanceof GetPasswordPolicyStateIssuesResponseControl)
691    {
692      return (GetPasswordPolicyStateIssuesResponseControl) c;
693    }
694    else
695    {
696      return new GetPasswordPolicyStateIssuesResponseControl(c.getOID(),
697           c.isCritical(), c.getValue());
698    }
699  }
700
701
702
703  /**
704   * {@inheritDoc}
705   */
706  @Override()
707  public String getControlName()
708  {
709    return INFO_CONTROL_NAME_GET_PWP_STATE_ISSUES_RESPONSE.get();
710  }
711
712
713
714  /**
715   * {@inheritDoc}
716   */
717  @Override()
718  public void toString(final StringBuilder buffer)
719  {
720    buffer.append("GetPasswordPolicyStateIssuesResponseControl(notices={ ");
721
722    final Iterator<PasswordPolicyStateAccountUsabilityNotice> noticeIterator =
723         notices.iterator();
724    while (noticeIterator.hasNext())
725    {
726      buffer.append(noticeIterator.next().toString());
727      if (noticeIterator.hasNext())
728      {
729        buffer.append(", ");
730      }
731    }
732    buffer.append("}, warnings={ ");
733
734    final Iterator<PasswordPolicyStateAccountUsabilityWarning> warningIterator =
735         warnings.iterator();
736    while (warningIterator.hasNext())
737    {
738      buffer.append(warningIterator.next().toString());
739      if (warningIterator.hasNext())
740      {
741        buffer.append(", ");
742      }
743    }
744    buffer.append("}, errors={ ");
745
746    final Iterator<PasswordPolicyStateAccountUsabilityError> errorIterator =
747         errors.iterator();
748    while (errorIterator.hasNext())
749    {
750      buffer.append(errorIterator.next().toString());
751      if (errorIterator.hasNext())
752      {
753        buffer.append(", ");
754      }
755    }
756    buffer.append('}');
757
758    if (authFailureReason != null)
759    {
760      buffer.append(", authFailureReason=");
761      buffer.append(authFailureReason.toString());
762    }
763
764    buffer.append(')');
765  }
766}