001/*
002 * Copyright 2012-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2012-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) 2012-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.unboundidds;
037
038
039
040import java.util.ArrayList;
041import java.util.List;
042
043import com.unboundid.asn1.ASN1Element;
044import com.unboundid.asn1.ASN1OctetString;
045import com.unboundid.asn1.ASN1Sequence;
046import com.unboundid.ldap.sdk.Control;
047import com.unboundid.ldap.sdk.LDAPException;
048import com.unboundid.ldap.sdk.ResultCode;
049import com.unboundid.ldap.sdk.ToCodeArgHelper;
050import com.unboundid.ldap.sdk.ToCodeHelper;
051import com.unboundid.util.Debug;
052import com.unboundid.util.NotMutable;
053import com.unboundid.util.NotNull;
054import com.unboundid.util.Nullable;
055import com.unboundid.util.StaticUtils;
056import com.unboundid.util.ThreadSafety;
057import com.unboundid.util.ThreadSafetyLevel;
058import com.unboundid.util.Validator;
059
060import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
061
062
063
064/**
065 * This class provides an implementation of the UNBOUNDID-TOTP SASL bind request
066 * that contains a point-in-time version of the one-time password and can be
067 * used for a single bind but is not suitable for repeated use.  This version of
068 * the bind request should be used for authentication in which the one-time
069 * password is provided by an external source rather than being generated by
070 * the LDAP SDK.
071 * <BR>
072 * <BLOCKQUOTE>
073 *   <B>NOTE:</B>  This class, and other classes within the
074 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
075 *   supported for use against Ping Identity, UnboundID, and
076 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
077 *   for proprietary functionality or for external specifications that are not
078 *   considered stable or mature enough to be guaranteed to work in an
079 *   interoperable way with other types of LDAP servers.
080 * </BLOCKQUOTE>
081 * <BR>
082 * Because the one-time password is provided rather than generated, this version
083 * of the bind request is not suitable for cases in which the authentication
084 * process may need to be repeated (e.g., for use in a connection pool,
085 * following referrals, or if the auto-reconnect feature is enabled), then the
086 * reusable variant (supported by the {@link ReusableTOTPBindRequest} class)
087 * which generates the one-time password should be used instead.
088 */
089@NotMutable()
090@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
091public final class SingleUseTOTPBindRequest
092       extends UnboundIDTOTPBindRequest
093{
094  /**
095   * The serial version UID for this serializable class.
096   */
097  private static final long serialVersionUID = -4429898810534930296L;
098
099
100
101  // The hard-coded TOTP password to include in the bind request.
102  @NotNull private final String totpPassword;
103
104
105
106  /**
107   * Creates a new SASL TOTP bind request with the provided information.
108   *
109   * @param  authenticationID  The authentication identity for the bind request.
110   *                           It must not be {@code null}, and must be in the
111   *                           form "u:" followed by a username, or "dn:"
112   *                           followed by a DN.
113   * @param  authorizationID   The authorization identity for the bind request.
114   *                           It may be {@code null} if the authorization
115   *                           identity should be the same as the authentication
116   *                           identity.  If an authorization identity is
117   *                           specified, it must be in the form "u:" followed
118   *                           by a username, or "dn:" followed by a DN.  The
119   *                           value "dn:" may indicate an authorization
120   *                           identity of the anonymous user.
121   * @param  totpPassword      The hard-coded TOTP password to include in the
122   *                           bind request.  It must not be {@code null}.
123   * @param  staticPassword    The static password for the target user.  It may
124   *                           be {@code null} if only the one-time password is
125   *                           to be used for authentication (which may or may
126   *                           not be allowed by the server).
127   * @param  controls          The set of controls to include in the bind
128   *                           request.
129   */
130  public SingleUseTOTPBindRequest(@NotNull final String authenticationID,
131                                  @Nullable final String authorizationID,
132                                  @NotNull final String totpPassword,
133                                  @Nullable final String staticPassword,
134                                  @Nullable final Control... controls)
135  {
136    super(authenticationID, authorizationID, staticPassword, controls);
137
138    Validator.ensureNotNull(totpPassword);
139    this.totpPassword = totpPassword;
140  }
141
142
143
144  /**
145   * Creates a new SASL TOTP bind request with the provided information.
146   *
147   * @param  authenticationID  The authentication identity for the bind request.
148   *                           It must not be {@code null}, and must be in the
149   *                           form "u:" followed by a username, or "dn:"
150   *                           followed by a DN.
151   * @param  authorizationID   The authorization identity for the bind request.
152   *                           It may be {@code null} if the authorization
153   *                           identity should be the same as the authentication
154   *                           identity.  If an authorization identity is
155   *                           specified, it must be in the form "u:" followed
156   *                           by a username, or "dn:" followed by a DN.  The
157   *                           value "dn:" may indicate an authorization
158   *                           identity of the anonymous user.
159   * @param  totpPassword      The hard-coded TOTP password to include in the
160   *                           bind request.  It must not be {@code null}.
161   * @param  staticPassword    The static password for the target user.  It may
162   *                           be {@code null} if only the one-time password is
163   *                           to be used for authentication (which may or may
164   *                           not be allowed by the server).
165   * @param  controls          The set of controls to include in the bind
166   *                           request.
167   */
168  public SingleUseTOTPBindRequest(@NotNull final String authenticationID,
169                                  @Nullable final String authorizationID,
170                                  @NotNull final String totpPassword,
171                                  @Nullable final byte[] staticPassword,
172                                  @Nullable final Control... controls)
173  {
174    super(authenticationID, authorizationID, staticPassword, controls);
175
176    Validator.ensureNotNull(totpPassword);
177    this.totpPassword = totpPassword;
178  }
179
180
181
182  /**
183   * Creates a new SASL TOTP bind request with the provided information.
184   *
185   * @param  authenticationID  The authentication identity for the bind request.
186   *                           It must not be {@code null}, and must be in the
187   *                           form "u:" followed by a username, or "dn:"
188   *                           followed by a DN.
189   * @param  authorizationID   The authorization identity for the bind request.
190   *                           It may be {@code null} if the authorization
191   *                           identity should be the same as the authentication
192   *                           identity.  If an authorization identity is
193   *                           specified, it must be in the form "u:" followed
194   *                           by a username, or "dn:" followed by a DN.  The
195   *                           value "dn:" may indicate an authorization
196   *                           identity of the anonymous user.
197   * @param  totpPassword      The hard-coded TOTP password to include in the
198   *                           bind request.  It must not be {@code null}.
199   * @param  staticPassword    The static password for the target user.  It may
200   *                           be {@code null} if only the one-time password is
201   *                           to be used for authentication (which may or may
202   *                           not be allowed by the server).
203   * @param  controls          The set of controls to include in the bind
204   *                           request.
205   */
206  private SingleUseTOTPBindRequest(@NotNull final String authenticationID,
207               @Nullable final String authorizationID,
208               @NotNull final String totpPassword,
209               @Nullable final ASN1OctetString staticPassword,
210               @Nullable final Control... controls)
211  {
212    super(authenticationID, authorizationID, staticPassword, controls);
213
214    Validator.ensureNotNull(totpPassword);
215    this.totpPassword = totpPassword;
216  }
217
218
219
220  /**
221   * Creates a new single-use TOTP bind request from the information contained
222   * in the provided encoded SASL credentials.
223   *
224   * @param  saslCredentials  The encoded SASL credentials to be decoded in
225   *                          order to create this single-use TOTP bind request.
226   *                          It must not be {@code null}.
227   * @param  controls         The set of controls to include in the bind
228   *                          request.
229   *
230   * @return  The single-use TOTP bind request decoded from the provided
231   *          credentials.
232   *
233   * @throws  LDAPException  If the provided credentials are not valid for an
234   *                         UNBOUNDID-TOTP bind request.
235   */
236  @NotNull()
237  public static SingleUseTOTPBindRequest decodeSASLCredentials(
238                     @NotNull final ASN1OctetString saslCredentials,
239                     @Nullable final Control... controls)
240         throws LDAPException
241  {
242    try
243    {
244      String          authenticationID = null;
245      String          authorizationID  = null;
246      String          totpPassword     = null;
247      ASN1OctetString staticPassword   = null;
248
249      final ASN1Sequence s =
250           ASN1Sequence.decodeAsSequence(saslCredentials.getValue());
251      for (final ASN1Element e : s.elements())
252      {
253        switch (e.getType())
254        {
255          case TYPE_AUTHENTICATION_ID:
256            authenticationID = e.decodeAsOctetString().stringValue();
257            break;
258          case TYPE_AUTHORIZATION_ID:
259            authorizationID = e.decodeAsOctetString().stringValue();
260            break;
261          case TYPE_TOTP_PASSWORD:
262            totpPassword = e.decodeAsOctetString().stringValue();
263            break;
264          case TYPE_STATIC_PASSWORD:
265            staticPassword = e.decodeAsOctetString();
266            break;
267          default:
268            throw new LDAPException(ResultCode.DECODING_ERROR,
269                 ERR_SINGLE_USE_TOTP_DECODE_INVALID_ELEMENT_TYPE.get(
270                      StaticUtils.toHex(e.getType())));
271        }
272      }
273
274      if (authenticationID == null)
275      {
276        throw new LDAPException(ResultCode.DECODING_ERROR,
277             ERR_SINGLE_USE_TOTP_DECODE_MISSING_AUTHN_ID.get());
278      }
279
280      if (totpPassword == null)
281      {
282        throw new LDAPException(ResultCode.DECODING_ERROR,
283             ERR_SINGLE_USE_TOTP_DECODE_MISSING_TOTP_PW.get());
284      }
285
286      return new SingleUseTOTPBindRequest(authenticationID, authorizationID,
287           totpPassword, staticPassword, controls);
288    }
289    catch (final Exception e)
290    {
291      Debug.debugException(e);
292      throw new LDAPException(ResultCode.DECODING_ERROR,
293           ERR_SINGLE_USE_TOTP_DECODE_ERROR.get(
294                StaticUtils.getExceptionMessage(e)),
295           e);
296    }
297  }
298
299
300
301  /**
302   * Retrieves the hard-coded TOTP password to include in the bind request.
303   *
304   * @return  The hard-coded TOTP password to include in the bind request.
305   */
306  @NotNull()
307  public String getTOTPPassword()
308  {
309    return totpPassword;
310  }
311
312
313
314  /**
315   * {@inheritDoc}
316   */
317  @Override()
318  @NotNull()
319  protected ASN1OctetString getSASLCredentials()
320  {
321    return encodeCredentials(getAuthenticationID(), getAuthorizationID(),
322         totpPassword, getStaticPassword());
323  }
324
325
326
327  /**
328   * {@inheritDoc}
329   */
330  @Override()
331  @Nullable()
332  public SingleUseTOTPBindRequest getRebindRequest(@NotNull final String host,
333                                                   final int port)
334  {
335    // Automatic rebinding is not supported for single-use TOTP binds.
336    return null;
337  }
338
339
340
341  /**
342   * {@inheritDoc}
343   */
344  @Override()
345  @NotNull()
346  public SingleUseTOTPBindRequest duplicate()
347  {
348    return duplicate(getControls());
349  }
350
351
352
353  /**
354   * {@inheritDoc}
355   */
356  @Override()
357  @NotNull()
358  public SingleUseTOTPBindRequest duplicate(@Nullable final Control[] controls)
359  {
360    final SingleUseTOTPBindRequest bindRequest =
361         new SingleUseTOTPBindRequest(getAuthenticationID(),
362              getAuthorizationID(), totpPassword, getStaticPassword(),
363              controls);
364    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
365    bindRequest.setIntermediateResponseListener(
366         getIntermediateResponseListener());
367    bindRequest.setReferralDepth(getReferralDepth());
368    bindRequest.setReferralConnector(getReferralConnectorInternal());
369    return bindRequest;
370  }
371
372
373
374  /**
375   * {@inheritDoc}
376   */
377  @Override()
378  public void toCode(@NotNull final List<String> lineList,
379                     @NotNull final String requestID,
380                     final int indentSpaces, final boolean includeProcessing)
381  {
382    // Create the request variable.
383    final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(5);
384    constructorArgs.add(ToCodeArgHelper.createString(getAuthenticationID(),
385         "Authentication ID"));
386    constructorArgs.add(ToCodeArgHelper.createString(getAuthorizationID(),
387         "Authorization ID"));
388    constructorArgs.add(ToCodeArgHelper.createString(
389         "---redacted-totp-password---", "TOTP Password"));
390    constructorArgs.add(ToCodeArgHelper.createString(
391         ((getStaticPassword() == null)
392              ? "null"
393              : "---redacted-static-password---"),
394         "Static Password"));
395
396    final Control[] controls = getControls();
397    if (controls.length > 0)
398    {
399      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
400           "Bind Controls"));
401    }
402
403    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
404         "SingleUseTOTPBindRequest", requestID + "Request",
405         "new SingleUseTOTPBindRequest", constructorArgs);
406
407
408    // Add lines for processing the request and obtaining the result.
409    if (includeProcessing)
410    {
411      // Generate a string with the appropriate indent.
412      final StringBuilder buffer = new StringBuilder();
413      for (int i=0; i < indentSpaces; i++)
414      {
415        buffer.append(' ');
416      }
417      final String indent = buffer.toString();
418
419      lineList.add("");
420      lineList.add(indent + "try");
421      lineList.add(indent + '{');
422      lineList.add(indent + "  BindResult " + requestID +
423           "Result = connection.bind(" + requestID + "Request);");
424      lineList.add(indent + "  // The bind was processed successfully.");
425      lineList.add(indent + '}');
426      lineList.add(indent + "catch (LDAPException e)");
427      lineList.add(indent + '{');
428      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
429           "help explain why.");
430      lineList.add(indent + "  // Note that the connection is now likely in " +
431           "an unauthenticated state.");
432      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
433      lineList.add(indent + "  String message = e.getMessage();");
434      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
435      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
436      lineList.add(indent + "  Control[] responseControls = " +
437           "e.getResponseControls();");
438      lineList.add(indent + '}');
439    }
440  }
441}