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