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}