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