001/*
002 * Copyright 2016-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2016-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) 2016-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.unboundidds;
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.ASN1Boolean;
048import com.unboundid.asn1.ASN1Element;
049import com.unboundid.asn1.ASN1OctetString;
050import com.unboundid.asn1.ASN1Sequence;
051import com.unboundid.ldap.sdk.BindResult;
052import com.unboundid.ldap.sdk.Control;
053import com.unboundid.ldap.sdk.InternalSDKHelper;
054import com.unboundid.ldap.sdk.LDAPConnection;
055import com.unboundid.ldap.sdk.LDAPException;
056import com.unboundid.ldap.sdk.ResultCode;
057import com.unboundid.ldap.sdk.SASLBindRequest;
058import com.unboundid.ldap.sdk.ToCodeArgHelper;
059import com.unboundid.ldap.sdk.ToCodeHelper;
060import com.unboundid.util.Debug;
061import com.unboundid.util.NotNull;
062import com.unboundid.util.Nullable;
063import com.unboundid.util.StaticUtils;
064import com.unboundid.util.ThreadSafety;
065import com.unboundid.util.ThreadSafetyLevel;
066import com.unboundid.util.Validator;
067
068import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
069
070
071
072/**
073 * This class provides support for an UnboundID-proprietary SASL mechanism that
074 * may be used to indicate that a user has attempted authentication, whether
075 * successfully or not, through some mechanism that is external to the Directory
076 * Server.  If this mechanism is supported in the server, then attempting to
077 * authenticate with it will not change the identity of the client connection,
078 * but will perform additional processing that would normally be completed
079 * during a more traditional authentication attempt.
080 * <BR>
081 * <BLOCKQUOTE>
082 *   <B>NOTE:</B>  This class, and other classes within the
083 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
084 *   supported for use against Ping Identity, UnboundID, and
085 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
086 *   for proprietary functionality or for external specifications that are not
087 *   considered stable or mature enough to be guaranteed to work in an
088 *   interoperable way with other types of LDAP servers.
089 * </BLOCKQUOTE>
090 * <BR>
091 * This SASL bind request has a mechanism of
092 * "UNBOUNDID-EXTERNALLY-PROCESSED-AUTHENTICATION" and must
093 * include SASL credentials with the following encoding:
094 * <PRE>
095 *   ExternallyProcessedAuthenticationCredentials ::= SEQUENCE {
096 *        authenticationID                          [0] OCTET STRING,
097 *        externalMechanismName                     [1] OCTET STRING,
098 *        externalAuthenticationWasSuccessful       [2] BOOLEAN,
099 *        externalAuthenticationFailureReason       [3] OCTET STRING OPTIONAL,
100 *        externalAuthenticationWasPasswordBased    [4] BOOLEAN DEFAULT TRUE,
101 *        externalAuthenticationWasSecure           [5] BOOLEAN DEFAULT FALSE,
102 *        endClientIPAddress                        [6] OCTET STRING OPTIONAL,
103 *        additionalAccessLogProperties             [7] SEQUENCE OF SEQUENCE {
104 *             propertyName      OCTET STRING,
105 *             propertyValue     OCTET STRING } OPTIONAL,
106 *        ... }
107 * </PRE>
108 * <BR><BR>
109 * In the event that the external authentication was considered successful, the
110 * server will ensure that the target user's account is in a usable state and,
111 * if not, will return a failure response.  If the external authentication was
112 * successful and the user's account is usable, then the server will make any
113 * appropriate password policy state updates (e.g., clearing previous
114 * authentication failures, updating the user's last login time and IP address,
115 * etc.) and return a success result.
116 * <BR><BR>
117 * In the event that the external authentication was not considered successful,
118 * the server may also make corresponding password policy state updates (e.g.,
119 * incrementing the number of authentication failures and locking the account if
120 * appropriate) before returning a failure result.
121 */
122@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
123public final class UnboundIDExternallyProcessedAuthenticationBindRequest
124       extends SASLBindRequest
125{
126  /**
127   * The name for the UnboundID externally-processed authentication SASL
128   * mechanism.
129   */
130  @NotNull public static final String
131       UNBOUNDID_EXTERNALLY_PROCESSED_AUTH_MECHANISM_NAME =
132            "UNBOUNDID-EXTERNALLY-PROCESSED-AUTHENTICATION";
133
134
135
136  /**
137   * The BER type for the authenticationID element of the bind request.
138   */
139  private static final byte TYPE_AUTHENTICATION_ID = (byte) 0x80;
140
141
142
143  /**
144   * The BER type for the externalMechanismName element of the bind request.
145   */
146  private static final byte TYPE_EXTERNAL_MECHANISM_NAME = (byte) 0x81;
147
148
149
150  /**
151   * The BER type for the externalAuthenticationWasSuccessful element of the
152   * bind request.
153   */
154  private static final byte TYPE_EXTERNAL_AUTH_WAS_SUCCESSFUL = (byte) 0x82;
155
156
157
158  /**
159   * The BER type for the externalAuthenticationFailureReason element of the
160   * bind request.
161   */
162  private static final byte TYPE_EXTERNAL_AUTH_FAILURE_REASON = (byte) 0x83;
163
164
165
166  /**
167   * The BER type for the externalAuthenticationWasPasswordBased element of the
168   * bind request.
169   */
170  private static final byte TYPE_EXTERNAL_AUTH_WAS_PASSWORD_BASED = (byte) 0x84;
171
172
173
174  /**
175   * The BER type for the externalAuthenticationWasSecure element of the bind
176   * request.
177   */
178  private static final byte TYPE_EXTERNAL_AUTH_WAS_SECURE = (byte) 0x85;
179
180
181
182  /**
183   * The BER type for the endClientIPAddress element of the bind request.
184   */
185  private static final byte TYPE_END_CLIENT_IP_ADDRESS = (byte) 0x86;
186
187
188
189  /**
190   * The BER type for the additionalAccessLogProperties element of the bind
191   * request.
192   */
193  private static final byte TYPE_ADDITIONAL_ACCESS_LOG_PROPERTIES = (byte) 0xA7;
194
195
196
197  /**
198   * The serial version UID for this serializable class.
199   */
200  private static final long serialVersionUID = -4312237491980971019L;
201
202
203
204  // The encoded SASL credentials for this bind request.
205  @Nullable private volatile ASN1OctetString encodedCredentials;
206
207  // Indicates whether the external authentication processing involved a
208  // password.
209  private final boolean externalAuthWasPasswordBased;
210
211  // Indicates whether the external authentication processing is considered to
212  // have been secure.
213  private final boolean externalAuthWasSecure;
214
215  // Indicates whether the external authentication attempt is considered to have
216  // been successful.
217  private final boolean externalAuthWasSuccessful;
218
219  // The message ID from the last LDAP message sent from this request.
220  private volatile int messageID;
221
222  // A map of additional properties that should be recorded in the server's
223  // access log.
224  @NotNull private final Map<String,String> additionalAccessLogProperties;
225
226  // The authentication ID that identifies the user for whom the external
227  // authentication processing was performed.
228  @NotNull private final String authenticationID;
229
230  // The IPv4 or IPv6 address of the end client, if available.
231  @Nullable private final String endClientIPAddress;
232
233  // The reason that the external authentication attempt was considered a
234  // failure.
235  @Nullable private final String externalAuthFailureReason;
236
237  // The name of the mechanism used for the external authentication attempt.
238  @NotNull private final String externalMechanismName;
239
240
241
242  /**
243   * Creates a new UNBOUNDID-EXTERNALLY-PROCESSED-AUTHENTICATION bind request
244   * with the provided information.
245   *
246   * @param  authenticationID               The authentication ID that
247   *                                        identifies the user for whom the
248   *                                        external authentication processing
249   *                                        was performed.  This should be
250   *                                        either "dn:" followed by the DN of
251   *                                        the target user's entry, or "u:"
252   *                                        followed by a username.  This must
253   *                                        not be {@code null}.
254   * @param  externalMechanismName          The name of the mechanism used for
255   *                                        the external authentication attempt.
256   *                                        This must not be {@code null}.
257   * @param  externalAuthWasSuccessful      Indicates whether the external
258   *                                        authentication attempt is considered
259   *                                        to have been successful.
260   * @param  externalAuthFailureReason      The reason that the external
261   *                                        authentication attempt was
262   *                                        considered a failure.  This should
263   *                                        be {@code null} if the external
264   *                                        authentication attempt succeeded,
265   *                                        and may be {@code null} if the
266   *                                        external authentication attempt
267   *                                        failed but no failure reason is
268   *                                        available.
269   * @param  externalAuthWasPasswordBased   Indicates whether the external
270   *                                        authentication processing involved a
271   *                                        password.
272   * @param  externalAuthWasSecure          Indicates whether the external
273   *                                        authentication processing was
274   *                                        considered secure.  A mechanism
275   *                                        should only be considered secure if
276   *                                        all credentials were protected in
277   *                                        all communication.
278   * @param  endClientIPAddress             The IPv4 or IPv6 address of the end
279   *                                        client involved in the external
280   *                                        authentication processing.  This may
281   *                                        be {@code null} if the end client
282   *                                        address is not available.
283   * @param  additionalAccessLogProperties  A map of additional properties that
284   *                                        should be recorded in the server's
285   *                                        access log for the external
286   *                                        authentication attempt.  This may be
287   *                                        {@code null} or empty if no
288   *                                        additional access log properties are
289   *                                        required.
290   * @param  controls                       The set of controls to include in
291   *                                        the request.  It may be {@code null}
292   *                                        or empty if no request controls are
293   *                                        needed.
294   */
295  public UnboundIDExternallyProcessedAuthenticationBindRequest(
296              @NotNull final String authenticationID,
297              @NotNull final String externalMechanismName,
298              final boolean externalAuthWasSuccessful,
299              @Nullable final String externalAuthFailureReason,
300              final boolean externalAuthWasPasswordBased,
301              final boolean externalAuthWasSecure,
302              @Nullable final String endClientIPAddress,
303              @Nullable final Map<String,String> additionalAccessLogProperties,
304              @Nullable final Control... controls)
305  {
306    super(controls);
307
308    Validator.ensureNotNull(authenticationID);
309    Validator.ensureNotNull(externalMechanismName);
310
311    this.authenticationID             = authenticationID;
312    this.externalMechanismName        = externalMechanismName;
313    this.externalAuthWasSuccessful    = externalAuthWasSuccessful;
314    this.externalAuthFailureReason    = externalAuthFailureReason;
315    this.externalAuthWasPasswordBased = externalAuthWasPasswordBased;
316    this.externalAuthWasSecure        = externalAuthWasSecure;
317    this.endClientIPAddress           = endClientIPAddress;
318
319    if (additionalAccessLogProperties == null)
320    {
321      this.additionalAccessLogProperties = Collections.emptyMap();
322    }
323    else
324    {
325      this.additionalAccessLogProperties = Collections.unmodifiableMap(
326           new LinkedHashMap<>(additionalAccessLogProperties));
327    }
328
329    messageID = -1;
330    encodedCredentials = null;
331  }
332
333
334
335  /**
336   * Creates a new UNBOUNDID-EXTERNALLY-PROCESSED-AUTHENTICATION bind request
337   * decoded from the provided information.
338   *
339   * @param  saslCredentials  The encoded SASL credentials to be decoded.  It
340   *                          must not be {@code null}.
341   * @param  controls         The set of controls to include in the request.  It
342   *                          may be {@code null} or empty if no request
343   *                          controls are needed.
344   *
345   * @return  The decoded UNBOUNDID-EXTERNALLY-PROCESSED-AUTHENTICATION bind
346   *          request.
347   *
348   * @throws  LDAPException  If the provided SASL credentials are not valid for
349   *                         am UNBOUNDID-EXTERNALLY-PROCESSED-AUTHENTICATION
350   *                         bind request
351   */
352  @NotNull()
353  public static UnboundIDExternallyProcessedAuthenticationBindRequest
354              decodeSASLCredentials(
355                   @NotNull final ASN1OctetString saslCredentials,
356                   @Nullable final Control... controls)
357         throws LDAPException
358  {
359    Validator.ensureNotNull(saslCredentials);
360
361    boolean passwordBased = true;
362    boolean secure = false;
363    Boolean successful = null;
364    String failureReason = null;
365    String ipAddress = null;
366    String mechanism = null;
367    String authID = null;
368
369    final LinkedHashMap<String,String> logProperties =
370         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
371
372    try
373    {
374      for (final ASN1Element e :
375           ASN1Sequence.decodeAsSequence(saslCredentials.getValue()).elements())
376      {
377        switch (e.getType())
378        {
379          case TYPE_AUTHENTICATION_ID:
380            authID = ASN1OctetString.decodeAsOctetString(e).stringValue();
381            break;
382          case TYPE_EXTERNAL_MECHANISM_NAME:
383            mechanism = ASN1OctetString.decodeAsOctetString(e).stringValue();
384            break;
385          case TYPE_EXTERNAL_AUTH_WAS_SUCCESSFUL:
386            successful = ASN1Boolean.decodeAsBoolean(e).booleanValue();
387            break;
388          case TYPE_EXTERNAL_AUTH_FAILURE_REASON:
389            failureReason =
390                 ASN1OctetString.decodeAsOctetString(e).stringValue();
391            break;
392          case TYPE_EXTERNAL_AUTH_WAS_PASSWORD_BASED:
393            passwordBased = ASN1Boolean.decodeAsBoolean(e).booleanValue();
394            break;
395          case TYPE_EXTERNAL_AUTH_WAS_SECURE:
396            secure = ASN1Boolean.decodeAsBoolean(e).booleanValue();
397            break;
398          case TYPE_END_CLIENT_IP_ADDRESS:
399            ipAddress = ASN1OctetString.decodeAsOctetString(e).stringValue();
400            break;
401          case TYPE_ADDITIONAL_ACCESS_LOG_PROPERTIES:
402            for (final ASN1Element propertiesElement :
403                 ASN1Sequence.decodeAsSequence(e).elements())
404            {
405              final ASN1Element[] logPairElements =
406                   ASN1Sequence.decodeAsSequence(propertiesElement).elements();
407              final String name = ASN1OctetString.decodeAsOctetString(
408                   logPairElements[0]).stringValue();
409              final String value = ASN1OctetString.decodeAsOctetString(
410                   logPairElements[1]).stringValue();
411              logProperties.put(name, value);
412            }
413            break;
414        }
415      }
416    }
417    catch (final Exception e)
418    {
419      Debug.debugException(e);
420      throw new LDAPException(ResultCode.DECODING_ERROR,
421           ERR_EXTERNALLY_PROCESSED_AUTH_CANNOT_DECODE_CREDS.get(
422                UNBOUNDID_EXTERNALLY_PROCESSED_AUTH_MECHANISM_NAME,
423                StaticUtils.getExceptionMessage(e)),
424           e);
425    }
426
427    if (authID == null)
428    {
429      throw new LDAPException(ResultCode.DECODING_ERROR,
430           ERR_EXTERNALLY_PROCESSED_AUTH_NO_AUTH_ID.get(
431                UNBOUNDID_EXTERNALLY_PROCESSED_AUTH_MECHANISM_NAME));
432    }
433
434    if (mechanism == null)
435    {
436      throw new LDAPException(ResultCode.DECODING_ERROR,
437           ERR_EXTERNALLY_PROCESSED_AUTH_NO_MECH.get(
438                UNBOUNDID_EXTERNALLY_PROCESSED_AUTH_MECHANISM_NAME));
439    }
440
441    if (successful == null)
442    {
443      throw new LDAPException(ResultCode.DECODING_ERROR,
444           ERR_EXTERNALLY_PROCESSED_AUTH_NO_WAS_SUCCESSFUL.get(
445                UNBOUNDID_EXTERNALLY_PROCESSED_AUTH_MECHANISM_NAME));
446    }
447
448    final UnboundIDExternallyProcessedAuthenticationBindRequest bindRequest =
449         new UnboundIDExternallyProcessedAuthenticationBindRequest(authID,
450              mechanism, successful, failureReason, passwordBased, secure,
451              ipAddress, logProperties, controls);
452    bindRequest.encodedCredentials = saslCredentials;
453
454    return bindRequest;
455  }
456
457
458
459  /**
460   * Retrieves the authentication ID that identifies the user for whom the
461   * external authentication processing was performed.
462   *
463   * @return  The authentication ID that identifies the user for whom the
464   *          external authentication processing was performed.
465   */
466  @NotNull()
467  public String getAuthenticationID()
468  {
469    return authenticationID;
470  }
471
472
473
474  /**
475   * Retrieves the name of the mechanism used for the external authentication
476   * attempt.
477   *
478   * @return  The name of the mechanism used for the external authentication
479   *          attempt.
480   */
481  @NotNull()
482  public String getExternalMechanismName()
483  {
484    return externalMechanismName;
485  }
486
487
488
489  /**
490   * Indicates whether the external authentication attempt is considered to have
491   * been successful.
492   *
493   * @return  {@code true} if the external authentication attempt was considered
494   *          successful, or {@code false} if not.
495   */
496  public boolean externalAuthenticationWasSuccessful()
497  {
498    return externalAuthWasSuccessful;
499  }
500
501
502
503  /**
504   * Retrieves the reason that the external authentication attempt was
505   * considered a failure, if available.
506   *
507   * @return  The reason that the external authentication attempt was considered
508   *          a failure, or {@code null} if no failure reason is available.
509   */
510  @Nullable()
511  public String getExternalAuthenticationFailureReason()
512  {
513    return externalAuthFailureReason;
514  }
515
516
517
518  /**
519   * Indicates whether the external authentication processing involved a
520   * password.
521   *
522   * @return  {@code true} if the external authentication processing involved a
523   *          password, or {@code false} if not.
524   */
525  public boolean externalAuthenticationWasPasswordBased()
526  {
527    return externalAuthWasPasswordBased;
528  }
529
530
531
532  /**
533   * Indicates whether the external authentication processing is considered to
534   * have been secure.
535   *
536   * @return  {@code true} if the external authentication processing was
537   *          considered secure, or {@code false} if not.
538   */
539  public boolean externalAuthenticationWasSecure()
540  {
541    return externalAuthWasSecure;
542  }
543
544
545
546  /**
547   * Retrieves the IPv4 or IPv6 address of the end client involved in the
548   * external authentication processing, if available.
549   *
550   * @return  The IPv4 or IPv6 address of the end client involved in the
551   *          external authentication processing, or {@code null} if this is not
552   *          available.
553   */
554  @Nullable()
555  public String getEndClientIPAddress()
556  {
557    return endClientIPAddress;
558  }
559
560
561
562  /**
563   * Retrieves a map of additional properties that should be recorded in the
564   * server's access log for the external authentication attempt.
565   *
566   * @return  A map of additional properties that should be recorded in the
567   *          server's access log for the external authentication attempt, or an
568   *          empty map if there are no additional log properties.
569   */
570  @NotNull()
571  public Map<String,String> getAdditionalAccessLogProperties()
572  {
573    return additionalAccessLogProperties;
574  }
575
576
577
578  /**
579   * {@inheritDoc}
580   */
581  @Override()
582  @NotNull()
583  public String getSASLMechanismName()
584  {
585    return UNBOUNDID_EXTERNALLY_PROCESSED_AUTH_MECHANISM_NAME;
586  }
587
588
589
590  /**
591   * Retrieves an encoded representation of the SASL credentials for this bind
592   * request.
593   *
594   * @return  An encoded representation of the SASL credentials for this bind
595   *          request.
596   */
597  @NotNull()
598  public ASN1OctetString getEncodedCredentials()
599  {
600    if (encodedCredentials == null)
601    {
602      final ArrayList<ASN1Element> credElements = new ArrayList<>(8);
603
604      credElements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID,
605           authenticationID));
606      credElements.add(new ASN1OctetString(TYPE_EXTERNAL_MECHANISM_NAME,
607           externalMechanismName));
608      credElements.add(new ASN1Boolean(TYPE_EXTERNAL_AUTH_WAS_SUCCESSFUL,
609           externalAuthWasSuccessful));
610
611      if (externalAuthFailureReason != null)
612      {
613        credElements.add(new ASN1OctetString(TYPE_EXTERNAL_AUTH_FAILURE_REASON,
614             externalAuthFailureReason));
615      }
616
617      if (! externalAuthWasPasswordBased)
618      {
619        credElements.add(new ASN1Boolean(TYPE_EXTERNAL_AUTH_WAS_PASSWORD_BASED,
620             false));
621      }
622
623      if (externalAuthWasSecure)
624      {
625        credElements.add(new ASN1Boolean(TYPE_EXTERNAL_AUTH_WAS_SECURE, true));
626      }
627
628      if (endClientIPAddress != null)
629      {
630        credElements.add(new ASN1OctetString(TYPE_END_CLIENT_IP_ADDRESS,
631             endClientIPAddress));
632      }
633
634      if (! additionalAccessLogProperties.isEmpty())
635      {
636        final ArrayList<ASN1Element> logElements =
637             new ArrayList<>(additionalAccessLogProperties.size());
638        for (final Map.Entry<String,String> e :
639             additionalAccessLogProperties.entrySet())
640        {
641          logElements.add(new ASN1Sequence(
642               new ASN1OctetString(e.getKey()),
643               new ASN1OctetString(e.getValue())));
644        }
645
646        credElements.add(new ASN1Sequence(TYPE_ADDITIONAL_ACCESS_LOG_PROPERTIES,
647             logElements));
648      }
649
650      final ASN1Sequence credSequence = new ASN1Sequence(credElements);
651      encodedCredentials = new ASN1OctetString(credSequence.encode());
652    }
653
654    return encodedCredentials;
655  }
656
657
658
659  /**
660   * {@inheritDoc}
661   */
662  @Override()
663  @NotNull()
664  protected BindResult process(@NotNull final LDAPConnection connection,
665                               final int depth)
666            throws LDAPException
667  {
668    messageID = InternalSDKHelper.nextMessageID(connection);
669    return sendBindRequest(connection, "", getEncodedCredentials(),
670         getControls(), getResponseTimeoutMillis(connection));
671  }
672
673
674
675  /**
676   * {@inheritDoc}
677   */
678  @Override()
679  public int getLastMessageID()
680  {
681    return messageID;
682  }
683
684
685
686  /**
687   * {@inheritDoc}
688   */
689  @Override()
690  @NotNull()
691  public UnboundIDExternallyProcessedAuthenticationBindRequest duplicate()
692  {
693    return duplicate(getControls());
694  }
695
696
697
698  /**
699   * {@inheritDoc}
700   */
701  @Override()
702  @NotNull()
703  public UnboundIDExternallyProcessedAuthenticationBindRequest duplicate(
704              @Nullable final Control[] controls)
705  {
706    final UnboundIDExternallyProcessedAuthenticationBindRequest bindRequest =
707         new UnboundIDExternallyProcessedAuthenticationBindRequest(
708              authenticationID, externalMechanismName,
709              externalAuthWasSuccessful, externalAuthFailureReason,
710              externalAuthWasPasswordBased, externalAuthWasSecure,
711              endClientIPAddress, additionalAccessLogProperties, controls);
712    bindRequest.encodedCredentials = encodedCredentials;
713
714    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
715    return bindRequest;
716  }
717
718
719
720  /**
721   * {@inheritDoc}
722   */
723  @Override()
724  @NotNull()
725  public UnboundIDExternallyProcessedAuthenticationBindRequest getRebindRequest(
726              @NotNull final String host, final int port)
727  {
728    return duplicate();
729  }
730
731
732
733  /**
734   * {@inheritDoc}
735   */
736  @Override()
737  public void toString(@NotNull final StringBuilder buffer)
738  {
739    buffer.append("UnboundIDExternallyProcessedAuthenticationBindRequest(" +
740         "authenticationID='");
741    buffer.append(authenticationID);
742    buffer.append("', externalMechanismName='");
743    buffer.append(externalMechanismName);
744    buffer.append("', externalAuthenticationWasSuccessful=");
745    buffer.append(externalAuthWasSuccessful);
746    buffer.append('\'');
747
748    if (externalAuthFailureReason != null)
749    {
750      buffer.append(", externalAuthenticationFailureReason='");
751      buffer.append(externalAuthFailureReason);
752      buffer.append('\'');
753    }
754
755    buffer.append(", externalAuthenticationWasPasswordBased=");
756    buffer.append(externalAuthWasPasswordBased);
757    buffer.append(", externalAuthenticationWasSecure=");
758    buffer.append(externalAuthWasSecure);
759
760    if (endClientIPAddress != null)
761    {
762      buffer.append(", endClientIPAddress='");
763      buffer.append(endClientIPAddress);
764      buffer.append('\'');
765    }
766
767    if (! additionalAccessLogProperties.isEmpty())
768    {
769      buffer.append(", additionalAccessLogProperties={");
770
771      final Iterator<Map.Entry<String,String>> iterator =
772           additionalAccessLogProperties.entrySet().iterator();
773      while (iterator.hasNext())
774      {
775        final Map.Entry<String,String> e = iterator.next();
776
777        buffer.append('\'');
778        buffer.append(e.getKey());
779        buffer.append("'='");
780        buffer.append(e.getValue());
781        buffer.append('\'');
782
783        if (iterator.hasNext())
784        {
785          buffer.append(", ");
786        }
787      }
788
789      buffer.append('}');
790    }
791
792
793    final Control[] controls = getControls();
794    if (controls.length > 0)
795    {
796      buffer.append(", controls={");
797      for (int i=0; i < controls.length; i++)
798      {
799        if (i > 0)
800        {
801          buffer.append(", ");
802        }
803
804        buffer.append(controls[i]);
805      }
806      buffer.append('}');
807    }
808
809    buffer.append(')');
810  }
811
812
813
814  /**
815   * {@inheritDoc}
816   */
817  @Override()
818  public void toCode(@NotNull final List<String> lineList,
819                     @NotNull final String requestID,
820                     final int indentSpaces, final boolean includeProcessing)
821  {
822    // Create the map of additional log properties.
823    final ArrayList<ToCodeArgHelper> mapConstructorArgs = new ArrayList<>(1);
824    mapConstructorArgs.add(ToCodeArgHelper.createInteger(
825         additionalAccessLogProperties.size(), "Initial Capacity"));
826
827    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
828         "LinkedHashMap<String,String>",
829         requestID + "AdditionalAccessLogProperties",
830         "new LinkedHashMap<String,String>",
831         mapConstructorArgs);
832
833
834    // Create the method calls used to populate the map.
835    for (final Map.Entry<String,String> e :
836         additionalAccessLogProperties.entrySet())
837    {
838      final ArrayList<ToCodeArgHelper> putArgs = new ArrayList<>(2);
839      putArgs.add(ToCodeArgHelper.createString(e.getKey(),
840           "Log Property Key"));
841      putArgs.add(ToCodeArgHelper.createString(e.getValue(),
842           "Log Property Value"));
843
844      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
845           requestID + "AdditionalAccessLogProperties.put", putArgs);
846    }
847
848
849    // Create the request variable.
850    final ArrayList<ToCodeArgHelper> requestConstructorArgs =
851         new ArrayList<>(8);
852    requestConstructorArgs.add(ToCodeArgHelper.createString(authenticationID,
853         "Authentication ID"));
854    requestConstructorArgs.add(ToCodeArgHelper.createString(
855         externalMechanismName, "External Mechanism Name"));
856    requestConstructorArgs.add(ToCodeArgHelper.createBoolean(
857         externalAuthWasSuccessful, "External Authentication Was Successful"));
858    requestConstructorArgs.add(ToCodeArgHelper.createString(
859         externalAuthFailureReason, "External Authentication Failure Reason"));
860    requestConstructorArgs.add(ToCodeArgHelper.createBoolean(
861         externalAuthWasPasswordBased,
862         "External Authentication Was Password Based"));
863    requestConstructorArgs.add(ToCodeArgHelper.createBoolean(
864         externalAuthWasSecure, "External Authentication Was Secure"));
865    requestConstructorArgs.add(ToCodeArgHelper.createString(endClientIPAddress,
866         "End Client IP Address"));
867    requestConstructorArgs.add(ToCodeArgHelper.createRaw(
868         requestID + "AdditionalAccessLogProperties",
869         "Additional AccessLogProperties"));
870
871    final Control[] controls = getControls();
872    if (controls.length > 0)
873    {
874      requestConstructorArgs.add(ToCodeArgHelper.createControlArray(controls,
875           "Bind Controls"));
876    }
877
878    lineList.add("");
879    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
880         "UnboundIDExternallyProcessedAuthenticationBindRequest",
881         requestID + "Request",
882         "new UnboundIDExternallyProcessedAuthenticationBindRequest",
883         requestConstructorArgs);
884
885
886    // Add lines for processing the request and obtaining the result.
887    if (includeProcessing)
888    {
889      // Generate a string with the appropriate indent.
890      final StringBuilder buffer = new StringBuilder();
891      for (int i=0; i < indentSpaces; i++)
892      {
893        buffer.append(' ');
894      }
895      final String indent = buffer.toString();
896
897      lineList.add("");
898      lineList.add(indent + "try");
899      lineList.add(indent + '{');
900      lineList.add(indent + "  BindResult " + requestID +
901           "Result = connection.bind(" + requestID + "Request);");
902      lineList.add(indent + "  // The bind was processed successfully.");
903      lineList.add(indent + '}');
904      lineList.add(indent + "catch (LDAPException e)");
905      lineList.add(indent + '{');
906      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
907           "help explain why.");
908      lineList.add(indent + "  // Note that the connection is now likely in " +
909           "an unauthenticated state.");
910      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
911      lineList.add(indent + "  String message = e.getMessage();");
912      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
913      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
914      lineList.add(indent + "  Control[] responseControls = " +
915           "e.getResponseControls();");
916      lineList.add(indent + '}');
917    }
918  }
919}