001/*
002 * Copyright 2013-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.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 Alcatel-Lucent 8661
064 *   server products.  These classes provide support for proprietary
065 *   functionality or for external specifications that are not considered stable
066 *   or mature enough to be guaranteed to work in an interoperable way with
067 *   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  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  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  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  // This is an ugly hack to prevent checkstyle from complaining about the
124  // import for the DeliverOneTimePasswordExtendedRequest class.  It is used
125  // by the @link element in the javadoc, but checkstyle apparently doesn't
126  // recognize that so we just need to use it in some way in this class to
127  // placate checkstyle.
128  static
129  {
130    final DeliverOneTimePasswordExtendedRequest r = null;
131  }
132
133
134
135  // The message ID from the last LDAP message sent from this request.
136  private volatile int messageID = -1;
137
138  // The authentication identity for the bind.
139  private final String authenticationID;
140
141  // The authorization identity for the bind, if provided.
142  private final String authorizationID;
143
144  // The one-time password for the bind, if provided.
145  private final String oneTimePassword;
146
147
148
149  /**
150   * Creates a new delivered one-time password bind request with the provided
151   * information.
152   *
153   * @param  authenticationID  The authentication identity for the bind request.
154   *                           It must not be {@code null} and must in the form
155   *                           "u:" followed by a username, or "dn:" followed
156   *                           by a DN.
157   * @param  authorizationID   The authorization identity for the bind request.
158   *                           It may be {@code null} if the authorization
159   *                           identity should be the same as the authentication
160   *                           identity.  If an authorization identity is
161   *                           specified, it must be in the form "u:" followed
162   *                           by a username, or "dn:" followed by a DN.  The
163   *                           value "dn:" may be used to indicate the
164   *                           authorization identity of the anonymous user.
165   * @param  oneTimePassword   The one-time password that has been delivered to
166   *                           the user via the deliver one-time password
167   *                           extended request.  It must not be {@code null}.
168   * @param  controls          The set of controls to include in the bind
169   *                           request.  It may be {@code null} or empty if no
170   *                           controls should be included.
171   */
172  public UnboundIDDeliveredOTPBindRequest(final String authenticationID,
173                                          final String authorizationID,
174                                          final String oneTimePassword,
175                                          final Control... controls)
176  {
177    super(controls);
178
179    Validator.ensureNotNull(authenticationID);
180    Validator.ensureNotNull(oneTimePassword);
181
182    this.authenticationID = authenticationID;
183    this.authorizationID = authorizationID;
184    this.oneTimePassword  = oneTimePassword;
185  }
186
187
188
189  /**
190   * Creates a new delivered one-time password bind request from the information
191   * contained in the provided encoded SASL credentials.
192   *
193   * @param  saslCredentials  The encoded SASL credentials to be decoded in
194   *                          order to create this delivered one-time password
195   *                          bind request.  It must not be {@code null}.
196   * @param  controls         The set of controls to include in the bind
197   *                          request.  It may be {@code null} or empty if no
198   *                          controls should be included.
199   *
200   * @return  The delivered one-time password bind request decoded from the
201   *          provided credentials.
202   *
203   * @throws  LDAPException  If the provided credentials are not valid for an
204   *                         UNBOUNDID-DELIVERED-OTP bind request.
205   */
206  public static UnboundIDDeliveredOTPBindRequest
207              decodeSASLCredentials(final ASN1OctetString saslCredentials,
208                                    final Control... controls)
209         throws LDAPException
210  {
211    String          authenticationID = null;
212    String          authorizationID  = null;
213    String          oneTimePassword  = null;
214
215    try
216    {
217      final ASN1Sequence s =
218           ASN1Sequence.decodeAsSequence(saslCredentials.getValue());
219      for (final ASN1Element e : s.elements())
220      {
221        switch (e.getType())
222        {
223          case TYPE_AUTHENTICATION_ID:
224            authenticationID = e.decodeAsOctetString().stringValue();
225            break;
226          case TYPE_AUTHORIZATION_ID:
227            authorizationID = e.decodeAsOctetString().stringValue();
228            break;
229          case TYPE_OTP:
230            oneTimePassword = e.decodeAsOctetString().stringValue();
231            break;
232          default:
233            throw new LDAPException(ResultCode.DECODING_ERROR,
234                 ERR_DOTP_DECODE_INVALID_ELEMENT_TYPE.get(
235                      StaticUtils.toHex(e.getType())));
236        }
237      }
238    }
239    catch (final Exception e)
240    {
241      Debug.debugException(e);
242      throw new LDAPException(ResultCode.DECODING_ERROR,
243           ERR_DOTP_DECODE_ERROR.get(StaticUtils.getExceptionMessage(e)),
244           e);
245    }
246
247    if (authenticationID == null)
248    {
249      throw new LDAPException(ResultCode.DECODING_ERROR,
250           ERR_DOTP_DECODE_MISSING_AUTHN_ID.get());
251    }
252
253    if (oneTimePassword == null)
254    {
255      throw new LDAPException(ResultCode.DECODING_ERROR,
256           ERR_DOTP_DECODE_MISSING_OTP.get());
257    }
258
259    return new UnboundIDDeliveredOTPBindRequest(authenticationID,
260         authorizationID, oneTimePassword, controls);
261  }
262
263
264
265  /**
266   * Retrieves the authentication identity for the bind request.
267   *
268   * @return  The authentication identity for the bind request.
269   */
270  public String getAuthenticationID()
271  {
272    return authenticationID;
273  }
274
275
276
277  /**
278   * Retrieves the authorization identity for the bind request, if available.
279   *
280   * @return  The authorization identity for the bind request, or {@code null}
281   *          if the authorization identity should be the same as the
282   *          authentication identity.
283   */
284  public String getAuthorizationID()
285  {
286    return authorizationID;
287  }
288
289
290
291  /**
292   * Retrieves the one-time password for the bind request.
293   *
294   * @return  The one-time password for the bind request.
295   */
296  public String getOneTimePassword()
297  {
298    return oneTimePassword;
299  }
300
301
302
303  /**
304   * {@inheritDoc}
305   */
306  @Override()
307  protected BindResult process(final LDAPConnection connection, final int depth)
308            throws LDAPException
309  {
310    messageID = InternalSDKHelper.nextMessageID(connection);
311    return sendBindRequest(connection, "",
312         encodeCredentials(authenticationID, authorizationID, oneTimePassword),
313         getControls(), getResponseTimeoutMillis(connection));
314  }
315
316
317
318  /**
319   * Encodes the provided information into an ASN.1 octet string that may be
320   * used as the SASL credentials for an UnboundID delivered one-time password
321   * bind request.
322   *
323   * @param  authenticationID  The authentication identity for the bind request.
324   *                           It must not be {@code null} and must in the form
325   *                           "u:" followed by a username, or "dn:" followed
326   *                           by a DN.
327   * @param  authorizationID   The authorization identity for the bind request.
328   *                           It may be {@code null} if the authorization
329   *                           identity should be the same as the authentication
330   *                           identity.  If an authorization identity is
331   *                           specified, it must be in the form "u:" followed
332   *                           by a username, or "dn:" followed by a DN.  The
333   *                           value "dn:" may be used to indicate the
334   *                           authorization identity of the anonymous user.
335   * @param  oneTimePassword   The one-time password that has been delivered to
336   *                           the user via the deliver one-time password
337   *                           extended request.  It must not be {@code null}.
338   *
339   * @return  An ASN.1 octet string that may be used as the SASL credentials for
340   *          an UnboundID delivered one-time password bind request.
341   */
342  public static ASN1OctetString encodeCredentials(final String authenticationID,
343                                                  final String authorizationID,
344                                                  final String oneTimePassword)
345  {
346    Validator.ensureNotNull(authenticationID);
347    Validator.ensureNotNull(oneTimePassword);
348
349    final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(3);
350    elements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID, authenticationID));
351
352    if (authorizationID != null)
353    {
354      elements.add(new ASN1OctetString(TYPE_AUTHORIZATION_ID, authorizationID));
355    }
356
357    elements.add(new ASN1OctetString(TYPE_OTP, oneTimePassword));
358    return new ASN1OctetString(new ASN1Sequence(elements).encode());
359  }
360
361
362
363  /**
364   * {@inheritDoc}
365   */
366  @Override()
367  public UnboundIDDeliveredOTPBindRequest duplicate()
368  {
369    return duplicate(getControls());
370  }
371
372
373
374  /**
375   * {@inheritDoc}
376   */
377  @Override()
378  public UnboundIDDeliveredOTPBindRequest duplicate(final Control[] controls)
379  {
380    final UnboundIDDeliveredOTPBindRequest bindRequest =
381         new UnboundIDDeliveredOTPBindRequest(authenticationID,
382              authorizationID, oneTimePassword, controls);
383    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
384    return bindRequest;
385  }
386
387
388
389  /**
390   * {@inheritDoc}
391   */
392  @Override()
393  public String getSASLMechanismName()
394  {
395    return UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME;
396  }
397
398
399
400  /**
401   * {@inheritDoc}
402   */
403  @Override()
404  public int getLastMessageID()
405  {
406    return messageID;
407  }
408
409
410
411  /**
412   * {@inheritDoc}
413   */
414  @Override()
415  public void toString(final StringBuilder buffer)
416  {
417    buffer.append("UnboundDeliveredOTPBindRequest(authID='");
418    buffer.append(authenticationID);
419    buffer.append("', ");
420
421    if (authorizationID != null)
422    {
423      buffer.append("authzID='");
424      buffer.append(authorizationID);
425      buffer.append("', ");
426    }
427
428    final Control[] controls = getControls();
429    if (controls.length > 0)
430    {
431      buffer.append(", controls={");
432      for (int i=0; i < controls.length; i++)
433      {
434        if (i > 0)
435        {
436          buffer.append(", ");
437        }
438
439        buffer.append(controls[i]);
440      }
441      buffer.append('}');
442    }
443
444    buffer.append(')');
445  }
446
447
448
449  /**
450   * {@inheritDoc}
451   */
452  @Override()
453  public void toCode(final List<String> lineList, final String requestID,
454                     final int indentSpaces, final boolean includeProcessing)
455  {
456    // Create the request variable.
457    final ArrayList<ToCodeArgHelper> constructorArgs =
458         new ArrayList<ToCodeArgHelper>(4);
459    constructorArgs.add(ToCodeArgHelper.createString(authenticationID,
460         "Authentication ID"));
461    constructorArgs.add(ToCodeArgHelper.createString(authorizationID,
462         "Authorization ID"));
463    constructorArgs.add(ToCodeArgHelper.createString("---redacted-otp---",
464         "One-Time Password"));
465
466    final Control[] controls = getControls();
467    if (controls.length > 0)
468    {
469      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
470           "Bind Controls"));
471    }
472
473    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
474         "UnboundIDDeliveredOTPBindRequest", requestID + "Request",
475         "new UnboundIDDeliveredOTPBindRequest", constructorArgs);
476
477
478    // Add lines for processing the request and obtaining the result.
479    if (includeProcessing)
480    {
481      // Generate a string with the appropriate indent.
482      final StringBuilder buffer = new StringBuilder();
483      for (int i=0; i < indentSpaces; i++)
484      {
485        buffer.append(' ');
486      }
487      final String indent = buffer.toString();
488
489      lineList.add("");
490      lineList.add(indent + "try");
491      lineList.add(indent + '{');
492      lineList.add(indent + "  BindResult " + requestID +
493           "Result = connection.bind(" + requestID + "Request);");
494      lineList.add(indent + "  // The bind was processed successfully.");
495      lineList.add(indent + '}');
496      lineList.add(indent + "catch (LDAPException e)");
497      lineList.add(indent + '{');
498      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
499           "help explain why.");
500      lineList.add(indent + "  // Note that the connection is now likely in " +
501           "an unauthenticated state.");
502      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
503      lineList.add(indent + "  String message = e.getMessage();");
504      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
505      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
506      lineList.add(indent + "  Control[] responseControls = " +
507           "e.getResponseControls();");
508      lineList.add(indent + '}');
509    }
510  }
511}