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