001/*
002 * Copyright 2013-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-2019 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.BindResult;
032import com.unboundid.ldap.sdk.Control;
033import com.unboundid.ldap.sdk.InternalSDKHelper;
034import com.unboundid.ldap.sdk.LDAPConnection;
035import com.unboundid.ldap.sdk.LDAPException;
036import com.unboundid.ldap.sdk.ResultCode;
037import com.unboundid.ldap.sdk.SASLBindRequest;
038import com.unboundid.ldap.sdk.ToCodeArgHelper;
039import com.unboundid.ldap.sdk.ToCodeHelper;
040import com.unboundid.ldap.sdk.unboundidds.extensions.
041            DeliverOneTimePasswordExtendedRequest;
042import com.unboundid.util.Debug;
043import com.unboundid.util.NotMutable;
044import com.unboundid.util.StaticUtils;
045import com.unboundid.util.ThreadSafety;
046import com.unboundid.util.ThreadSafetyLevel;
047import com.unboundid.util.Validator;
048
049import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
050
051
052
053/**
054 * This class provides support for an UnboundID-proprietary SASL mechanism that
055 * allows for multifactor authentication using a one-time password that has been
056 * delivered to the user via some out-of-band mechanism as triggered by the
057 * {@link DeliverOneTimePasswordExtendedRequest} (which requires the user to
058 * provide an authentication ID and a static password).
059 * <BR>
060 * <BLOCKQUOTE>
061 *   <B>NOTE:</B>  This class, and other classes within the
062 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
063 *   supported for use against Ping Identity, UnboundID, and
064 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
065 *   for proprietary functionality or for external specifications that are not
066 *   considered stable or mature enough to be guaranteed to work in an
067 *   interoperable way with other types of LDAP servers.
068 * </BLOCKQUOTE>
069 * <BR>
070 * The name for this SASL mechanism is "UNBOUNDID-DELIVERED-OTP".  An
071 * UNBOUNDID-DELIVERED-OTP SASL bind request MUST include SASL credentials with
072 * the following ASN.1 encoding:
073 * <BR><BR>
074 * <PRE>
075 *   UnboundIDDeliveredOTPCredentials ::= SEQUENCE {
076 *        authenticationID     [0] OCTET STRING,
077 *        authorizationID      [1] OCTET STRING OPTIONAL.
078 *        oneTimePassword      [2] OCTET STRING,
079 *        ... }
080 * </PRE>
081 */
082@NotMutable()
083@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
084public final class UnboundIDDeliveredOTPBindRequest
085       extends SASLBindRequest
086{
087  /**
088   * The name for the UnboundID delivered OTP SASL mechanism.
089   */
090  public static final String UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME =
091       "UNBOUNDID-DELIVERED-OTP";
092
093
094
095  /**
096   * The BER type for the authentication ID included in the request.
097   */
098  private static final byte TYPE_AUTHENTICATION_ID = (byte) 0x80;
099
100
101
102  /**
103   * The BER type for the authorization ID included in the request.
104   */
105  private static final byte TYPE_AUTHORIZATION_ID = (byte) 0x81;
106
107
108
109  /**
110   * The BER type for the one-time password included in the request.
111   */
112  private static final byte TYPE_OTP = (byte) 0x82;
113
114
115
116  /**
117   * The serial version UID for this serializable class.
118   */
119  private static final long serialVersionUID = 8148101285676071058L;
120
121
122
123  // The message ID from the last LDAP message sent from this request.
124  private volatile int messageID = -1;
125
126  // The authentication identity for the bind.
127  private final String authenticationID;
128
129  // The authorization identity for the bind, if provided.
130  private final String authorizationID;
131
132  // The one-time password for the bind, if provided.
133  private final String oneTimePassword;
134
135
136
137  /**
138   * Creates a new delivered one-time password bind request with the provided
139   * information.
140   *
141   * @param  authenticationID  The authentication identity for the bind request.
142   *                           It must not be {@code null} and must in the form
143   *                           "u:" followed by a username, or "dn:" followed
144   *                           by a DN.
145   * @param  authorizationID   The authorization identity for the bind request.
146   *                           It may be {@code null} if the authorization
147   *                           identity should be the same as the authentication
148   *                           identity.  If an authorization identity is
149   *                           specified, it must be in the form "u:" followed
150   *                           by a username, or "dn:" followed by a DN.  The
151   *                           value "dn:" may be used to indicate the
152   *                           authorization identity of the anonymous user.
153   * @param  oneTimePassword   The one-time password that has been delivered to
154   *                           the user via the deliver one-time password
155   *                           extended request.  It must not be {@code null}.
156   * @param  controls          The set of controls to include in the bind
157   *                           request.  It may be {@code null} or empty if no
158   *                           controls should be included.
159   */
160  public UnboundIDDeliveredOTPBindRequest(final String authenticationID,
161                                          final String authorizationID,
162                                          final String oneTimePassword,
163                                          final Control... controls)
164  {
165    super(controls);
166
167    Validator.ensureNotNull(authenticationID);
168    Validator.ensureNotNull(oneTimePassword);
169
170    this.authenticationID = authenticationID;
171    this.authorizationID = authorizationID;
172    this.oneTimePassword  = oneTimePassword;
173  }
174
175
176
177  /**
178   * Creates a new delivered one-time password bind request from the information
179   * contained in the provided encoded SASL credentials.
180   *
181   * @param  saslCredentials  The encoded SASL credentials to be decoded in
182   *                          order to create this delivered one-time password
183   *                          bind request.  It must not be {@code null}.
184   * @param  controls         The set of controls to include in the bind
185   *                          request.  It may be {@code null} or empty if no
186   *                          controls should be included.
187   *
188   * @return  The delivered one-time password bind request decoded from the
189   *          provided credentials.
190   *
191   * @throws  LDAPException  If the provided credentials are not valid for an
192   *                         UNBOUNDID-DELIVERED-OTP bind request.
193   */
194  public static UnboundIDDeliveredOTPBindRequest
195              decodeSASLCredentials(final ASN1OctetString saslCredentials,
196                                    final Control... controls)
197         throws LDAPException
198  {
199    String          authenticationID = null;
200    String          authorizationID  = null;
201    String          oneTimePassword  = null;
202
203    try
204    {
205      final ASN1Sequence s =
206           ASN1Sequence.decodeAsSequence(saslCredentials.getValue());
207      for (final ASN1Element e : s.elements())
208      {
209        switch (e.getType())
210        {
211          case TYPE_AUTHENTICATION_ID:
212            authenticationID = e.decodeAsOctetString().stringValue();
213            break;
214          case TYPE_AUTHORIZATION_ID:
215            authorizationID = e.decodeAsOctetString().stringValue();
216            break;
217          case TYPE_OTP:
218            oneTimePassword = e.decodeAsOctetString().stringValue();
219            break;
220          default:
221            throw new LDAPException(ResultCode.DECODING_ERROR,
222                 ERR_DOTP_DECODE_INVALID_ELEMENT_TYPE.get(
223                      StaticUtils.toHex(e.getType())));
224        }
225      }
226    }
227    catch (final Exception e)
228    {
229      Debug.debugException(e);
230      throw new LDAPException(ResultCode.DECODING_ERROR,
231           ERR_DOTP_DECODE_ERROR.get(StaticUtils.getExceptionMessage(e)),
232           e);
233    }
234
235    if (authenticationID == null)
236    {
237      throw new LDAPException(ResultCode.DECODING_ERROR,
238           ERR_DOTP_DECODE_MISSING_AUTHN_ID.get());
239    }
240
241    if (oneTimePassword == null)
242    {
243      throw new LDAPException(ResultCode.DECODING_ERROR,
244           ERR_DOTP_DECODE_MISSING_OTP.get());
245    }
246
247    return new UnboundIDDeliveredOTPBindRequest(authenticationID,
248         authorizationID, oneTimePassword, controls);
249  }
250
251
252
253  /**
254   * Retrieves the authentication identity for the bind request.
255   *
256   * @return  The authentication identity for the bind request.
257   */
258  public String getAuthenticationID()
259  {
260    return authenticationID;
261  }
262
263
264
265  /**
266   * Retrieves the authorization identity for the bind request, if available.
267   *
268   * @return  The authorization identity for the bind request, or {@code null}
269   *          if the authorization identity should be the same as the
270   *          authentication identity.
271   */
272  public String getAuthorizationID()
273  {
274    return authorizationID;
275  }
276
277
278
279  /**
280   * Retrieves the one-time password for the bind request.
281   *
282   * @return  The one-time password for the bind request.
283   */
284  public String getOneTimePassword()
285  {
286    return oneTimePassword;
287  }
288
289
290
291  /**
292   * {@inheritDoc}
293   */
294  @Override()
295  protected BindResult process(final LDAPConnection connection, final int depth)
296            throws LDAPException
297  {
298    messageID = InternalSDKHelper.nextMessageID(connection);
299    return sendBindRequest(connection, "",
300         encodeCredentials(authenticationID, authorizationID, oneTimePassword),
301         getControls(), getResponseTimeoutMillis(connection));
302  }
303
304
305
306  /**
307   * Encodes the provided information into an ASN.1 octet string that may be
308   * used as the SASL credentials for an UnboundID delivered one-time password
309   * bind request.
310   *
311   * @param  authenticationID  The authentication identity for the bind request.
312   *                           It must not be {@code null} and must in the form
313   *                           "u:" followed by a username, or "dn:" followed
314   *                           by a DN.
315   * @param  authorizationID   The authorization identity for the bind request.
316   *                           It may be {@code null} if the authorization
317   *                           identity should be the same as the authentication
318   *                           identity.  If an authorization identity is
319   *                           specified, it must be in the form "u:" followed
320   *                           by a username, or "dn:" followed by a DN.  The
321   *                           value "dn:" may be used to indicate the
322   *                           authorization identity of the anonymous user.
323   * @param  oneTimePassword   The one-time password that has been delivered to
324   *                           the user via the deliver one-time password
325   *                           extended request.  It must not be {@code null}.
326   *
327   * @return  An ASN.1 octet string that may be used as the SASL credentials for
328   *          an UnboundID delivered one-time password bind request.
329   */
330  public static ASN1OctetString encodeCredentials(final String authenticationID,
331                                                  final String authorizationID,
332                                                  final String oneTimePassword)
333  {
334    Validator.ensureNotNull(authenticationID);
335    Validator.ensureNotNull(oneTimePassword);
336
337    final ArrayList<ASN1Element> elements = new ArrayList<>(3);
338    elements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID, authenticationID));
339
340    if (authorizationID != null)
341    {
342      elements.add(new ASN1OctetString(TYPE_AUTHORIZATION_ID, authorizationID));
343    }
344
345    elements.add(new ASN1OctetString(TYPE_OTP, oneTimePassword));
346    return new ASN1OctetString(new ASN1Sequence(elements).encode());
347  }
348
349
350
351  /**
352   * {@inheritDoc}
353   */
354  @Override()
355  public UnboundIDDeliveredOTPBindRequest duplicate()
356  {
357    return duplicate(getControls());
358  }
359
360
361
362  /**
363   * {@inheritDoc}
364   */
365  @Override()
366  public UnboundIDDeliveredOTPBindRequest duplicate(final Control[] controls)
367  {
368    final UnboundIDDeliveredOTPBindRequest bindRequest =
369         new UnboundIDDeliveredOTPBindRequest(authenticationID,
370              authorizationID, oneTimePassword, controls);
371    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
372    return bindRequest;
373  }
374
375
376
377  /**
378   * {@inheritDoc}
379   */
380  @Override()
381  public String getSASLMechanismName()
382  {
383    return UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME;
384  }
385
386
387
388  /**
389   * {@inheritDoc}
390   */
391  @Override()
392  public int getLastMessageID()
393  {
394    return messageID;
395  }
396
397
398
399  /**
400   * {@inheritDoc}
401   */
402  @Override()
403  public void toString(final StringBuilder buffer)
404  {
405    buffer.append("UnboundDeliveredOTPBindRequest(authID='");
406    buffer.append(authenticationID);
407    buffer.append("', ");
408
409    if (authorizationID != null)
410    {
411      buffer.append("authzID='");
412      buffer.append(authorizationID);
413      buffer.append("', ");
414    }
415
416    final Control[] controls = getControls();
417    if (controls.length > 0)
418    {
419      buffer.append(", controls={");
420      for (int i=0; i < controls.length; i++)
421      {
422        if (i > 0)
423        {
424          buffer.append(", ");
425        }
426
427        buffer.append(controls[i]);
428      }
429      buffer.append('}');
430    }
431
432    buffer.append(')');
433  }
434
435
436
437  /**
438   * {@inheritDoc}
439   */
440  @Override()
441  public void toCode(final List<String> lineList, final String requestID,
442                     final int indentSpaces, final boolean includeProcessing)
443  {
444    // Create the request variable.
445    final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(4);
446    constructorArgs.add(ToCodeArgHelper.createString(authenticationID,
447         "Authentication ID"));
448    constructorArgs.add(ToCodeArgHelper.createString(authorizationID,
449         "Authorization ID"));
450    constructorArgs.add(ToCodeArgHelper.createString("---redacted-otp---",
451         "One-Time Password"));
452
453    final Control[] controls = getControls();
454    if (controls.length > 0)
455    {
456      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
457           "Bind Controls"));
458    }
459
460    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
461         "UnboundIDDeliveredOTPBindRequest", requestID + "Request",
462         "new UnboundIDDeliveredOTPBindRequest", constructorArgs);
463
464
465    // Add lines for processing the request and obtaining the result.
466    if (includeProcessing)
467    {
468      // Generate a string with the appropriate indent.
469      final StringBuilder buffer = new StringBuilder();
470      for (int i=0; i < indentSpaces; i++)
471      {
472        buffer.append(' ');
473      }
474      final String indent = buffer.toString();
475
476      lineList.add("");
477      lineList.add(indent + "try");
478      lineList.add(indent + '{');
479      lineList.add(indent + "  BindResult " + requestID +
480           "Result = connection.bind(" + requestID + "Request);");
481      lineList.add(indent + "  // The bind was processed successfully.");
482      lineList.add(indent + '}');
483      lineList.add(indent + "catch (LDAPException e)");
484      lineList.add(indent + '{');
485      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
486           "help explain why.");
487      lineList.add(indent + "  // Note that the connection is now likely in " +
488           "an unauthenticated state.");
489      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
490      lineList.add(indent + "  String message = e.getMessage();");
491      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
492      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
493      lineList.add(indent + "  Control[] responseControls = " +
494           "e.getResponseControls();");
495      lineList.add(indent + '}');
496    }
497  }
498}