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 }