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}