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 }