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