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 Alcatel-Lucent 8661
064 *   server products.  These classes provide support for proprietary
065 *   functionality or for external specifications that are not considered stable
066 *   or mature enough to be guaranteed to work in an interoperable way with
067 *   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(
232           new ArrayList<PasswordPolicyStateAccountUsabilityNotice>(notices));
233    }
234
235    if (warnings == null)
236    {
237      this.warnings = Collections.emptyList();
238    }
239    else
240    {
241      this.warnings = Collections.unmodifiableList(
242           new ArrayList<PasswordPolicyStateAccountUsabilityWarning>(warnings));
243    }
244
245    if (errors == null)
246    {
247      this.errors = Collections.emptyList();
248    }
249    else
250    {
251      this.errors = Collections.unmodifiableList(
252           new ArrayList<PasswordPolicyStateAccountUsabilityError>(errors));
253    }
254  }
255
256
257
258  /**
259   * Creates a new instance of this control that is decoded from the provided
260   * generic control.
261   *
262   * @param  oid         The OID for the control.
263   * @param  isCritical  Indicates whether this control should be marked
264   *                     critical.
265   * @param  value       The encoded value for the control.
266   *
267   * @throws LDAPException  If a problem is encountered while attempting to
268   *                         decode the provided control as a get password
269   *                         policy state issues response control.
270   */
271  public GetPasswordPolicyStateIssuesResponseControl(final String oid,
272              final boolean isCritical, final ASN1OctetString value)
273         throws LDAPException
274  {
275    super(oid, isCritical, value);
276
277    if (value == null)
278    {
279      throw new LDAPException(ResultCode.DECODING_ERROR,
280           ERR_GET_PWP_STATE_ISSUES_RESPONSE_NO_VALUE.get());
281    }
282
283    AuthenticationFailureReason afr = null;
284    List<PasswordPolicyStateAccountUsabilityNotice> nList =
285         Collections.emptyList();
286    List<PasswordPolicyStateAccountUsabilityWarning> wList =
287         Collections.emptyList();
288    List<PasswordPolicyStateAccountUsabilityError> eList =
289         Collections.emptyList();
290
291    try
292    {
293      for (final ASN1Element e :
294           ASN1Sequence.decodeAsSequence(value.getValue()).elements())
295      {
296        switch (e.getType())
297        {
298          case TYPE_NOTICES:
299            nList =
300                 new ArrayList<PasswordPolicyStateAccountUsabilityNotice>(10);
301            for (final ASN1Element ne :
302                 ASN1Sequence.decodeAsSequence(e).elements())
303            {
304              final ASN1Element[] noticeElements =
305                   ASN1Sequence.decodeAsSequence(ne).elements();
306              final int type = ASN1Integer.decodeAsInteger(
307                   noticeElements[0]).intValue();
308              final String name = ASN1OctetString.decodeAsOctetString(
309                   noticeElements[1]).stringValue();
310
311              final String message;
312              if (noticeElements.length == 3)
313              {
314                message = ASN1OctetString.decodeAsOctetString(
315                     noticeElements[2]).stringValue();
316              }
317              else
318              {
319                message = null;
320              }
321
322              nList.add(new PasswordPolicyStateAccountUsabilityNotice(type,
323                   name, message));
324            }
325            nList = Collections.unmodifiableList(nList);
326            break;
327
328          case TYPE_WARNINGS:
329            wList =
330                 new ArrayList<PasswordPolicyStateAccountUsabilityWarning>(10);
331            for (final ASN1Element we :
332                 ASN1Sequence.decodeAsSequence(e).elements())
333            {
334              final ASN1Element[] warningElements =
335                   ASN1Sequence.decodeAsSequence(we).elements();
336              final int type = ASN1Integer.decodeAsInteger(
337                   warningElements[0]).intValue();
338              final String name = ASN1OctetString.decodeAsOctetString(
339                   warningElements[1]).stringValue();
340
341              final String message;
342              if (warningElements.length == 3)
343              {
344                message = ASN1OctetString.decodeAsOctetString(
345                     warningElements[2]).stringValue();
346              }
347              else
348              {
349                message = null;
350              }
351
352              wList.add(new PasswordPolicyStateAccountUsabilityWarning(type,
353                   name, message));
354            }
355            wList = Collections.unmodifiableList(wList);
356            break;
357
358          case TYPE_ERRORS:
359            eList =
360                 new ArrayList<PasswordPolicyStateAccountUsabilityError>(10);
361            for (final ASN1Element ee :
362                 ASN1Sequence.decodeAsSequence(e).elements())
363            {
364              final ASN1Element[] errorElements =
365                   ASN1Sequence.decodeAsSequence(ee).elements();
366              final int type = ASN1Integer.decodeAsInteger(
367                   errorElements[0]).intValue();
368              final String name = ASN1OctetString.decodeAsOctetString(
369                   errorElements[1]).stringValue();
370
371              final String message;
372              if (errorElements.length == 3)
373              {
374                message = ASN1OctetString.decodeAsOctetString(
375                     errorElements[2]).stringValue();
376              }
377              else
378              {
379                message = null;
380              }
381
382              eList.add(new PasswordPolicyStateAccountUsabilityError(type,
383                   name, message));
384            }
385            eList = Collections.unmodifiableList(eList);
386            break;
387
388          case TYPE_AUTH_FAILURE_REASON:
389            final ASN1Element[] afrElements =
390                 ASN1Sequence.decodeAsSequence(e).elements();
391            final int afrType =
392                 ASN1Integer.decodeAsInteger(afrElements[0]).intValue();
393            final String afrName = ASN1OctetString.decodeAsOctetString(
394                 afrElements[1]).stringValue();
395
396            final String afrMessage;
397            if (afrElements.length == 3)
398            {
399              afrMessage = ASN1OctetString.decodeAsOctetString(
400                   afrElements[2]).stringValue();
401            }
402            else
403            {
404              afrMessage = null;
405            }
406            afr = new AuthenticationFailureReason(afrType, afrName, afrMessage);
407            break;
408
409          default:
410            throw new LDAPException(ResultCode.DECODING_ERROR,
411                 ERR_GET_PWP_STATE_ISSUES_RESPONSE_UNEXPECTED_TYPE.get(
412                      StaticUtils.toHex(e.getType())));
413        }
414      }
415    }
416    catch (final LDAPException le)
417    {
418      Debug.debugException(le);
419
420      throw le;
421    }
422    catch (final Exception e)
423    {
424      Debug.debugException(e);
425
426      throw new LDAPException(ResultCode.DECODING_ERROR,
427           ERR_GET_PWP_STATE_ISSUES_RESPONSE_CANNOT_DECODE.get(
428                StaticUtils.getExceptionMessage(e)),
429           e);
430    }
431
432    authFailureReason = afr;
433    notices           = nList;
434    warnings          = wList;
435    errors            = eList;
436  }
437
438
439
440  /**
441   * Encodes the provided information into an ASN.1 octet string suitable for
442   * use as the value of this control.
443   *
444   * @param  notices            The set of password policy state usability
445   *                            notices to include.  It may be {@code null} or
446   *                            empty if there are no notices.
447   * @param  warnings           The set of password policy state usability
448   *                            warnings to include.  It may be {@code null} or
449   *                            empty if there are no warnings.
450   * @param  errors             The set of password policy state usability
451   *                            errors to include.  It may be {@code null} or
452   *                            empty if there are no errors.
453   * @param  authFailureReason  The authentication failure reason for the bind
454   *                            operation.  It may be {@code null} if there is
455   *                            no authentication failure reason.
456   *
457   * @return  The ASN.1 octet string containing the encoded control value.
458   */
459  private static ASN1OctetString encodeValue(
460              final List<PasswordPolicyStateAccountUsabilityNotice> notices,
461              final List<PasswordPolicyStateAccountUsabilityWarning> warnings,
462              final List<PasswordPolicyStateAccountUsabilityError> errors,
463              final AuthenticationFailureReason authFailureReason)
464  {
465    final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(4);
466    if ((notices != null) && (! notices.isEmpty()))
467    {
468      final ArrayList<ASN1Element> noticeElements =
469           new ArrayList<ASN1Element>(notices.size());
470      for (final PasswordPolicyStateAccountUsabilityNotice n : notices)
471      {
472        if (n.getMessage() == null)
473        {
474          noticeElements.add(new ASN1Sequence(
475               new ASN1Integer(n.getIntValue()),
476               new ASN1OctetString(n.getName())));
477        }
478        else
479        {
480          noticeElements.add(new ASN1Sequence(
481               new ASN1Integer(n.getIntValue()),
482               new ASN1OctetString(n.getName()),
483               new ASN1OctetString(n.getMessage())));
484        }
485      }
486
487      elements.add(new ASN1Sequence(TYPE_NOTICES, noticeElements));
488    }
489
490    if ((warnings != null) && (! warnings.isEmpty()))
491    {
492      final ArrayList<ASN1Element> warningElements =
493           new ArrayList<ASN1Element>(warnings.size());
494      for (final PasswordPolicyStateAccountUsabilityWarning w : warnings)
495      {
496        if (w.getMessage() == null)
497        {
498          warningElements.add(new ASN1Sequence(
499               new ASN1Integer(w.getIntValue()),
500               new ASN1OctetString(w.getName())));
501        }
502        else
503        {
504          warningElements.add(new ASN1Sequence(
505               new ASN1Integer(w.getIntValue()),
506               new ASN1OctetString(w.getName()),
507               new ASN1OctetString(w.getMessage())));
508        }
509      }
510
511      elements.add(new ASN1Sequence(TYPE_WARNINGS, warningElements));
512    }
513
514    if ((errors != null) && (! errors.isEmpty()))
515    {
516      final ArrayList<ASN1Element> errorElements =
517           new ArrayList<ASN1Element>(errors.size());
518      for (final PasswordPolicyStateAccountUsabilityError e : errors)
519      {
520        if (e.getMessage() == null)
521        {
522          errorElements.add(new ASN1Sequence(
523               new ASN1Integer(e.getIntValue()),
524               new ASN1OctetString(e.getName())));
525        }
526        else
527        {
528          errorElements.add(new ASN1Sequence(
529               new ASN1Integer(e.getIntValue()),
530               new ASN1OctetString(e.getName()),
531               new ASN1OctetString(e.getMessage())));
532        }
533      }
534
535      elements.add(new ASN1Sequence(TYPE_ERRORS, errorElements));
536    }
537
538    if (authFailureReason != null)
539    {
540      if (authFailureReason.getMessage() == null)
541      {
542        elements.add(new ASN1Sequence(TYPE_AUTH_FAILURE_REASON,
543             new ASN1Integer(authFailureReason.getIntValue()),
544             new ASN1OctetString(authFailureReason.getName())));
545      }
546      else
547      {
548        elements.add(new ASN1Sequence(TYPE_AUTH_FAILURE_REASON,
549             new ASN1Integer(authFailureReason.getIntValue()),
550             new ASN1OctetString(authFailureReason.getName()),
551             new ASN1OctetString(authFailureReason.getMessage())));
552      }
553    }
554
555    return new ASN1OctetString(new ASN1Sequence(elements).encode());
556  }
557
558
559
560  /**
561   * {@inheritDoc}
562   */
563  @Override()
564  public GetPasswordPolicyStateIssuesResponseControl decodeControl(
565              final String oid, final boolean isCritical,
566              final ASN1OctetString value)
567          throws LDAPException
568  {
569    return new GetPasswordPolicyStateIssuesResponseControl(oid, isCritical,
570         value);
571  }
572
573
574
575  /**
576   * Retrieves the set of account usability notices for the user.
577   *
578   * @return  The set of account usability notices for the user, or an empty
579   *          list if there are no notices.
580   */
581  public List<PasswordPolicyStateAccountUsabilityNotice> getNotices()
582  {
583    return notices;
584  }
585
586
587
588  /**
589   * Retrieves the set of account usability warnings for the user.
590   *
591   * @return  The set of account usability warnings for the user, or an empty
592   *          list if there are no warnings.
593   */
594  public List<PasswordPolicyStateAccountUsabilityWarning> getWarnings()
595  {
596    return warnings;
597  }
598
599
600
601  /**
602   * Retrieves the set of account usability errors for the user.
603   *
604   * @return  The set of account usability errors for the user, or an empty
605   *          list if there are no errors.
606   */
607  public List<PasswordPolicyStateAccountUsabilityError> getErrors()
608  {
609    return errors;
610  }
611
612
613
614  /**
615   * Retrieves the authentication failure reason for the bind operation, if
616   * available.
617   *
618   * @return  The authentication failure reason for the bind operation, or
619   *          {@code null} if none was provided.
620   */
621  public AuthenticationFailureReason getAuthenticationFailureReason()
622  {
623    return authFailureReason;
624  }
625
626
627
628  /**
629   * Extracts a get password policy state issues response control from the
630   * provided bind result.
631   *
632   * @param  bindResult  The bind result from which to retrieve the get password
633   *                     policy state issues response control.
634   *
635   * @return  The get password policy state issues response control contained in
636   *          the provided bind result, or {@code null} if the bind result did
637   *          not contain a get password policy state issues response control.
638   *
639   * @throws  LDAPException  If a problem is encountered while attempting to
640   *                         decode the get password policy state issues
641   *                         response control contained in the provided bind
642   *                         result.
643   */
644  public static GetPasswordPolicyStateIssuesResponseControl get(
645                     final BindResult bindResult)
646         throws LDAPException
647  {
648    final Control c = bindResult.getResponseControl(
649         GET_PASSWORD_POLICY_STATE_ISSUES_RESPONSE_OID);
650    if (c == null)
651    {
652      return null;
653    }
654
655    if (c instanceof GetPasswordPolicyStateIssuesResponseControl)
656    {
657      return (GetPasswordPolicyStateIssuesResponseControl) c;
658    }
659    else
660    {
661      return new GetPasswordPolicyStateIssuesResponseControl(c.getOID(),
662           c.isCritical(), c.getValue());
663    }
664  }
665
666
667
668  /**
669   * Extracts a get password policy state issues response control from the
670   * provided LDAP exception.
671   *
672   * @param  ldapException  The LDAP exception from which to retrieve the get
673   *                        password policy state issues response control.
674   *
675   * @return  The get password policy state issues response control contained in
676   *          the provided LDAP exception, or {@code null} if the exception did
677   *          not contain a get password policy state issues response control.
678   *
679   * @throws  LDAPException  If a problem is encountered while attempting to
680   *                         decode the get password policy state issues
681   *                         response control contained in the provided LDAP
682   *                         exception.
683   */
684  public static GetPasswordPolicyStateIssuesResponseControl get(
685                     final LDAPException ldapException)
686         throws LDAPException
687  {
688    final Control c = ldapException.getResponseControl(
689         GET_PASSWORD_POLICY_STATE_ISSUES_RESPONSE_OID);
690    if (c == null)
691    {
692      return null;
693    }
694
695    if (c instanceof GetPasswordPolicyStateIssuesResponseControl)
696    {
697      return (GetPasswordPolicyStateIssuesResponseControl) c;
698    }
699    else
700    {
701      return new GetPasswordPolicyStateIssuesResponseControl(c.getOID(),
702           c.isCritical(), c.getValue());
703    }
704  }
705
706
707
708  /**
709   * {@inheritDoc}
710   */
711  @Override()
712  public String getControlName()
713  {
714    return INFO_CONTROL_NAME_GET_PWP_STATE_ISSUES_RESPONSE.get();
715  }
716
717
718
719  /**
720   * {@inheritDoc}
721   */
722  @Override()
723  public void toString(final StringBuilder buffer)
724  {
725    buffer.append("GetPasswordPolicyStateIssuesResponseControl(notices={ ");
726
727    final Iterator<PasswordPolicyStateAccountUsabilityNotice> noticeIterator =
728         notices.iterator();
729    while (noticeIterator.hasNext())
730    {
731      buffer.append(noticeIterator.next().toString());
732      if (noticeIterator.hasNext())
733      {
734        buffer.append(", ");
735      }
736    }
737    buffer.append("}, warnings={ ");
738
739    final Iterator<PasswordPolicyStateAccountUsabilityWarning> warningIterator =
740         warnings.iterator();
741    while (warningIterator.hasNext())
742    {
743      buffer.append(warningIterator.next().toString());
744      if (warningIterator.hasNext())
745      {
746        buffer.append(", ");
747      }
748    }
749    buffer.append("}, errors={ ");
750
751    final Iterator<PasswordPolicyStateAccountUsabilityError> errorIterator =
752         errors.iterator();
753    while (errorIterator.hasNext())
754    {
755      buffer.append(errorIterator.next().toString());
756      if (errorIterator.hasNext())
757      {
758        buffer.append(", ");
759      }
760    }
761    buffer.append('}');
762
763    if (authFailureReason != null)
764    {
765      buffer.append(", authFailureReason=");
766      buffer.append(authFailureReason.toString());
767    }
768
769    buffer.append(')');
770  }
771}