001/* 002 * Copyright 2012-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2012-2020 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2015-2020 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds; 037 038 039 040import java.util.ArrayList; 041import java.util.List; 042 043import com.unboundid.asn1.ASN1Element; 044import com.unboundid.asn1.ASN1OctetString; 045import com.unboundid.asn1.ASN1Sequence; 046import com.unboundid.ldap.sdk.Control; 047import com.unboundid.ldap.sdk.LDAPException; 048import com.unboundid.ldap.sdk.ResultCode; 049import com.unboundid.ldap.sdk.ToCodeArgHelper; 050import com.unboundid.ldap.sdk.ToCodeHelper; 051import com.unboundid.util.Debug; 052import com.unboundid.util.NotMutable; 053import com.unboundid.util.StaticUtils; 054import com.unboundid.util.ThreadSafety; 055import com.unboundid.util.ThreadSafetyLevel; 056import com.unboundid.util.Validator; 057 058import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*; 059 060 061 062/** 063 * This class provides an implementation of the UNBOUNDID-TOTP SASL bind request 064 * that contains a point-in-time version of the one-time password and can be 065 * used for a single bind but is not suitable for repeated use. This version of 066 * the bind request should be used for authentication in which the one-time 067 * password is provided by an external source rather than being generated by 068 * the LDAP SDK. 069 * <BR> 070 * <BLOCKQUOTE> 071 * <B>NOTE:</B> This class, and other classes within the 072 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 073 * supported for use against Ping Identity, UnboundID, and 074 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 075 * for proprietary functionality or for external specifications that are not 076 * considered stable or mature enough to be guaranteed to work in an 077 * interoperable way with other types of LDAP servers. 078 * </BLOCKQUOTE> 079 * <BR> 080 * Because the one-time password is provided rather than generated, this version 081 * of the bind request is not suitable for cases in which the authentication 082 * process may need to be repeated (e.g., for use in a connection pool, 083 * following referrals, or if the auto-reconnect feature is enabled), then the 084 * reusable variant (supported by the {@link ReusableTOTPBindRequest} class) 085 * which generates the one-time password should be used instead. 086 */ 087@NotMutable() 088@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 089public final class SingleUseTOTPBindRequest 090 extends UnboundIDTOTPBindRequest 091{ 092 /** 093 * The serial version UID for this serializable class. 094 */ 095 private static final long serialVersionUID = -4429898810534930296L; 096 097 098 099 // The hard-coded TOTP password to include in the bind request. 100 private final String totpPassword; 101 102 103 104 /** 105 * Creates a new SASL TOTP bind request with the provided information. 106 * 107 * @param authenticationID The authentication identity for the bind request. 108 * It must not be {@code null}, and must be in the 109 * form "u:" followed by a username, or "dn:" 110 * followed by a DN. 111 * @param authorizationID The authorization identity for the bind request. 112 * It may be {@code null} if the authorization 113 * identity should be the same as the authentication 114 * identity. If an authorization identity is 115 * specified, it must be in the form "u:" followed 116 * by a username, or "dn:" followed by a DN. The 117 * value "dn:" may indicate an authorization 118 * identity of the anonymous user. 119 * @param totpPassword The hard-coded TOTP password to include in the 120 * bind request. It must not be {@code null}. 121 * @param staticPassword The static password for the target user. It may 122 * be {@code null} if only the one-time password is 123 * to be used for authentication (which may or may 124 * not be allowed by the server). 125 * @param controls The set of controls to include in the bind 126 * request. 127 */ 128 public SingleUseTOTPBindRequest(final String authenticationID, 129 final String authorizationID, 130 final String totpPassword, 131 final String staticPassword, 132 final Control... controls) 133 { 134 super(authenticationID, authorizationID, staticPassword, controls); 135 136 Validator.ensureNotNull(totpPassword); 137 this.totpPassword = totpPassword; 138 } 139 140 141 142 /** 143 * Creates a new SASL TOTP bind request with the provided information. 144 * 145 * @param authenticationID The authentication identity for the bind request. 146 * It must not be {@code null}, and must be in the 147 * form "u:" followed by a username, or "dn:" 148 * followed by a DN. 149 * @param authorizationID The authorization identity for the bind request. 150 * It may be {@code null} if the authorization 151 * identity should be the same as the authentication 152 * identity. If an authorization identity is 153 * specified, it must be in the form "u:" followed 154 * by a username, or "dn:" followed by a DN. The 155 * value "dn:" may indicate an authorization 156 * identity of the anonymous user. 157 * @param totpPassword The hard-coded TOTP password to include in the 158 * bind request. It must not be {@code null}. 159 * @param staticPassword The static password for the target user. It may 160 * be {@code null} if only the one-time password is 161 * to be used for authentication (which may or may 162 * not be allowed by the server). 163 * @param controls The set of controls to include in the bind 164 * request. 165 */ 166 public SingleUseTOTPBindRequest(final String authenticationID, 167 final String authorizationID, 168 final String totpPassword, 169 final byte[] staticPassword, 170 final Control... controls) 171 { 172 super(authenticationID, authorizationID, staticPassword, controls); 173 174 Validator.ensureNotNull(totpPassword); 175 this.totpPassword = totpPassword; 176 } 177 178 179 180 /** 181 * Creates a new SASL TOTP bind request with the provided information. 182 * 183 * @param authenticationID The authentication identity for the bind request. 184 * It must not be {@code null}, and must be in the 185 * form "u:" followed by a username, or "dn:" 186 * followed by a DN. 187 * @param authorizationID The authorization identity for the bind request. 188 * It may be {@code null} if the authorization 189 * identity should be the same as the authentication 190 * identity. If an authorization identity is 191 * specified, it must be in the form "u:" followed 192 * by a username, or "dn:" followed by a DN. The 193 * value "dn:" may indicate an authorization 194 * identity of the anonymous user. 195 * @param totpPassword The hard-coded TOTP password to include in the 196 * bind request. It must not be {@code null}. 197 * @param staticPassword The static password for the target user. It may 198 * be {@code null} if only the one-time password is 199 * to be used for authentication (which may or may 200 * not be allowed by the server). 201 * @param controls The set of controls to include in the bind 202 * request. 203 */ 204 private SingleUseTOTPBindRequest(final String authenticationID, 205 final String authorizationID, 206 final String totpPassword, 207 final ASN1OctetString staticPassword, 208 final Control... controls) 209 { 210 super(authenticationID, authorizationID, staticPassword, controls); 211 212 Validator.ensureNotNull(totpPassword); 213 this.totpPassword = totpPassword; 214 } 215 216 217 218 /** 219 * Creates a new single-use TOTP bind request from the information contained 220 * in the provided encoded SASL credentials. 221 * 222 * @param saslCredentials The encoded SASL credentials to be decoded in 223 * order to create this single-use TOTP bind request. 224 * It must not be {@code null}. 225 * @param controls The set of controls to include in the bind 226 * request. 227 * 228 * @return The single-use TOTP bind request decoded from the provided 229 * credentials. 230 * 231 * @throws LDAPException If the provided credentials are not valid for an 232 * UNBOUNDID-TOTP bind request. 233 */ 234 public static SingleUseTOTPBindRequest 235 decodeSASLCredentials(final ASN1OctetString saslCredentials, 236 final Control... controls) 237 throws LDAPException 238 { 239 try 240 { 241 String authenticationID = null; 242 String authorizationID = null; 243 String totpPassword = null; 244 ASN1OctetString staticPassword = null; 245 246 final ASN1Sequence s = 247 ASN1Sequence.decodeAsSequence(saslCredentials.getValue()); 248 for (final ASN1Element e : s.elements()) 249 { 250 switch (e.getType()) 251 { 252 case TYPE_AUTHENTICATION_ID: 253 authenticationID = e.decodeAsOctetString().stringValue(); 254 break; 255 case TYPE_AUTHORIZATION_ID: 256 authorizationID = e.decodeAsOctetString().stringValue(); 257 break; 258 case TYPE_TOTP_PASSWORD: 259 totpPassword = e.decodeAsOctetString().stringValue(); 260 break; 261 case TYPE_STATIC_PASSWORD: 262 staticPassword = e.decodeAsOctetString(); 263 break; 264 default: 265 throw new LDAPException(ResultCode.DECODING_ERROR, 266 ERR_SINGLE_USE_TOTP_DECODE_INVALID_ELEMENT_TYPE.get( 267 StaticUtils.toHex(e.getType()))); 268 } 269 } 270 271 if (authenticationID == null) 272 { 273 throw new LDAPException(ResultCode.DECODING_ERROR, 274 ERR_SINGLE_USE_TOTP_DECODE_MISSING_AUTHN_ID.get()); 275 } 276 277 if (totpPassword == null) 278 { 279 throw new LDAPException(ResultCode.DECODING_ERROR, 280 ERR_SINGLE_USE_TOTP_DECODE_MISSING_TOTP_PW.get()); 281 } 282 283 return new SingleUseTOTPBindRequest(authenticationID, authorizationID, 284 totpPassword, staticPassword, controls); 285 } 286 catch (final Exception e) 287 { 288 Debug.debugException(e); 289 throw new LDAPException(ResultCode.DECODING_ERROR, 290 ERR_SINGLE_USE_TOTP_DECODE_ERROR.get( 291 StaticUtils.getExceptionMessage(e)), 292 e); 293 } 294 } 295 296 297 298 /** 299 * Retrieves the hard-coded TOTP password to include in the bind request. 300 * 301 * @return The hard-coded TOTP password to include in the bind request. 302 */ 303 public String getTOTPPassword() 304 { 305 return totpPassword; 306 } 307 308 309 310 /** 311 * {@inheritDoc} 312 */ 313 @Override() 314 protected ASN1OctetString getSASLCredentials() 315 { 316 return encodeCredentials(getAuthenticationID(), getAuthorizationID(), 317 totpPassword, getStaticPassword()); 318 } 319 320 321 322 /** 323 * {@inheritDoc} 324 */ 325 @Override() 326 public SingleUseTOTPBindRequest getRebindRequest(final String host, 327 final int port) 328 { 329 // Automatic rebinding is not supported for single-use TOTP binds. 330 return null; 331 } 332 333 334 335 /** 336 * {@inheritDoc} 337 */ 338 @Override() 339 public SingleUseTOTPBindRequest duplicate() 340 { 341 return duplicate(getControls()); 342 } 343 344 345 346 /** 347 * {@inheritDoc} 348 */ 349 @Override() 350 public SingleUseTOTPBindRequest duplicate(final Control[] controls) 351 { 352 final SingleUseTOTPBindRequest bindRequest = 353 new SingleUseTOTPBindRequest(getAuthenticationID(), 354 getAuthorizationID(), totpPassword, getStaticPassword(), 355 controls); 356 bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 357 return bindRequest; 358 } 359 360 361 362 /** 363 * {@inheritDoc} 364 */ 365 @Override() 366 public void toCode(final List<String> lineList, final String requestID, 367 final int indentSpaces, final boolean includeProcessing) 368 { 369 // Create the request variable. 370 final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(5); 371 constructorArgs.add(ToCodeArgHelper.createString(getAuthenticationID(), 372 "Authentication ID")); 373 constructorArgs.add(ToCodeArgHelper.createString(getAuthorizationID(), 374 "Authorization ID")); 375 constructorArgs.add(ToCodeArgHelper.createString( 376 "---redacted-totp-password", "TOTP Password")); 377 constructorArgs.add(ToCodeArgHelper.createString( 378 ((getStaticPassword() == null) 379 ? "null" 380 : "---redacted-static-password---"), 381 "Static Password")); 382 383 final Control[] controls = getControls(); 384 if (controls.length > 0) 385 { 386 constructorArgs.add(ToCodeArgHelper.createControlArray(controls, 387 "Bind Controls")); 388 } 389 390 ToCodeHelper.generateMethodCall(lineList, indentSpaces, 391 "SingleUseTOTPBindRequest", requestID + "Request", 392 "new SingleUseTOTPBindRequest", constructorArgs); 393 394 395 // Add lines for processing the request and obtaining the result. 396 if (includeProcessing) 397 { 398 // Generate a string with the appropriate indent. 399 final StringBuilder buffer = new StringBuilder(); 400 for (int i=0; i < indentSpaces; i++) 401 { 402 buffer.append(' '); 403 } 404 final String indent = buffer.toString(); 405 406 lineList.add(""); 407 lineList.add(indent + "try"); 408 lineList.add(indent + '{'); 409 lineList.add(indent + " BindResult " + requestID + 410 "Result = connection.bind(" + requestID + "Request);"); 411 lineList.add(indent + " // The bind was processed successfully."); 412 lineList.add(indent + '}'); 413 lineList.add(indent + "catch (LDAPException e)"); 414 lineList.add(indent + '{'); 415 lineList.add(indent + " // The bind failed. Maybe the following will " + 416 "help explain why."); 417 lineList.add(indent + " // Note that the connection is now likely in " + 418 "an unauthenticated state."); 419 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 420 lineList.add(indent + " String message = e.getMessage();"); 421 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 422 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 423 lineList.add(indent + " Control[] responseControls = " + 424 "e.getResponseControls();"); 425 lineList.add(indent + '}'); 426 } 427 } 428}