001/*
002 * Copyright 2016-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2016-2019 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 =
350         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
351
352    try
353    {
354      for (final ASN1Element e :
355           ASN1Sequence.decodeAsSequence(saslCredentials.getValue()).elements())
356      {
357        switch (e.getType())
358        {
359          case TYPE_AUTHENTICATION_ID:
360            authID = ASN1OctetString.decodeAsOctetString(e).stringValue();
361            break;
362          case TYPE_EXTERNAL_MECHANISM_NAME:
363            mechanism = ASN1OctetString.decodeAsOctetString(e).stringValue();
364            break;
365          case TYPE_EXTERNAL_AUTH_WAS_SUCCESSFUL:
366            successful = ASN1Boolean.decodeAsBoolean(e).booleanValue();
367            break;
368          case TYPE_EXTERNAL_AUTH_FAILURE_REASON:
369            failureReason =
370                 ASN1OctetString.decodeAsOctetString(e).stringValue();
371            break;
372          case TYPE_EXTERNAL_AUTH_WAS_PASSWORD_BASED:
373            passwordBased = ASN1Boolean.decodeAsBoolean(e).booleanValue();
374            break;
375          case TYPE_EXTERNAL_AUTH_WAS_SECURE:
376            secure = ASN1Boolean.decodeAsBoolean(e).booleanValue();
377            break;
378          case TYPE_END_CLIENT_IP_ADDRESS:
379            ipAddress = ASN1OctetString.decodeAsOctetString(e).stringValue();
380            break;
381          case TYPE_ADDITIONAL_ACCESS_LOG_PROPERTIES:
382            for (final ASN1Element propertiesElement :
383                 ASN1Sequence.decodeAsSequence(e).elements())
384            {
385              final ASN1Element[] logPairElements =
386                   ASN1Sequence.decodeAsSequence(propertiesElement).elements();
387              final String name = ASN1OctetString.decodeAsOctetString(
388                   logPairElements[0]).stringValue();
389              final String value = ASN1OctetString.decodeAsOctetString(
390                   logPairElements[1]).stringValue();
391              logProperties.put(name, value);
392            }
393            break;
394        }
395      }
396    }
397    catch (final Exception e)
398    {
399      Debug.debugException(e);
400      throw new LDAPException(ResultCode.DECODING_ERROR,
401           ERR_EXTERNALLY_PROCESSED_AUTH_CANNOT_DECODE_CREDS.get(
402                UNBOUNDID_EXTERNALLY_PROCESSED_AUTH_MECHANISM_NAME,
403                StaticUtils.getExceptionMessage(e)),
404           e);
405    }
406
407    if (authID == null)
408    {
409      throw new LDAPException(ResultCode.DECODING_ERROR,
410           ERR_EXTERNALLY_PROCESSED_AUTH_NO_AUTH_ID.get(
411                UNBOUNDID_EXTERNALLY_PROCESSED_AUTH_MECHANISM_NAME));
412    }
413
414    if (mechanism == null)
415    {
416      throw new LDAPException(ResultCode.DECODING_ERROR,
417           ERR_EXTERNALLY_PROCESSED_AUTH_NO_MECH.get(
418                UNBOUNDID_EXTERNALLY_PROCESSED_AUTH_MECHANISM_NAME));
419    }
420
421    if (successful == null)
422    {
423      throw new LDAPException(ResultCode.DECODING_ERROR,
424           ERR_EXTERNALLY_PROCESSED_AUTH_NO_WAS_SUCCESSFUL.get(
425                UNBOUNDID_EXTERNALLY_PROCESSED_AUTH_MECHANISM_NAME));
426    }
427
428    final UnboundIDExternallyProcessedAuthenticationBindRequest bindRequest =
429         new UnboundIDExternallyProcessedAuthenticationBindRequest(authID,
430              mechanism, successful, failureReason, passwordBased, secure,
431              ipAddress, logProperties, controls);
432    bindRequest.encodedCredentials = saslCredentials;
433
434    return bindRequest;
435  }
436
437
438
439  /**
440   * Retrieves the authentication ID that identifies the user for whom the
441   * external authentication processing was performed.
442   *
443   * @return  The authentication ID that identifies the user for whom the
444   *          external authentication processing was performed.
445   */
446  public String getAuthenticationID()
447  {
448    return authenticationID;
449  }
450
451
452
453  /**
454   * Retrieves the name of the mechanism used for the external authentication
455   * attempt.
456   *
457   * @return  The name of the mechanism used for the external authentication
458   *          attempt.
459   */
460  public String getExternalMechanismName()
461  {
462    return externalMechanismName;
463  }
464
465
466
467  /**
468   * Indicates whether the external authentication attempt is considered to have
469   * been successful.
470   *
471   * @return  {@code true} if the external authentication attempt was considered
472   *          successful, or {@code false} if not.
473   */
474  public boolean externalAuthenticationWasSuccessful()
475  {
476    return externalAuthWasSuccessful;
477  }
478
479
480
481  /**
482   * Retrieves the reason that the external authentication attempt was
483   * considered a failure, if available.
484   *
485   * @return  The reason that the external authentication attempt was considered
486   *          a failure, or {@code null} if no failure reason is available.
487   */
488  public String getExternalAuthenticationFailureReason()
489  {
490    return externalAuthFailureReason;
491  }
492
493
494
495  /**
496   * Indicates whether the external authentication processing involved a
497   * password.
498   *
499   * @return  {@code true} if the external authentication processing involved a
500   *          password, or {@code false} if not.
501   */
502  public boolean externalAuthenticationWasPasswordBased()
503  {
504    return externalAuthWasPasswordBased;
505  }
506
507
508
509  /**
510   * Indicates whether the external authentication processing is considered to
511   * have been secure.
512   *
513   * @return  {@code true} if the external authentication processing was
514   *          considered secure, or {@code false} if not.
515   */
516  public boolean externalAuthenticationWasSecure()
517  {
518    return externalAuthWasSecure;
519  }
520
521
522
523  /**
524   * Retrieves the IPv4 or IPv6 address of the end client involved in the
525   * external authentication processing, if available.
526   *
527   * @return  The IPv4 or IPv6 address of the end client involved in the
528   *          external authentication processing, or {@code null} if this is not
529   *          available.
530   */
531  public String getEndClientIPAddress()
532  {
533    return endClientIPAddress;
534  }
535
536
537
538  /**
539   * Retrieves a map of additional properties that should be recorded in the
540   * server's access log for the external authentication attempt.
541   *
542   * @return  A map of additional properties that should be recorded in the
543   *          server's access log for the external authentication attempt, or an
544   *          empty map if there are no additional log properties.
545   */
546  public Map<String,String> getAdditionalAccessLogProperties()
547  {
548    return additionalAccessLogProperties;
549  }
550
551
552
553  /**
554   * {@inheritDoc}
555   */
556  @Override()
557  public String getSASLMechanismName()
558  {
559    return UNBOUNDID_EXTERNALLY_PROCESSED_AUTH_MECHANISM_NAME;
560  }
561
562
563
564  /**
565   * Retrieves an encoded representation of the SASL credentials for this bind
566   * request.
567   *
568   * @return  An encoded representation of the SASL credentials for this bind
569   *          request.
570   */
571  public ASN1OctetString getEncodedCredentials()
572  {
573    if (encodedCredentials == null)
574    {
575      final ArrayList<ASN1Element> credElements = new ArrayList<>(8);
576
577      credElements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID,
578           authenticationID));
579      credElements.add(new ASN1OctetString(TYPE_EXTERNAL_MECHANISM_NAME,
580           externalMechanismName));
581      credElements.add(new ASN1Boolean(TYPE_EXTERNAL_AUTH_WAS_SUCCESSFUL,
582           externalAuthWasSuccessful));
583
584      if (externalAuthFailureReason != null)
585      {
586        credElements.add(new ASN1OctetString(TYPE_EXTERNAL_AUTH_FAILURE_REASON,
587             externalAuthFailureReason));
588      }
589
590      if (! externalAuthWasPasswordBased)
591      {
592        credElements.add(new ASN1Boolean(TYPE_EXTERNAL_AUTH_WAS_PASSWORD_BASED,
593             false));
594      }
595
596      if (externalAuthWasSecure)
597      {
598        credElements.add(new ASN1Boolean(TYPE_EXTERNAL_AUTH_WAS_SECURE, true));
599      }
600
601      if (endClientIPAddress != null)
602      {
603        credElements.add(new ASN1OctetString(TYPE_END_CLIENT_IP_ADDRESS,
604             endClientIPAddress));
605      }
606
607      if (! additionalAccessLogProperties.isEmpty())
608      {
609        final ArrayList<ASN1Element> logElements =
610             new ArrayList<>(additionalAccessLogProperties.size());
611        for (final Map.Entry<String,String> e :
612             additionalAccessLogProperties.entrySet())
613        {
614          logElements.add(new ASN1Sequence(
615               new ASN1OctetString(e.getKey()),
616               new ASN1OctetString(e.getValue())));
617        }
618
619        credElements.add(new ASN1Sequence(TYPE_ADDITIONAL_ACCESS_LOG_PROPERTIES,
620             logElements));
621      }
622
623      final ASN1Sequence credSequence = new ASN1Sequence(credElements);
624      encodedCredentials = new ASN1OctetString(credSequence.encode());
625    }
626
627    return encodedCredentials;
628  }
629
630
631
632  /**
633   * {@inheritDoc}
634   */
635  @Override()
636  protected BindResult process(final LDAPConnection connection, final int depth)
637            throws LDAPException
638  {
639    messageID = InternalSDKHelper.nextMessageID(connection);
640    return sendBindRequest(connection, "", getEncodedCredentials(),
641         getControls(), getResponseTimeoutMillis(connection));
642  }
643
644
645
646  /**
647   * {@inheritDoc}
648   */
649  @Override()
650  public int getLastMessageID()
651  {
652    return messageID;
653  }
654
655
656
657  /**
658   * {@inheritDoc}
659   */
660  @Override()
661  public UnboundIDExternallyProcessedAuthenticationBindRequest duplicate()
662  {
663    return duplicate(getControls());
664  }
665
666
667
668  /**
669   * {@inheritDoc}
670   */
671  @Override()
672  public UnboundIDExternallyProcessedAuthenticationBindRequest duplicate(
673              final Control[] controls)
674  {
675    final UnboundIDExternallyProcessedAuthenticationBindRequest bindRequest =
676         new UnboundIDExternallyProcessedAuthenticationBindRequest(
677              authenticationID, externalMechanismName,
678              externalAuthWasSuccessful, externalAuthFailureReason,
679              externalAuthWasPasswordBased, externalAuthWasSecure,
680              endClientIPAddress, additionalAccessLogProperties, controls);
681    bindRequest.encodedCredentials = encodedCredentials;
682
683    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
684    return bindRequest;
685  }
686
687
688
689  /**
690   * {@inheritDoc}
691   */
692  @Override()
693  public UnboundIDExternallyProcessedAuthenticationBindRequest getRebindRequest(
694              final String host, final int port)
695  {
696    return duplicate();
697  }
698
699
700
701  /**
702   * {@inheritDoc}
703   */
704  @Override()
705  public void toString(final StringBuilder buffer)
706  {
707    buffer.append("UnboundIDExternallyProcessedAuthenticationBindRequest(" +
708         "authenticationID='");
709    buffer.append(authenticationID);
710    buffer.append("', externalMechanismName='");
711    buffer.append(externalMechanismName);
712    buffer.append("', externalAuthenticationWasSuccessful=");
713    buffer.append(externalAuthWasSuccessful);
714    buffer.append('\'');
715
716    if (externalAuthFailureReason != null)
717    {
718      buffer.append(", externalAuthenticationFailureReason='");
719      buffer.append(externalAuthFailureReason);
720      buffer.append('\'');
721    }
722
723    buffer.append(", externalAuthenticationWasPasswordBased=");
724    buffer.append(externalAuthWasPasswordBased);
725    buffer.append(", externalAuthenticationWasSecure=");
726    buffer.append(externalAuthWasSecure);
727
728    if (endClientIPAddress != null)
729    {
730      buffer.append(", endClientIPAddress='");
731      buffer.append(endClientIPAddress);
732      buffer.append('\'');
733    }
734
735    if (! additionalAccessLogProperties.isEmpty())
736    {
737      buffer.append(", additionalAccessLogProperties={");
738
739      final Iterator<Map.Entry<String,String>> iterator =
740           additionalAccessLogProperties.entrySet().iterator();
741      while (iterator.hasNext())
742      {
743        final Map.Entry<String,String> e = iterator.next();
744
745        buffer.append('\'');
746        buffer.append(e.getKey());
747        buffer.append("'='");
748        buffer.append(e.getValue());
749        buffer.append('\'');
750
751        if (iterator.hasNext())
752        {
753          buffer.append(", ");
754        }
755      }
756
757      buffer.append('}');
758    }
759
760
761    final Control[] controls = getControls();
762    if (controls.length > 0)
763    {
764      buffer.append(", controls={");
765      for (int i=0; i < controls.length; i++)
766      {
767        if (i > 0)
768        {
769          buffer.append(", ");
770        }
771
772        buffer.append(controls[i]);
773      }
774      buffer.append('}');
775    }
776
777    buffer.append(')');
778  }
779
780
781
782  /**
783   * {@inheritDoc}
784   */
785  @Override()
786  public void toCode(final List<String> lineList, final String requestID,
787                     final int indentSpaces, final boolean includeProcessing)
788  {
789    // Create the map of additional log properties.
790    final ArrayList<ToCodeArgHelper> mapConstructorArgs = new ArrayList<>(1);
791    mapConstructorArgs.add(ToCodeArgHelper.createInteger(
792         additionalAccessLogProperties.size(), "Initial Capacity"));
793
794    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
795         "LinkedHashMap<String,String>",
796         requestID + "AdditionalAccessLogProperties",
797         "new LinkedHashMap<String,String>",
798         mapConstructorArgs);
799
800
801    // Create the method calls used to populate the map.
802    for (final Map.Entry<String,String> e :
803         additionalAccessLogProperties.entrySet())
804    {
805      final ArrayList<ToCodeArgHelper> putArgs = new ArrayList<>(2);
806      putArgs.add(ToCodeArgHelper.createString(e.getKey(),
807           "Log Property Key"));
808      putArgs.add(ToCodeArgHelper.createString(e.getValue(),
809           "Log Property Value"));
810
811      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
812           requestID + "AdditionalAccessLogProperties.put", putArgs);
813    }
814
815
816    // Create the request variable.
817    final ArrayList<ToCodeArgHelper> requestConstructorArgs =
818         new ArrayList<>(8);
819    requestConstructorArgs.add(ToCodeArgHelper.createString(authenticationID,
820         "Authentication ID"));
821    requestConstructorArgs.add(ToCodeArgHelper.createString(
822         externalMechanismName, "External Mechanism Name"));
823    requestConstructorArgs.add(ToCodeArgHelper.createBoolean(
824         externalAuthWasSuccessful, "External Authentication Was Successful"));
825    requestConstructorArgs.add(ToCodeArgHelper.createString(
826         externalAuthFailureReason, "External Authentication Failure Reason"));
827    requestConstructorArgs.add(ToCodeArgHelper.createBoolean(
828         externalAuthWasPasswordBased,
829         "External Authentication Was Password Based"));
830    requestConstructorArgs.add(ToCodeArgHelper.createBoolean(
831         externalAuthWasSecure, "External Authentication Was Secure"));
832    requestConstructorArgs.add(ToCodeArgHelper.createString(endClientIPAddress,
833         "End Client IP Address"));
834    requestConstructorArgs.add(ToCodeArgHelper.createRaw(
835         requestID + "AdditionalAccessLogProperties",
836         "Additional AccessLogProperties"));
837
838    final Control[] controls = getControls();
839    if (controls.length > 0)
840    {
841      requestConstructorArgs.add(ToCodeArgHelper.createControlArray(controls,
842           "Bind Controls"));
843    }
844
845    lineList.add("");
846    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
847         "UnboundIDExternallyProcessedAuthenticationBindRequest",
848         requestID + "Request",
849         "new UnboundIDExternallyProcessedAuthenticationBindRequest",
850         requestConstructorArgs);
851
852
853    // Add lines for processing the request and obtaining the result.
854    if (includeProcessing)
855    {
856      // Generate a string with the appropriate indent.
857      final StringBuilder buffer = new StringBuilder();
858      for (int i=0; i < indentSpaces; i++)
859      {
860        buffer.append(' ');
861      }
862      final String indent = buffer.toString();
863
864      lineList.add("");
865      lineList.add(indent + "try");
866      lineList.add(indent + '{');
867      lineList.add(indent + "  BindResult " + requestID +
868           "Result = connection.bind(" + requestID + "Request);");
869      lineList.add(indent + "  // The bind was processed successfully.");
870      lineList.add(indent + '}');
871      lineList.add(indent + "catch (LDAPException e)");
872      lineList.add(indent + '{');
873      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
874           "help explain why.");
875      lineList.add(indent + "  // Note that the connection is now likely in " +
876           "an unauthenticated state.");
877      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
878      lineList.add(indent + "  String message = e.getMessage();");
879      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
880      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
881      lineList.add(indent + "  Control[] responseControls = " +
882           "e.getResponseControls();");
883      lineList.add(indent + '}');
884    }
885  }
886}