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