001    /*
002     * Copyright 2013-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2015 UnboundID Corp.
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     */
021    package com.unboundid.ldap.sdk.unboundidds;
022    
023    
024    
025    import java.util.ArrayList;
026    
027    import com.unboundid.asn1.ASN1Element;
028    import com.unboundid.asn1.ASN1OctetString;
029    import com.unboundid.asn1.ASN1Sequence;
030    import com.unboundid.ldap.sdk.BindResult;
031    import com.unboundid.ldap.sdk.Control;
032    import com.unboundid.ldap.sdk.InternalSDKHelper;
033    import com.unboundid.ldap.sdk.LDAPConnection;
034    import com.unboundid.ldap.sdk.LDAPException;
035    import com.unboundid.ldap.sdk.ResultCode;
036    import com.unboundid.ldap.sdk.SASLBindRequest;
037    import com.unboundid.ldap.sdk.unboundidds.extensions.
038                DeliverOneTimePasswordExtendedRequest;
039    import com.unboundid.util.Debug;
040    import com.unboundid.util.NotMutable;
041    import com.unboundid.util.StaticUtils;
042    import com.unboundid.util.ThreadSafety;
043    import com.unboundid.util.ThreadSafetyLevel;
044    import com.unboundid.util.Validator;
045    
046    import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
047    
048    
049    
050    /**
051     * <BLOCKQUOTE>
052     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
053     *   LDAP SDK for Java.  It is not available for use in applications that
054     *   include only the Standard Edition of the LDAP SDK, and is not supported for
055     *   use in conjunction with non-UnboundID products.
056     * </BLOCKQUOTE>
057     * This class provides support for an UnboundID-proprietary SASL mechanism that
058     * allows for multifactor authentication using a one-time password that has been
059     * delivered to the user via some out-of-band mechanism as triggered by the
060     * {@link DeliverOneTimePasswordExtendedRequest} (which requires the user to
061     * provide an authentication ID and a static password).
062     * <BR><BR>
063     * The name for this SASL mechanism is "UNBOUNDID-DELIVERED-OTP".  An
064     * UNBOUNDID-DELIVERED-OTP SASL bind request MUST include SASL credentials with
065     * the following ASN.1 encoding:
066     * <BR><BR>
067     * <PRE>
068     *   UnboundIDDeliveredOTPCredentials ::= SEQUENCE {
069     *        authenticationID     [0] OCTET STRING,
070     *        authorizationID      [1] OCTET STRING OPTIONAL.
071     *        oneTimePassword      [2] OCTET STRING,
072     *        ... }
073     * </PRE>
074     */
075    @NotMutable()
076    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
077    public final class UnboundIDDeliveredOTPBindRequest
078           extends SASLBindRequest
079    {
080      /**
081       * The name for the UnboundID delivered OTP SASL mechanism.
082       */
083      public static final String UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME =
084           "UNBOUNDID-DELIVERED-OTP";
085    
086    
087    
088      /**
089       * The BER type for the authentication ID included in the request.
090       */
091      static final byte TYPE_AUTHENTICATION_ID = (byte) 0x80;
092    
093    
094    
095      /**
096       * The BER type for the authorization ID included in the request.
097       */
098      static final byte TYPE_AUTHORIZATION_ID = (byte) 0x81;
099    
100    
101    
102      /**
103       * The BER type for the one-time password included in the request.
104       */
105      static final byte TYPE_OTP = (byte) 0x82;
106    
107    
108    
109      /**
110       * The serial version UID for this serializable class.
111       */
112      private static final long serialVersionUID = 8148101285676071058L;
113    
114    
115    
116      // This is an ugly hack to prevent checkstyle from complaining about the
117      // import for the DeliverOneTimePasswordExtendedRequest class.  It is used
118      // by the @link element in the javadoc, but checkstyle apparently doesn't
119      // recognize that so we just need to use it in some way in this class to
120      // placate checkstyle.
121      static
122      {
123        final DeliverOneTimePasswordExtendedRequest r = null;
124      }
125    
126    
127    
128      // The message ID from the last LDAP message sent from this request.
129      private volatile int messageID = -1;
130    
131      // The authentication identity for the bind.
132      private final String authenticationID;
133    
134      // The authorization identity for the bind, if provided.
135      private final String authorizationID;
136    
137      // The one-time password for the bind, if provided.
138      private final String oneTimePassword;
139    
140    
141    
142      /**
143       * Creates a new delivered one-time password bind request with the provided
144       * information.
145       *
146       * @param  authenticationID  The authentication identity for the bind request.
147       *                           It must not be {@code null} and must in the form
148       *                           "u:" followed by a username, or "dn:" followed
149       *                           by a DN.
150       * @param  authorizationID   The authorization identity for the bind request.
151       *                           It may be {@code null} if the authorization
152       *                           identity should be the same as the authentication
153       *                           identity.  If an authorization identity is
154       *                           specified, it must be in the form "u:" followed
155       *                           by a username, or "dn:" followed by a DN.  The
156       *                           value "dn:" may be used to indicate the
157       *                           authorization identity of the anonymous user.
158       * @param  oneTimePassword   The one-time password that has been delivered to
159       *                           the user via the deliver one-time password
160       *                           extended request.  It must not be {@code null}.
161       * @param  controls          The set of controls to include in the bind
162       *                           request.  It may be {@code null} or empty if no
163       *                           controls should be included.
164       */
165      public UnboundIDDeliveredOTPBindRequest(final String authenticationID,
166                                              final String authorizationID,
167                                              final String oneTimePassword,
168                                              final Control... controls)
169      {
170        super(controls);
171    
172        Validator.ensureNotNull(authenticationID);
173        Validator.ensureNotNull(oneTimePassword);
174    
175        this.authenticationID = authenticationID;
176        this.authorizationID = authorizationID;
177        this.oneTimePassword  = oneTimePassword;
178      }
179    
180    
181    
182      /**
183       * Creates a new delivered one-time password bind request from the information
184       * contained in the provided encoded SASL credentials.
185       *
186       * @param  saslCredentials  The encoded SASL credentials to be decoded in
187       *                          order to create this delivered one-time password
188       *                          bind request.  It must not be {@code null}.
189       * @param  controls         The set of controls to include in the bind
190       *                          request.  It may be {@code null} or empty if no
191       *                          controls should be included.
192       *
193       * @return  The delivered one-time password bind request decoded from the
194       *          provided credentials.
195       *
196       * @throws  LDAPException  If the provided credentials are not valid for an
197       *                         UNBOUNDID-DELIVERED-OTP bind request.
198       */
199      public static UnboundIDDeliveredOTPBindRequest
200                  decodeSASLCredentials(final ASN1OctetString saslCredentials,
201                                        final Control... controls)
202             throws LDAPException
203      {
204        String          authenticationID = null;
205        String          authorizationID  = null;
206        String          oneTimePassword  = null;
207    
208        try
209        {
210          final ASN1Sequence s =
211               ASN1Sequence.decodeAsSequence(saslCredentials.getValue());
212          for (final ASN1Element e : s.elements())
213          {
214            switch (e.getType())
215            {
216              case TYPE_AUTHENTICATION_ID:
217                authenticationID = e.decodeAsOctetString().stringValue();
218                break;
219              case TYPE_AUTHORIZATION_ID:
220                authorizationID = e.decodeAsOctetString().stringValue();
221                break;
222              case TYPE_OTP:
223                oneTimePassword = e.decodeAsOctetString().stringValue();
224                break;
225              default:
226                throw new LDAPException(ResultCode.DECODING_ERROR,
227                     ERR_DOTP_DECODE_INVALID_ELEMENT_TYPE.get(
228                          StaticUtils.toHex(e.getType())));
229            }
230          }
231        }
232        catch (final Exception e)
233        {
234          Debug.debugException(e);
235          throw new LDAPException(ResultCode.DECODING_ERROR,
236               ERR_DOTP_DECODE_ERROR.get(StaticUtils.getExceptionMessage(e)),
237               e);
238        }
239    
240        if (authenticationID == null)
241        {
242          throw new LDAPException(ResultCode.DECODING_ERROR,
243               ERR_DOTP_DECODE_MISSING_AUTHN_ID.get());
244        }
245    
246        if (oneTimePassword == null)
247        {
248          throw new LDAPException(ResultCode.DECODING_ERROR,
249               ERR_DOTP_DECODE_MISSING_OTP.get());
250        }
251    
252        return new UnboundIDDeliveredOTPBindRequest(authenticationID,
253             authorizationID, oneTimePassword, controls);
254      }
255    
256    
257    
258      /**
259       * Retrieves the authentication identity for the bind request.
260       *
261       * @return  The authentication identity for the bind request.
262       */
263      public String getAuthenticationID()
264      {
265        return authenticationID;
266      }
267    
268    
269    
270      /**
271       * Retrieves the authorization identity for the bind request, if available.
272       *
273       * @return  The authorization identity for the bind request, or {@code null}
274       *          if the authorization identity should be the same as the
275       *          authentication identity.
276       */
277      public String getAuthorizationID()
278      {
279        return authorizationID;
280      }
281    
282    
283    
284      /**
285       * Retrieves the one-time password for the bind request.
286       *
287       * @return  The one-time password for the bind request.
288       */
289      public String getOneTimePassword()
290      {
291        return oneTimePassword;
292      }
293    
294    
295    
296      /**
297       * {@inheritDoc}
298       */
299      @Override()
300      protected BindResult process(final LDAPConnection connection, final int depth)
301                throws LDAPException
302      {
303        messageID = InternalSDKHelper.nextMessageID(connection);
304        return sendBindRequest(connection, "",
305             encodeCredentials(authenticationID, authorizationID, oneTimePassword),
306             getControls(), getResponseTimeoutMillis(connection));
307      }
308    
309    
310    
311      /**
312       * Encodes the provided information into an ASN.1 octet string that may be
313       * used as the SASL credentials for an UnboundID delivered one-time password
314       * bind request.
315       *
316       * @param  authenticationID  The authentication identity for the bind request.
317       *                           It must not be {@code null} and must in the form
318       *                           "u:" followed by a username, or "dn:" followed
319       *                           by a DN.
320       * @param  authorizationID   The authorization identity for the bind request.
321       *                           It may be {@code null} if the authorization
322       *                           identity should be the same as the authentication
323       *                           identity.  If an authorization identity is
324       *                           specified, it must be in the form "u:" followed
325       *                           by a username, or "dn:" followed by a DN.  The
326       *                           value "dn:" may be used to indicate the
327       *                           authorization identity of the anonymous user.
328       * @param  oneTimePassword   The one-time password that has been delivered to
329       *                           the user via the deliver one-time password
330       *                           extended request.  It must not be {@code null}.
331       *
332       * @return  An ASN.1 octet string that may be used as the SASL credentials for
333       *          an UnboundID delivered one-time password bind request.
334       */
335      public static ASN1OctetString encodeCredentials(final String authenticationID,
336                                                      final String authorizationID,
337                                                      final String oneTimePassword)
338      {
339        Validator.ensureNotNull(authenticationID);
340        Validator.ensureNotNull(oneTimePassword);
341    
342        final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(3);
343        elements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID, authenticationID));
344    
345        if (authorizationID != null)
346        {
347          elements.add(new ASN1OctetString(TYPE_AUTHORIZATION_ID, authorizationID));
348        }
349    
350        elements.add(new ASN1OctetString(TYPE_OTP, oneTimePassword));
351        return new ASN1OctetString(new ASN1Sequence(elements).encode());
352      }
353    
354    
355    
356      /**
357       * {@inheritDoc}
358       */
359      @Override()
360      public UnboundIDDeliveredOTPBindRequest duplicate()
361      {
362        return duplicate(getControls());
363      }
364    
365    
366    
367      /**
368       * {@inheritDoc}
369       */
370      @Override()
371      public UnboundIDDeliveredOTPBindRequest duplicate(final Control[] controls)
372      {
373        final UnboundIDDeliveredOTPBindRequest bindRequest =
374             new UnboundIDDeliveredOTPBindRequest(authenticationID,
375                  authorizationID, oneTimePassword, controls);
376        bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
377        return bindRequest;
378      }
379    
380    
381    
382      /**
383       * {@inheritDoc}
384       */
385      @Override()
386      public String getSASLMechanismName()
387      {
388        return UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME;
389      }
390    
391    
392    
393      /**
394       * {@inheritDoc}
395       */
396      @Override()
397      public int getLastMessageID()
398      {
399        return messageID;
400      }
401    
402    
403    
404      /**
405       * {@inheritDoc}
406       */
407      @Override()
408      public void toString(final StringBuilder buffer)
409      {
410        buffer.append("UnboundDeliveredOTPBindRequest(authID='");
411        buffer.append(authenticationID);
412        buffer.append("', ");
413    
414        if (authorizationID != null)
415        {
416          buffer.append("authzID='");
417          buffer.append(authorizationID);
418          buffer.append("', ");
419        }
420    
421        final Control[] controls = getControls();
422        if (controls.length > 0)
423        {
424          buffer.append(", controls={");
425          for (int i=0; i < controls.length; i++)
426          {
427            if (i > 0)
428            {
429              buffer.append(", ");
430            }
431    
432            buffer.append(controls[i]);
433          }
434          buffer.append('}');
435        }
436    
437        buffer.append(')');
438      }
439    }