001/*
002 * Copyright 2020-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2020-2022 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) 2020-2022 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;
037
038
039
040import java.util.ArrayList;
041import java.util.Collections;
042import java.util.Iterator;
043import java.util.LinkedHashMap;
044import java.util.List;
045import java.util.Map;
046
047import com.unboundid.asn1.ASN1OctetString;
048import com.unboundid.util.ByteStringBuffer;
049import com.unboundid.util.Debug;
050import com.unboundid.util.NotMutable;
051import com.unboundid.util.NotNull;
052import com.unboundid.util.Nullable;
053import com.unboundid.util.StaticUtils;
054import com.unboundid.util.ThreadSafety;
055import com.unboundid.util.ThreadSafetyLevel;
056import com.unboundid.util.Validator;
057
058
059
060/**
061 * This class provides an implementation of a SASL bind request that uses the
062 * OAUTHBEARER SASL mechanism described in
063 * <A HREF="http://www.ietf.org/rfc/rfc7628.txt">RFC 7628</A> to allow a user
064 * to authenticate with an OAuth 2.0 bearer token.
065 *
066 * @see  OAUTHBEARERBindRequestProperties
067 * @see  OAUTHBEARERBindResult
068 */
069@NotMutable()
070@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
071public final class OAUTHBEARERBindRequest
072       extends SASLBindRequest
073{
074  /**
075   * The name for the OAUTHBEARER SASL mechanism.
076   */
077  @NotNull public static final String OAUTHBEARER_MECHANISM_NAME =
078       "OAUTHBEARER";
079
080
081
082  /**
083   * The delimiter that appears between elements of the GS2 header.
084   */
085  private static final byte GS2_HEADER_DELIMITER = ',';
086
087
088
089  /**
090   * The delimiter that appears after each element of the encoded credentials.
091   */
092  private static final byte OAUTHBEARER_DELIMITER = (byte) 0x01;
093
094
095
096  /**
097   * The component of the GS2 header that indicates that channel binding is not
098   * supported.
099   */
100  @NotNull private static final byte[] GS2_HEADER_ELEMENT_NO_CHANNEL_BINDING =
101       StaticUtils.getBytes("n");
102
103
104
105  /**
106   * The component of the GS2 header that precedes the authorization ID.
107   */
108  @NotNull private static final byte[] GS2_HEADER_ELEMENT_AUTHZ_ID_PREFIX =
109       StaticUtils.getBytes("a=");
110
111
112
113  /**
114   * The component of the OAUTHBEARER bind request credentials that precedes the
115   * access token.
116   */
117  @NotNull private static final byte[]
118       OAUTHBEARER_CRED_ELEMENT_ACCESS_TOKEN_PREFIX =
119            StaticUtils.getBytes("auth=Bearer ");
120
121
122
123  /**
124   * The component of the OAUTHBEARER bind request credentials that precedes the
125   * server address.
126   */
127  @NotNull private static final byte[]
128       OAUTHBEARER_CRED_ELEMENT_SERVER_ADDRESS_PREFIX =
129            StaticUtils.getBytes("host=");
130
131
132
133  /**
134   * The component of the OAUTHBEARER bind request credentials that precedes the
135   * server port.
136   */
137  @NotNull private static final byte[]
138       OAUTHBEARER_CRED_ELEMENT_SERVER_PORT_PREFIX =
139            StaticUtils.getBytes("port=");
140
141
142
143  /**
144   * The component of the OAUTHBEARER bind request credentials that precedes the
145   * request method.
146   */
147  @NotNull private static final byte[]
148       OAUTHBEARER_CRED_ELEMENT_REQUEST_METHOD_PREFIX =
149            StaticUtils.getBytes("mthd=");
150
151
152
153  /**
154   * The component of the OAUTHBEARER bind request credentials that precedes the
155   * request path.
156   */
157  @NotNull private static final byte[]
158       OAUTHBEARER_CRED_ELEMENT_REQUEST_PATH_PREFIX =
159            StaticUtils.getBytes("path=");
160
161
162
163  /**
164   * The component of the OAUTHBEARER bind request credentials that precedes the
165   * request post data.
166   */
167  @NotNull private static final byte[]
168       OAUTHBEARER_CRED_ELEMENT_REQUEST_POST_DATA_PREFIX =
169            StaticUtils.getBytes("post=");
170
171
172
173  /**
174   * The component of the OAUTHBEARER bind request credentials that precedes the
175   * request query string.
176   */
177  @NotNull private static final byte[]
178       OAUTHBEARER_CRED_ELEMENT_REQUEST_QUERY_STRING_PREFIX =
179            StaticUtils.getBytes("qs=");
180
181
182
183  /**
184   * The SASL credentials that should be included in the dummy bind request that
185   * is used to complete a failed authentication attempt.
186   */
187  @NotNull private static final ASN1OctetString DUMMY_REQUEST_CREDENTIALS =
188       new ASN1OctetString(new byte[] { OAUTHBEARER_DELIMITER });
189
190
191
192  /**
193   * The serial version UID for this serializable class.
194   */
195  private static final long serialVersionUID = -1216152242833705618L;
196
197
198
199  // The message ID from the last LDAP message sent from this request.
200  private volatile int messageID;
201
202  // The port of the server to which the request will be sent.
203  @Nullable private final Integer serverPort;
204
205  // A set of additional key-value pairs that should be included in the bind
206  // request.
207  @NotNull private final Map<String,String> additionalKeyValuePairs;
208
209  // The access token to include in the bind request.
210  @NotNull private final String accessToken;
211
212  // The authorization identity to include in the GS2 header for the bind
213  // request.
214  @Nullable private final String authorizationID;
215
216  // The method to use for HTTP-based requests.
217  @Nullable private final String requestMethod;
218
219  // The path to use for HTTP-based requests.
220  @Nullable private final String requestPath;
221
222  // The post data for HTTP-based requests.
223  @Nullable private final String requestPostData;
224
225  // The query string for HTTP-based requests.
226  @Nullable private final String requestQueryString;
227
228  // The address of the server to which the request will be sent.
229  @Nullable private final String serverAddress;
230
231
232
233  /**
234   * Creates a new OAUTHBEARER bind request with the provided access token.
235   * All other properties will be unset.
236   *
237   * @param  accessToken  The access token to use for this bind request.  It
238   *                      must not be {@code null} or empty.
239   * @param  controls     The set of controls to include in the bind request.
240   *                      It may be {@code null} or empty if no controls are
241   *                      needed.
242   */
243  public OAUTHBEARERBindRequest(@NotNull final String accessToken,
244                                @Nullable final Control... controls)
245  {
246    super(controls);
247
248    Validator.ensureNotNullOrEmpty(accessToken,
249         "OAUTHBEARERBindRequest.accessToken must not be null or empty.");
250
251    this.accessToken = accessToken;
252
253    authorizationID = null;
254    serverAddress = null;
255    serverPort = null;
256    requestMethod = null;
257    requestPath = null;
258    requestPostData = null;
259    requestQueryString = null;
260
261    additionalKeyValuePairs = Collections.emptyMap();
262    messageID = -1;
263  }
264
265
266
267  /**
268   * Creates a new OAUTHBEARER bind request with the provided set of properties.
269   *
270   * @param  properties  The set of properties to use to create this bind
271   *                     request.  It must not be {@code null}.
272   * @param  controls    The set of controls to include in the bind request.  It
273   *                     may be {@code null} or empty if no controls are needed.
274   */
275  public OAUTHBEARERBindRequest(
276              @NotNull final OAUTHBEARERBindRequestProperties properties,
277              @Nullable final Control... controls)
278  {
279    super(controls);
280
281    accessToken = properties.getAccessToken();
282    authorizationID = properties.getAuthorizationID();
283    serverAddress = properties.getServerAddress();
284    serverPort = properties.getServerPort();
285    requestMethod = properties.getRequestMethod();
286    requestPath = properties.getRequestPath();
287    requestPostData = properties.getRequestPostData();
288    requestQueryString = properties.getRequestQueryString();
289
290    additionalKeyValuePairs = Collections.unmodifiableMap(
291         new LinkedHashMap<>(properties.getAdditionalKeyValuePairs()));
292
293    messageID = -1;
294  }
295
296
297
298  /**
299   * {@inheritDoc}
300   */
301  @Override()
302  @NotNull()
303  public String getSASLMechanismName()
304  {
305    return OAUTHBEARER_MECHANISM_NAME;
306  }
307
308
309
310  /**
311   * Retrieves the access token to include in the bind request.
312   *
313   * @return  The access token to include in the bind request.
314   */
315  @NotNull()
316  public String getAccessToken()
317  {
318    return accessToken;
319  }
320
321
322
323  /**
324   * Retrieves the authorization ID to include in the GS2 header for the bind
325   * request, if any.
326   *
327   * @return  The authorization ID to include in the GS2 header for the bind
328   *          request, or {@code null} if no authorization ID should be
329   *          included.
330   */
331  @Nullable()
332  public String getAuthorizationID()
333  {
334    return authorizationID;
335  }
336
337
338
339  /**
340   * Retrieves the server address to include in the bind request, if any.
341   *
342   * @return  The server address to include in the bind request, or {@code null}
343   *          if it should be omitted.
344   */
345  @Nullable()
346  public String getServerAddress()
347  {
348    return serverAddress;
349  }
350
351
352
353  /**
354   * Retrieves the server port to include in the bind request, if any.
355   *
356   * @return  The server port to include in the bind request, or {@code null}
357   *          if it should be omitted.
358   */
359  @Nullable()
360  public Integer getServerPort()
361  {
362    return serverPort;
363  }
364
365
366
367  /**
368   * Retrieves the method to use for HTTP-based requests, if any.
369   *
370   * @return  The method to use for HTTP-based requests, or {@code null} if it
371   *          should be omitted from the bind request.
372   */
373  @Nullable()
374  public String getRequestMethod()
375  {
376    return requestMethod;
377  }
378
379
380
381  /**
382   * Retrieves the path to use for HTTP-based requests, if any.
383   *
384   * @return  The path to use for HTTP-based requests, or {@code null} if it
385   *          should be omitted from the bind request.
386   */
387  @Nullable()
388  public String getRequestPath()
389  {
390    return requestPath;
391  }
392
393
394
395  /**
396   * Retrieves the data to submit when posting an HTTP-based request, if any.
397   *
398   * @return  The post data for HTTP-based requests, or {@code null} if it
399   *          should be omitted from the bind request.
400   */
401  @Nullable()
402  public String getRequestPostData()
403  {
404    return requestPostData;
405  }
406
407
408
409  /**
410   * Retrieves the query string to use for HTTP-based requests, if any.
411   *
412   * @return  The query string to use for HTTP-based requests, or {@code null}
413   *          if it should be omitted from the bind request.
414   */
415  @Nullable()
416  public String getRequestQueryString()
417  {
418    return requestQueryString;
419  }
420
421
422
423  /**
424   * Retrieves an unmodifiable map of additional key-value pairs that should be
425   * included in the bind request.
426   *
427   * @return  An unmodifiable map of additional key-value pairs that should be
428   *          included in the bind request.  It will not be {@code null} but may
429   *          be empty.
430   */
431  @NotNull()
432  public Map<String,String> getAdditionalKeyValuePairs()
433  {
434    return additionalKeyValuePairs;
435  }
436
437
438
439  /**
440   * {@inheritDoc}
441   */
442  @Override()
443  @NotNull()
444  protected OAUTHBEARERBindResult process(
445                 @NotNull final LDAPConnection connection, final int depth)
446            throws LDAPException
447  {
448    // Send the initial request.  If the response has a result code that is
449    // anything other than SASL_BIND_IN_PROGRESS, then we can just return it
450    // directly without needing to do anything else.
451    messageID = InternalSDKHelper.nextMessageID(connection);
452    final BindResult initialBindResult =  sendBindRequest(connection, "",
453         encodeCredentials(), getControls(),
454         getResponseTimeoutMillis(connection));
455    if (initialBindResult.getResultCode() != ResultCode.SASL_BIND_IN_PROGRESS)
456    {
457      return new OAUTHBEARERBindResult(initialBindResult);
458    }
459
460
461    // If we've gotten here, then it indicates that the attempt failed.  We need
462    // to send a second, dummy request to complete the bind process and get the
463    // ultimate failure result.
464    BindResult finalBindResult;
465    try
466    {
467      messageID = InternalSDKHelper.nextMessageID(connection);
468      finalBindResult = sendBindRequest(connection, "",
469           DUMMY_REQUEST_CREDENTIALS, getControls(),
470           getResponseTimeoutMillis(connection));
471    }
472    catch (final LDAPException e)
473    {
474      Debug.debugException(e);
475      finalBindResult = new BindResult(e);
476    }
477
478    return new OAUTHBEARERBindResult(initialBindResult, finalBindResult);
479  }
480
481
482
483  /**
484   * Encodes the credentials as appropriate for this bind request.
485   *
486   * @return  An ASN.1 octet string containing the encoded credentials.
487   */
488  @NotNull()
489  ASN1OctetString encodeCredentials()
490  {
491    final ByteStringBuffer buffer = new ByteStringBuffer();
492
493    // Construct the GS2 header and follow it with the necessary delimiter.
494    buffer.append(GS2_HEADER_ELEMENT_NO_CHANNEL_BINDING);
495    buffer.append(GS2_HEADER_DELIMITER);
496
497    if (authorizationID != null)
498    {
499      buffer.append(GS2_HEADER_ELEMENT_AUTHZ_ID_PREFIX);
500      escapeAuthorizationID(authorizationID, buffer);
501    }
502
503    buffer.append(GS2_HEADER_DELIMITER);
504    buffer.append(OAUTHBEARER_DELIMITER);
505
506
507    // Append the access token.
508    buffer.append(OAUTHBEARER_CRED_ELEMENT_ACCESS_TOKEN_PREFIX);
509    buffer.append(accessToken);
510    buffer.append(OAUTHBEARER_DELIMITER);
511
512
513    // Append the server address, if appropriate.
514    if (serverAddress != null)
515    {
516      buffer.append(OAUTHBEARER_CRED_ELEMENT_SERVER_ADDRESS_PREFIX);
517      buffer.append(serverAddress);
518      buffer.append(OAUTHBEARER_DELIMITER);
519    }
520
521
522    // Append the server port, if appropriate.
523    if (serverPort != null)
524    {
525      buffer.append(OAUTHBEARER_CRED_ELEMENT_SERVER_PORT_PREFIX);
526      buffer.append(serverPort.toString());
527      buffer.append(OAUTHBEARER_DELIMITER);
528    }
529
530
531    // Append the request method, if appropriate.
532    if (requestMethod != null)
533    {
534      buffer.append(OAUTHBEARER_CRED_ELEMENT_REQUEST_METHOD_PREFIX);
535      buffer.append(requestMethod);
536      buffer.append(OAUTHBEARER_DELIMITER);
537    }
538
539
540    // Append the request path, if appropriate.
541    if (requestPath != null)
542    {
543      buffer.append(OAUTHBEARER_CRED_ELEMENT_REQUEST_PATH_PREFIX);
544      buffer.append(requestPath);
545      buffer.append(OAUTHBEARER_DELIMITER);
546    }
547
548
549    // Append the request post data, if appropriate.
550    if (requestPostData != null)
551    {
552      buffer.append(OAUTHBEARER_CRED_ELEMENT_REQUEST_POST_DATA_PREFIX);
553      buffer.append(requestPostData);
554      buffer.append(OAUTHBEARER_DELIMITER);
555    }
556
557
558    // Append the request query string, if appropriate.
559    if (requestQueryString != null)
560    {
561      buffer.append(OAUTHBEARER_CRED_ELEMENT_REQUEST_QUERY_STRING_PREFIX);
562      buffer.append(requestQueryString);
563      buffer.append(OAUTHBEARER_DELIMITER);
564    }
565
566    // Append any additional key-value pairs.
567    for (final Map.Entry<String,String> e : additionalKeyValuePairs.entrySet())
568    {
569      buffer.append(e.getKey());
570      buffer.append('=');
571      buffer.append(e.getValue());
572      buffer.append(OAUTHBEARER_DELIMITER);
573    }
574
575    return new ASN1OctetString(buffer.toByteArray());
576  }
577
578
579
580  /**
581   * Appends an escaped version of the provided authorization ID to the given
582   * buffer.  Any equal signs will be replaced with "=3D" and any commas will be
583   * replaced with "=2C".
584   *
585   * @param  authorizationID  The authorization ID to be escaped.
586   * @param  buffer           The buffer to which the escaped authorization ID
587   *                          should be appended.
588   */
589  private static void escapeAuthorizationID(
590               @NotNull final String authorizationID,
591               @NotNull final ByteStringBuffer buffer)
592  {
593    final int length = authorizationID.length();
594    for (int i=0; i < length; i++)
595    {
596      final char c = authorizationID.charAt(i);
597      switch (c)
598      {
599        case ',':
600          buffer.append("=2C");
601          break;
602        case '=':
603          buffer.append("=3D");
604          break;
605        default:
606          buffer.append(c);
607          break;
608      }
609    }
610  }
611
612
613
614  /**
615   * {@inheritDoc}
616   */
617  @Override()
618  @NotNull()
619  public OAUTHBEARERBindRequest duplicate()
620  {
621    return duplicate(getControls());
622  }
623
624
625
626  /**
627   * {@inheritDoc}
628   */
629  @Override()
630  @NotNull()
631  public OAUTHBEARERBindRequest duplicate(@Nullable final Control[] controls)
632  {
633    final OAUTHBEARERBindRequestProperties properties =
634         new OAUTHBEARERBindRequestProperties(this);
635    final OAUTHBEARERBindRequest bindRequest =
636         new OAUTHBEARERBindRequest(properties, controls);
637    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
638    return bindRequest;
639  }
640
641
642
643  /**
644   * {@inheritDoc}
645   */
646  @Override()
647  public int getLastMessageID()
648  {
649    return messageID;
650  }
651
652
653
654  /**
655   * Retrieves a string representation of the OAUTHBEARER bind request.
656   *
657   * @return  A string representation of the OAUTHBEARER bind request.
658   */
659  @Override()
660  @NotNull()
661  public String toString()
662  {
663    final StringBuilder buffer = new StringBuilder();
664    toString(buffer);
665    return buffer.toString();
666  }
667
668
669
670  /**
671   * Appends a string representation of the OAUTHBEARER bind request to the
672   * provided buffer.
673   *
674   * @param  buffer  The buffer to which the information should be appended.  It
675   *                 must not be {@code null}.
676   */
677  @Override()
678  public void toString(@NotNull final StringBuilder buffer)
679  {
680    buffer.append("OAUTHBEARERBindRequest(accessToken='{redacted}'");
681
682    if (authorizationID != null)
683    {
684      buffer.append(", authorizationID='");
685      buffer.append(authorizationID);
686      buffer.append('\'');
687    }
688
689    if (serverAddress != null)
690    {
691      buffer.append(", serverAddress='");
692      buffer.append(serverAddress);
693      buffer.append('\'');
694    }
695
696    if (serverPort != null)
697    {
698      buffer.append(", serverPort=");
699      buffer.append(serverPort);
700    }
701
702    if (requestMethod != null)
703    {
704      buffer.append(", requestMethod='");
705      buffer.append(requestMethod);
706      buffer.append('\'');
707    }
708
709    if (requestPath != null)
710    {
711      buffer.append(", requestPath='");
712      buffer.append(requestPath);
713      buffer.append('\'');
714    }
715
716    if (requestPostData != null)
717    {
718      buffer.append(", requestPostData='{redacted}'");
719    }
720
721    if (requestQueryString != null)
722    {
723      buffer.append(", requestQueryString='");
724      buffer.append(requestQueryString);
725      buffer.append('\'');
726    }
727
728    if (! additionalKeyValuePairs.isEmpty())
729    {
730      buffer.append(", additionalKeyValuePairs=[");
731
732      final Iterator<Map.Entry<String,String>> iterator =
733           additionalKeyValuePairs.entrySet().iterator();
734      while (iterator.hasNext())
735      {
736        final Map.Entry<String,String> e = iterator.next();
737        buffer.append(" \"");
738        buffer.append(e.getKey());
739        buffer.append("\"=\"");
740        buffer.append(e.getValue());
741        buffer.append('"');
742
743        if (iterator.hasNext())
744        {
745          buffer.append(',');
746        }
747      }
748
749      buffer.append(" ]");
750    }
751
752    buffer.append(')');
753  }
754
755
756
757  /**
758   * {@inheritDoc}
759   */
760  @Override()
761  public void toCode(@NotNull final List<String> lineList,
762                     @NotNull final String requestID,
763                     final int indentSpaces, final boolean includeProcessing)
764  {
765    // Create and update the request properties object.
766    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
767         "OAUTHBEARERBindRequestProperties", requestID + "RequestProperties",
768         "new OAUTHBEARERBindRequestProperties",
769         ToCodeArgHelper.createString(accessToken, "Access Token"));
770
771    if (authorizationID != null)
772    {
773      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
774           requestID + "RequestProperties.setAuthorizationID",
775           ToCodeArgHelper.createString(authorizationID, null));
776    }
777
778    if (serverAddress != null)
779    {
780      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
781           requestID + "RequestProperties.setServerAddress",
782           ToCodeArgHelper.createString(serverAddress, null));
783    }
784
785    if (serverPort != null)
786    {
787      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
788           requestID + "RequestProperties.setServerPort",
789           ToCodeArgHelper.createInteger(serverPort, null));
790    }
791
792    if (requestMethod != null)
793    {
794      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
795           requestID + "RequestProperties.setRequestMethod",
796           ToCodeArgHelper.createString(requestMethod, null));
797    }
798
799    if (requestPath != null)
800    {
801      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
802           requestID + "RequestProperties.setRequestPath",
803           ToCodeArgHelper.createString(requestPath, null));
804    }
805
806    if (requestPostData != null)
807    {
808      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
809           requestID + "RequestProperties.setRequestPostData",
810           ToCodeArgHelper.createString(requestPostData, null));
811    }
812
813    if (requestQueryString != null)
814    {
815      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
816           requestID + "RequestProperties.setRequestQueryString",
817           ToCodeArgHelper.createString(requestQueryString, null));
818    }
819
820    for (final Map.Entry<String,String> e : additionalKeyValuePairs.entrySet())
821    {
822      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
823           requestID + "RequestProperties.addKeyValuePair",
824           ToCodeArgHelper.createString(e.getKey(), null),
825           ToCodeArgHelper.createString(e.getValue(), null));
826    }
827
828
829    // Create the request variable.
830    final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(2);
831    constructorArgs.add(
832         ToCodeArgHelper.createRaw(requestID + "RequestProperties", null));
833
834    final Control[] controls = getControls();
835    if (controls.length > 0)
836    {
837      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
838           "Bind Controls"));
839    }
840
841    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
842         "OAUTHBEARERBindRequest", requestID + "Request",
843         "new OAUTHBEARERBindRequest", constructorArgs);
844
845
846    // Add lines for processing the request and obtaining the result.
847    if (includeProcessing)
848    {
849      // Generate a string with the appropriate indent.
850      final StringBuilder buffer = new StringBuilder();
851      for (int i=0; i < indentSpaces; i++)
852      {
853        buffer.append(' ');
854      }
855      final String indent = buffer.toString();
856
857      lineList.add("");
858      lineList.add(indent + "try");
859      lineList.add(indent + '{');
860      lineList.add(indent + "  BindResult " + requestID +
861           "Result = connection.bind(" + requestID + "Request);");
862      lineList.add(indent + "  // The bind was processed successfully.");
863      lineList.add(indent + '}');
864      lineList.add(indent + "catch (LDAPException e)");
865      lineList.add(indent + '{');
866      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
867           "help explain why.");
868      lineList.add(indent + "  // Note that the connection is now likely in " +
869           "an unauthenticated state.");
870      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
871      lineList.add(indent + "  String message = e.getMessage();");
872      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
873      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
874      lineList.add(indent + "  Control[] responseControls = " +
875           "e.getResponseControls();");
876
877      lineList.add("");
878      lineList.add("OAUTHBEARERBindResult bindResult = " +
879                "new OAUTHBEARERBindResult(new BindResult(e));");
880      lineList.add("String authorizationErrorCode = " +
881           "bindResult.getAuthorizationErrorCode();");
882      lineList.add("Set<String> scopes = bindResult.getScopes();");
883      lineList.add("String openIDConfigurationURL = " +
884           "bindResult.getOpenIDConfigurationURL();");
885
886      lineList.add(indent + '}');
887    }
888  }
889}