001/*
002 * Copyright 2007-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2024 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) 2007-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk;
037
038
039
040import java.util.ArrayList;
041import java.util.List;
042
043import com.unboundid.asn1.ASN1OctetString;
044import com.unboundid.util.NotMutable;
045import com.unboundid.util.NotNull;
046import com.unboundid.util.Nullable;
047import com.unboundid.util.StaticUtils;
048import com.unboundid.util.ThreadSafety;
049import com.unboundid.util.ThreadSafetyLevel;
050import com.unboundid.util.Validator;
051
052
053
054/**
055 * This class provides a SASL PLAIN bind request implementation as described in
056 * <A HREF="http://www.ietf.org/rfc/rfc4616.txt">RFC 4616</A>.  The SASL PLAIN
057 * mechanism allows the client to authenticate with an authentication ID and
058 * password, and optionally allows the client to provide an authorization ID for
059 * use in performing subsequent operations.
060 * <BR><BR>
061 * Elements included in a PLAIN bind request include:
062 * <UL>
063 *   <LI>Authentication ID -- A string which identifies the user that is
064 *       attempting to authenticate.  It should be an "authzId" value as
065 *       described in section 5.2.1.8 of
066 *       <A HREF="http://www.ietf.org/rfc/rfc4513.txt">RFC 4513</A>.  That is,
067 *       it should be either "dn:" followed by the distinguished name of the
068 *       target user, or "u:" followed by the username.  If the "u:" form is
069 *       used, then the mechanism used to resolve the provided username to an
070 *       entry may vary from server to server.</LI>
071 *   <LI>Authorization ID -- An optional string which specifies an alternate
072 *       authorization identity that should be used for subsequent operations
073 *       requested on the connection.  Like the authentication ID, the
074 *       authorization ID should use the "authzId" syntax.</LI>
075 *   <LI>Password -- The clear-text password for the target user.</LI>
076 * </UL>
077 * <H2>Example</H2>
078 * The following example demonstrates the process for performing a PLAIN bind
079 * against a directory server with a username of "test.user" and a password of
080 * "password":
081 * <PRE>
082 * PLAINBindRequest bindRequest =
083 *      new PLAINBindRequest("u:test.user", "password");
084 * BindResult bindResult;
085 * try
086 * {
087 *   bindResult = connection.bind(bindRequest);
088 *   // If we get here, then the bind was successful.
089 * }
090 * catch (LDAPException le)
091 * {
092 *   // The bind failed for some reason.
093 *   bindResult = new BindResult(le.toLDAPResult());
094 *   ResultCode resultCode = le.getResultCode();
095 *   String errorMessageFromServer = le.getDiagnosticMessage();
096 * }
097 * </PRE>
098 */
099@NotMutable()
100@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
101public final class PLAINBindRequest
102       extends SASLBindRequest
103{
104  /**
105   * The name for the PLAIN SASL mechanism.
106   */
107  @NotNull public static final String PLAIN_MECHANISM_NAME = "PLAIN";
108
109
110
111  /**
112   * The serial version UID for this serializable class.
113   */
114  private static final long serialVersionUID = -5186140710317748684L;
115
116
117
118  // The password for this bind request.
119  @NotNull private final ASN1OctetString password;
120
121  // The authentication ID string for this bind request.
122  @NotNull private final String authenticationID;
123
124  // The authorization ID string for this bind request, if available.
125  @Nullable private final String authorizationID;
126
127
128
129  /**
130   * Creates a new SASL PLAIN bind request with the provided authentication ID
131   * and password.  It will not include an authorization ID or set of controls.
132   *
133   * @param  authenticationID  The authentication ID for this bind request.  It
134   *                           must not be {@code null}.
135   * @param  password          The password for this bind request.  It must not
136   *                           be {@code null}.
137   */
138  public PLAINBindRequest(@NotNull final String authenticationID,
139                          @NotNull final String password)
140  {
141    this(authenticationID, null, new ASN1OctetString(password), NO_CONTROLS);
142
143    Validator.ensureNotNull(password);
144  }
145
146
147
148  /**
149   * Creates a new SASL PLAIN bind request with the provided authentication ID
150   * and password.  It will not include an authorization ID or set of controls.
151   *
152   * @param  authenticationID  The authentication ID for this bind request.  It
153   *                           must not be {@code null}.
154   * @param  password          The password for this bind request.  It must not
155   *                           be {@code null}.
156   */
157  public PLAINBindRequest(@NotNull final String authenticationID,
158                          @NotNull final byte[] password)
159  {
160    this(authenticationID, null, new ASN1OctetString(password), NO_CONTROLS);
161
162    Validator.ensureNotNull(password);
163  }
164
165
166
167  /**
168   * Creates a new SASL PLAIN bind request with the provided authentication ID
169   * and password.  It will not include an authorization ID or set of controls.
170   *
171   * @param  authenticationID  The authentication ID for this bind request.  It
172   *                           must not be {@code null}.
173   * @param  password          The password for this bind request.  It must not
174   *                           be {@code null}.
175   */
176  public PLAINBindRequest(@NotNull final String authenticationID,
177                          @NotNull final ASN1OctetString password)
178  {
179    this(authenticationID, null, password, NO_CONTROLS);
180  }
181
182
183
184  /**
185   * Creates a new SASL PLAIN bind request with the provided authentication ID,
186   * authorization ID, and password.  It will not include a set of controls.
187   *
188   * @param  authenticationID  The authentication ID for this bind request.  It
189   *                           must not be {@code null}.
190   * @param  authorizationID   The authorization ID for this bind request, or
191   *                           {@code null} if there is to be no authorization
192   *                           ID.
193   * @param  password          The password for this bind request.  It must not
194   *                           be {@code null}.
195   */
196  public PLAINBindRequest(@NotNull final String authenticationID,
197                          @Nullable final String authorizationID,
198                          @NotNull final String password)
199  {
200    this(authenticationID, authorizationID, new ASN1OctetString(password),
201         NO_CONTROLS);
202
203    Validator.ensureNotNull(password);
204  }
205
206
207
208  /**
209   * Creates a new SASL PLAIN bind request with the provided authentication ID,
210   * authorization ID, and password.  It will not include a set of controls.
211   *
212   * @param  authenticationID  The authentication ID for this bind request.  It
213   *                           must not be {@code null}.
214   * @param  authorizationID   The authorization ID for this bind request, or
215   *                           {@code null} if there is to be no authorization
216   *                           ID.
217   * @param  password          The password for this bind request.  It must not
218   *                           be {@code null}.
219   */
220  public PLAINBindRequest(@NotNull final String authenticationID,
221                          @Nullable final String authorizationID,
222                          @NotNull final byte[] password)
223  {
224    this(authenticationID, authorizationID, new ASN1OctetString(password),
225         NO_CONTROLS);
226
227    Validator.ensureNotNull(password);
228  }
229
230
231
232  /**
233   * Creates a new SASL PLAIN bind request with the provided authentication ID,
234   * authorization ID, and password.  It will not include a set of controls.
235   *
236   * @param  authenticationID  The authentication ID for this bind request.  It
237   *                           must not be {@code null}.
238   * @param  authorizationID   The authorization ID for this bind request, or
239   *                           {@code null} if there is to be no authorization
240   *                           ID.
241   * @param  password          The password for this bind request.  It must not
242   *                           be {@code null}.
243   */
244  public PLAINBindRequest(@NotNull final String authenticationID,
245                          @Nullable final String authorizationID,
246                          @NotNull final ASN1OctetString password)
247  {
248    this(authenticationID, authorizationID, password, NO_CONTROLS);
249  }
250
251
252
253  /**
254   * Creates a new SASL PLAIN bind request with the provided authentication ID,
255   * password, and set of controls.  It will not include an authorization ID.
256   *
257   * @param  authenticationID  The authentication ID for this bind request.  It
258   *                           must not be {@code null}.
259   * @param  password          The password for this bind request.  It must not
260   *                           be {@code null}.
261   * @param  controls          The set of controls to include
262   */
263  public PLAINBindRequest(@NotNull final String authenticationID,
264                          @NotNull final String password,
265                          @Nullable final Control... controls)
266  {
267    this(authenticationID, null, new ASN1OctetString(password), controls);
268
269    Validator.ensureNotNull(password);
270  }
271
272
273
274  /**
275   * Creates a new SASL PLAIN bind request with the provided authentication ID,
276   * password, and set of controls.  It will not include an authorization ID.
277   *
278   * @param  authenticationID  The authentication ID for this bind request.  It
279   *                           must not be {@code null}.
280   * @param  password          The password for this bind request.  It must not
281   *                           be {@code null}.
282   * @param  controls          The set of controls to include
283   */
284  public PLAINBindRequest(@NotNull final String authenticationID,
285                          @NotNull final byte[] password,
286                          @Nullable final Control... controls)
287  {
288    this(authenticationID, null, new ASN1OctetString(password), controls);
289
290    Validator.ensureNotNull(password);
291  }
292
293
294
295  /**
296   * Creates a new SASL PLAIN bind request with the provided authentication ID,
297   * password, and set of controls.  It will not include an authorization ID.
298   *
299   * @param  authenticationID  The authentication ID for this bind request.  It
300   *                           must not be {@code null}.
301   * @param  password          The password for this bind request.  It must not
302   *                           be {@code null}.
303   * @param  controls          The set of controls to include
304   */
305  public PLAINBindRequest(@NotNull final String authenticationID,
306                          @NotNull final ASN1OctetString password,
307                          @Nullable final Control... controls)
308  {
309    this(authenticationID, null, password, controls);
310  }
311
312
313
314  /**
315   * Creates a new SASL PLAIN bind request with the provided information.
316   *
317   * @param  authenticationID  The authentication ID for this bind request.  It
318   *                           must not be {@code null}.
319   * @param  authorizationID   The authorization ID for this bind request, or
320   *                           {@code null} if there is to be no authorization
321   *                           ID.
322   * @param  password          The password for this bind request.  It must not
323   *                           be {@code null}.
324   * @param  controls          The set of controls to include
325   */
326  public PLAINBindRequest(@NotNull final String authenticationID,
327                          @Nullable final String authorizationID,
328                          @NotNull final String password,
329                          @Nullable final Control... controls)
330  {
331    this(authenticationID, authorizationID, new ASN1OctetString(password),
332         controls);
333
334    Validator.ensureNotNull(password);
335  }
336
337
338
339  /**
340   * Creates a new SASL PLAIN bind request with the provided information.
341   *
342   * @param  authenticationID  The authentication ID for this bind request.  It
343   *                           must not be {@code null}.
344   * @param  authorizationID   The authorization ID for this bind request, or
345   *                           {@code null} if there is to be no authorization
346   *                           ID.
347   * @param  password          The password for this bind request.  It must not
348   *                           be {@code null}.
349   * @param  controls          The set of controls to include
350   */
351  public PLAINBindRequest(@NotNull final String authenticationID,
352                          @Nullable final String authorizationID,
353                          @NotNull final byte[] password,
354                          @Nullable final Control... controls)
355  {
356    this(authenticationID, authorizationID, new ASN1OctetString(password),
357         controls);
358
359    Validator.ensureNotNull(password);
360  }
361
362
363
364  /**
365   * Creates a new SASL PLAIN bind request with the provided information.
366   *
367   * @param  authenticationID  The authentication ID for this bind request.  It
368   *                           must not be {@code null}.
369   * @param  authorizationID   The authorization ID for this bind request, or
370   *                           {@code null} if there is to be no authorization
371   *                           ID.
372   * @param  password          The password for this bind request.  It must not
373   *                           be {@code null}.
374   * @param  controls          The set of controls to include
375   */
376  public PLAINBindRequest(@NotNull final String authenticationID,
377                          @Nullable final String authorizationID,
378                          @NotNull final ASN1OctetString password,
379                          @Nullable final Control... controls)
380  {
381    super(controls);
382
383    Validator.ensureNotNull(authenticationID, password);
384
385    this.authenticationID = authenticationID;
386    this.authorizationID  = authorizationID;
387    this.password         = password;
388  }
389
390
391
392  /**
393   * {@inheritDoc}
394   */
395  @Override()
396  @NotNull()
397  public String getSASLMechanismName()
398  {
399    return PLAIN_MECHANISM_NAME;
400  }
401
402
403
404  /**
405   * Retrieves the authentication ID for this bind request.
406   *
407   * @return  The authentication ID for this bind request.
408   */
409  @NotNull()
410  public String getAuthenticationID()
411  {
412    return authenticationID;
413  }
414
415
416
417  /**
418   * Retrieves the authorization ID for this bind request.
419   *
420   * @return  The authorization ID for this bind request, or {@code null} if
421   *          there is no authorization ID.
422   */
423  @Nullable()
424  public String getAuthorizationID()
425  {
426    return authorizationID;
427  }
428
429
430
431  /**
432   * Retrieves the string representation of the password for this bind request.
433   *
434   * @return  The string representation of the password for this bind request.
435   */
436  @NotNull()
437  public String getPasswordString()
438  {
439    return password.stringValue();
440  }
441
442
443
444  /**
445   * Retrieves the bytes that comprise the the password for this bind request.
446   *
447   * @return  The bytes that comprise the password for this bind request.
448   */
449  @NotNull()
450  public byte[] getPasswordBytes()
451  {
452    return password.getValue();
453  }
454
455
456
457  /**
458   * Sends this bind request to the target server over the provided connection
459   * and returns the corresponding response.
460   *
461   * @param  connection  The connection to use to send this bind request to the
462   *                     server and read the associated response.
463   * @param  depth       The current referral depth for this request.  It should
464   *                     always be one for the initial request, and should only
465   *                     be incremented when following referrals.
466   *
467   * @return  The bind response read from the server.
468   *
469   * @throws  LDAPException  If a problem occurs while sending the request or
470   *                         reading the response.
471   */
472  @Override()
473  @NotNull()
474  protected BindResult process(@NotNull final LDAPConnection connection,
475                               final int depth)
476            throws LDAPException
477  {
478    setReferralDepth(depth);
479
480    return sendBindRequest(connection, "",
481         encodeCredentials(authenticationID, authorizationID, password),
482         getControls(), getResponseTimeoutMillis(connection));
483  }
484
485
486
487  /**
488   * Encodes the provided information into an ASN.1 octet string that may be
489   * used as the SASL credentials for an UnboundID delivered one-time password
490   * bind request.
491   *
492   * @param  authenticationID  The authentication identity for the bind request.
493   *                           It must not be {@code null} and must in the form
494   *                           "u:" followed by a username, or "dn:" followed
495   *                           by a DN.
496   * @param  authorizationID   The authorization identity for the bind request.
497   *                           It may be {@code null} if the authorization
498   *                           identity should be the same as the authentication
499   *                           identity.  If an authorization identity is
500   *                           specified, it must be in the form "u:" followed
501   *                           by a username, or "dn:" followed by a DN.  The
502   *                           value "dn:" may be used to indicate the
503   *                           authorization identity of the anonymous user.
504   * @param  password          The password for this PLAIN bind request.  It
505   *                           must not be {@code null}.
506   *
507   * @return  An ASN.1 octet string that may be used as the SASL credentials for
508   *          an UnboundID delivered one-time password bind request.
509   */
510  @NotNull()
511  public static ASN1OctetString encodeCredentials(
512              @NotNull final String authenticationID,
513              @Nullable final String authorizationID,
514              @NotNull final ASN1OctetString password)
515  {
516    final byte[] authZIDBytes  = StaticUtils.getBytes(authorizationID);
517    final byte[] authNIDBytes  = StaticUtils.getBytes(authenticationID);
518    final byte[] passwordBytes = password.getValue();
519    final byte[] credBytes     = new byte[2 + authZIDBytes.length +
520                                    authNIDBytes.length + passwordBytes.length];
521
522    System.arraycopy(authZIDBytes, 0, credBytes, 0, authZIDBytes.length);
523
524    int pos = authZIDBytes.length + 1;
525    System.arraycopy(authNIDBytes, 0, credBytes, pos, authNIDBytes.length);
526
527    pos += authNIDBytes.length + 1;
528    System.arraycopy(passwordBytes, 0, credBytes, pos, passwordBytes.length);
529
530    return new ASN1OctetString(credBytes);
531  }
532
533
534
535  /**
536   * {@inheritDoc}
537   */
538  @Override()
539  @NotNull()
540  public PLAINBindRequest getRebindRequest(@NotNull final String host,
541                                           final int port)
542  {
543    return new PLAINBindRequest(authenticationID, authorizationID, password,
544                                getControls());
545  }
546
547
548
549  /**
550   * {@inheritDoc}
551   */
552  @Override()
553  @NotNull()
554  public PLAINBindRequest duplicate()
555  {
556    return duplicate(getControls());
557  }
558
559
560
561  /**
562   * {@inheritDoc}
563   */
564  @Override()
565  @NotNull()
566  public PLAINBindRequest duplicate(@Nullable final Control[] controls)
567  {
568    final PLAINBindRequest bindRequest = new PLAINBindRequest(authenticationID,
569         authorizationID, password, controls);
570    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
571    bindRequest.setIntermediateResponseListener(
572         getIntermediateResponseListener());
573    bindRequest.setReferralDepth(getReferralDepth());
574    bindRequest.setReferralConnector(getReferralConnectorInternal());
575    return bindRequest;
576  }
577
578
579
580  /**
581   * {@inheritDoc}
582   */
583  @Override()
584  public void toString(@NotNull final StringBuilder buffer)
585  {
586    buffer.append("PLAINBindRequest(authenticationID='");
587    buffer.append(authenticationID);
588    buffer.append('\'');
589
590    if (authorizationID != null)
591    {
592      buffer.append(", authorizationID='");
593      buffer.append(authorizationID);
594      buffer.append('\'');
595    }
596
597    final Control[] controls = getControls();
598    if (controls.length > 0)
599    {
600      buffer.append(", controls={");
601      for (int i=0; i < controls.length; i++)
602      {
603        if (i > 0)
604        {
605          buffer.append(", ");
606        }
607
608        buffer.append(controls[i]);
609      }
610      buffer.append('}');
611    }
612
613    buffer.append(')');
614  }
615
616
617
618  /**
619   * {@inheritDoc}
620   */
621  @Override()
622  public void toCode(@NotNull final List<String> lineList,
623                     @NotNull final String requestID,
624                     final int indentSpaces, final boolean includeProcessing)
625  {
626    // Create the request variable.
627    final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(4);
628    constructorArgs.add(ToCodeArgHelper.createString(authenticationID,
629         "Authentication ID"));
630    constructorArgs.add(ToCodeArgHelper.createString(authorizationID,
631         "Authorization ID"));
632    constructorArgs.add(ToCodeArgHelper.createString("---redacted-password---",
633         "Bind Password"));
634
635    final Control[] controls = getControls();
636    if (controls.length > 0)
637    {
638      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
639           "Bind Controls"));
640    }
641
642    ToCodeHelper.generateMethodCall(lineList, indentSpaces, "PLAINBindRequest",
643         requestID + "Request", "new PLAINBindRequest", constructorArgs);
644
645
646    // Add lines for processing the request and obtaining the result.
647    if (includeProcessing)
648    {
649      // Generate a string with the appropriate indent.
650      final StringBuilder buffer = new StringBuilder();
651      for (int i=0; i < indentSpaces; i++)
652      {
653        buffer.append(' ');
654      }
655      final String indent = buffer.toString();
656
657      lineList.add("");
658      lineList.add(indent + "try");
659      lineList.add(indent + '{');
660      lineList.add(indent + "  BindResult " + requestID +
661           "Result = connection.bind(" + requestID + "Request);");
662      lineList.add(indent + "  // The bind was processed successfully.");
663      lineList.add(indent + '}');
664      lineList.add(indent + "catch (LDAPException e)");
665      lineList.add(indent + '{');
666      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
667           "help explain why.");
668      lineList.add(indent + "  // Note that the connection is now likely in " +
669           "an unauthenticated state.");
670      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
671      lineList.add(indent + "  String message = e.getMessage();");
672      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
673      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
674      lineList.add(indent + "  Control[] responseControls = " +
675           "e.getResponseControls();");
676      lineList.add(indent + '}');
677    }
678  }
679}