001/* 002 * Copyright 2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2019 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.extensions; 022 023 024 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.Iterator; 028import java.util.List; 029 030import com.unboundid.asn1.ASN1Element; 031import com.unboundid.asn1.ASN1OctetString; 032import com.unboundid.asn1.ASN1Sequence; 033import com.unboundid.ldap.sdk.Control; 034import com.unboundid.ldap.sdk.ExtendedResult; 035import com.unboundid.ldap.sdk.LDAPException; 036import com.unboundid.ldap.sdk.ResultCode; 037import com.unboundid.util.Debug; 038import com.unboundid.util.NotMutable; 039import com.unboundid.util.StaticUtils; 040import com.unboundid.util.ThreadSafety; 041import com.unboundid.util.ThreadSafetyLevel; 042import com.unboundid.util.Validator; 043 044import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*; 045 046 047 048/** 049 * This class provides an implementation of an extended result that may be used 050 * provide the client with the passwords generated by the server in response to 051 * a {@link GeneratePasswordExtendedRequest}. 052 * <BR> 053 * <BLOCKQUOTE> 054 * <B>NOTE:</B> This class, and other classes within the 055 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 056 * supported for use against Ping Identity, UnboundID, and 057 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 058 * for proprietary functionality or for external specifications that are not 059 * considered stable or mature enough to be guaranteed to work in an 060 * interoperable way with other types of LDAP servers. 061 * </BLOCKQUOTE> 062 * <BR> 063 * If the extended request was processed successfully, then this result will 064 * have an OID of 1.3.6.1.4.1.30221.2.6.63 and a value with the following 065 * encoding: 066 * <BR><BR> 067 * <PRE> 068 * GeneratePasswordResponse ::= SEQUENCE { 069 * passwordPolicyDN LDAPDN, 070 * generatedPasswords SEQUENCE OF SEQUENCE { 071 * generatedPassword OCTET STRING, 072 * validationAttempted BOOLEAN, 073 * validationErrors [0] SEQUENCE OF OCTET STRING OPTIONAL, 074 * ... }, 075 * ... } 076 * </PRE> 077 * <BR><BR> 078 * The elements of the response value are: 079 * <UL> 080 * <LI>passwordPolicyDN -- The DN of the password policy that was used to 081 * select the password generator and validators used in the course of 082 * creating the passwords.</LI> 083 * <LI>generatedPassword -- A clear-text password that was generated by the 084 * server.</LI> 085 * <LI>validationAttempted -- Indicates whether the server attempted to 086 * perform any validation for the generated password.</LI> 087 * <LI>validationErrors -- A list of messages describing any problems 088 * that were identified while validating the generated password.</LI> 089 * </UL> 090 */ 091@NotMutable() 092@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 093public final class GeneratePasswordExtendedResult 094 extends ExtendedResult 095{ 096 /** 097 * The OID (1.3.6.1.4.1.30221.2.6.57) for the generate TOTP shared secret 098 * extended result. 099 */ 100 public static final String GENERATE_PASSWORD_RESULT_OID = 101 "1.3.6.1.4.1.30221.2.6.63"; 102 103 104 105 /** 106 * The serial version UID for this serializable class. 107 */ 108 private static final long serialVersionUID = -6840636721723079194L; 109 110 111 112 // The list of generated passwords returned by the server. 113 private final List<GeneratedPassword> generatedPasswords; 114 115 // The base32-encoded representation TOTP shared secret generated by the 116 // server. 117 private final String passwordPolicyDN; 118 119 120 121 /** 122 * Creates a new generate password extended result that indicates successful 123 * processing with the provided information. 124 * 125 * @param messageID The message ID for the LDAP message that is 126 * associated with this LDAP result. 127 * @param passwordPolicyDN The DN of the password policy that was used in 128 * in the course of generating the password. It 129 * must not be {@code null}. 130 * @param generatedPasswords The list of generated passwords. It must not 131 * be {@code null} or empty. 132 * @param controls An optional set of controls for the response, 133 * if any. It may be {@code null} or empty if no 134 * controls are needed. 135 */ 136 public GeneratePasswordExtendedResult(final int messageID, 137 final String passwordPolicyDN, 138 final List<GeneratedPassword> generatedPasswords, 139 final Control... controls) 140 { 141 this(messageID, ResultCode.SUCCESS, null, null, null, passwordPolicyDN, 142 generatedPasswords, controls); 143 } 144 145 146 147 /** 148 * Creates a new generate password extended result with the provided 149 * information. 150 * 151 * @param messageID The message ID for the LDAP message that is 152 * associated with this LDAP result. 153 * @param resultCode The result code for the response. It must not 154 * be {@code null}. 155 * @param diagnosticMessage The diagnostic message for the response. It 156 * may be {@code null} if none is needed. 157 * @param matchedDN The matched DN for the response. It may be 158 * {@code null} if none is needed. 159 * @param referralURLs The set of referral URLs for the response. It 160 * may be {@code null} or empty if none are 161 * needed. 162 * @param passwordPolicyDN The DN of the password policy that was used in 163 * in the course of generating the password. It 164 * must not be {@code null} for a successful 165 * result, but must be {@code null} for a 166 * non-successful result. 167 * @param generatedPasswords The list of generated passwords. It must not 168 * be {@code null} or empty for a successful 169 * result, but must be {@code null} or empty for a 170 * non-successful result. 171 * @param controls An optional set of controls for the response, 172 * if any. It may be {@code null} or empty if no 173 * controls are needed. 174 */ 175 public GeneratePasswordExtendedResult(final int messageID, 176 final ResultCode resultCode, final String diagnosticMessage, 177 final String matchedDN, final String[] referralURLs, 178 final String passwordPolicyDN, 179 final List<GeneratedPassword> generatedPasswords, 180 final Control... controls) 181 { 182 super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs, 183 (resultCode == ResultCode.SUCCESS 184 ? GENERATE_PASSWORD_RESULT_OID 185 : null), 186 (resultCode == ResultCode.SUCCESS 187 ? encodeValue(passwordPolicyDN, generatedPasswords) 188 : null), 189 controls); 190 191 this.passwordPolicyDN = passwordPolicyDN; 192 193 if (resultCode == ResultCode.SUCCESS) 194 { 195 this.generatedPasswords = Collections.unmodifiableList( 196 new ArrayList<>(generatedPasswords)); 197 } 198 else 199 { 200 Validator.ensureTrue((passwordPolicyDN == null), 201 "GeneratePasswordExtendedResult.passwordPolicyDN must be null for " + 202 "a non-success result."); 203 Validator.ensureTrue( 204 ((generatedPasswords == null) || generatedPasswords.isEmpty()), 205 "GeneratePasswordExtendedResult.generatedPasswords must be null " + 206 "or empty for a non-success result."); 207 208 this.generatedPasswords = Collections.emptyList(); 209 } 210 } 211 212 213 214 /** 215 * Creates an ASN.1 octet string that is suitable for the value of a 216 * successful generate password extended result. 217 * 218 * @param passwordPolicyDN The DN of the password policy that was used in 219 * in the course of generating the password. It 220 * must not be {@code null} for a successful 221 * result, but must be {@code null} for a 222 * non-successful result. 223 * @param generatedPasswords The list of generated passwords. It must not 224 * be {@code null} or empty for a successful 225 * result, but must be {@code null} or empty for a 226 * non-successful result. 227 * 228 * @return The ASN.1 octet string that was created. 229 */ 230 private static ASN1OctetString encodeValue(final String passwordPolicyDN, 231 final List<GeneratedPassword> generatedPasswords) 232 { 233 Validator.ensureNotNullOrEmpty(passwordPolicyDN, 234 "GeneratePasswordExtendedResult.passwordPolicyDN must not be null " + 235 "or empty in a success result."); 236 Validator.ensureNotNullOrEmpty(generatedPasswords, 237 "GeneratePasswordExtendedResult.generatedPasswords must not be null " + 238 "or empty in a success result."); 239 240 final List<ASN1Element> passwordElements = 241 new ArrayList<>(generatedPasswords.size()); 242 for (final GeneratedPassword p : generatedPasswords) 243 { 244 passwordElements.add(p.encode()); 245 } 246 247 final ASN1Sequence valueSequence = new ASN1Sequence( 248 new ASN1OctetString(passwordPolicyDN), 249 new ASN1Sequence(passwordElements)); 250 251 return new ASN1OctetString(valueSequence.encode()); 252 } 253 254 255 256 /** 257 * Creates a new generate password extended result from the provided extended 258 * result. 259 * 260 * @param extendedResult The extended result to be decoded as a generate 261 * password extended result. It must not be 262 * {@code null}. 263 * 264 * @throws LDAPException If the provided extended result cannot be decoded 265 * as a generate password result. 266 */ 267 public GeneratePasswordExtendedResult(final ExtendedResult extendedResult) 268 throws LDAPException 269 { 270 super(extendedResult); 271 272 final ASN1OctetString value = extendedResult.getValue(); 273 if (value == null) 274 { 275 if (extendedResult.getResultCode() == ResultCode.SUCCESS) 276 { 277 throw new LDAPException(ResultCode.DECODING_ERROR, 278 ERR_GENERATE_PASSWORD_RESULT_SUCCESS_MISSING_VALUE.get()); 279 } 280 281 passwordPolicyDN = null; 282 generatedPasswords = Collections.emptyList(); 283 return; 284 } 285 286 if (extendedResult.getResultCode() != ResultCode.SUCCESS) 287 { 288 throw new LDAPException(ResultCode.DECODING_ERROR, 289 ERR_GENERATE_PASSWORD_RESULT_NON_SUCCESS_WITH_VALUE.get( 290 String.valueOf(extendedResult.getResultCode()))); 291 } 292 293 try 294 { 295 final ASN1Element[] valueElements = 296 ASN1Sequence.decodeAsSequence(value.getValue()).elements(); 297 passwordPolicyDN = 298 ASN1OctetString.decodeAsOctetString(valueElements[0]).stringValue(); 299 300 final ASN1Element[] pwElements = 301 ASN1Sequence.decodeAsSequence(valueElements[1]).elements(); 302 final List<GeneratedPassword> pwList = 303 new ArrayList<>(pwElements.length); 304 for (final ASN1Element e : pwElements) 305 { 306 pwList.add(GeneratedPassword.decode(e)); 307 } 308 309 if (pwList.isEmpty()) 310 { 311 throw new LDAPException(ResultCode.DECODING_ERROR, 312 ERR_GENERATE_PASSWORD_RESULT_DECODE_NO_PASSWORDS.get()); 313 } 314 315 generatedPasswords = Collections.unmodifiableList(pwList); 316 } 317 catch (final LDAPException e) 318 { 319 Debug.debugException(e); 320 throw e; 321 } 322 catch (final Exception e) 323 { 324 Debug.debugException(e); 325 throw new LDAPException(ResultCode.DECODING_ERROR, 326 ERR_GENERATE_PASSWORD_RESULT_DECODING_ERROR.get( 327 StaticUtils.getExceptionMessage(e)), 328 e); 329 } 330 } 331 332 333 334 /** 335 * Retrieves the DN of the password policy that was used in the course of 336 * generating and validating the passwords. 337 * 338 * @return The DN of the password policy that was used in the course of 339 * generating and validating the passwords, or {@code null} if the 340 * operation was not processed successfully. 341 */ 342 public String getPasswordPolicyDN() 343 { 344 return passwordPolicyDN; 345 } 346 347 348 349 /** 350 * Retrieves the list of passwords that were generated by the server. 351 * 352 * @return The list of passwords that were generated by the server, or an 353 * empty list if the operation was not processed successfully. 354 */ 355 public List<GeneratedPassword> getGeneratedPasswords() 356 { 357 return generatedPasswords; 358 } 359 360 361 362 /** 363 * {@inheritDoc} 364 */ 365 @Override() 366 public String getExtendedResultName() 367 { 368 return INFO_GENERATE_PASSWORD_RESULT_NAME.get(); 369 } 370 371 372 373 /** 374 * Appends a string representation of this extended result to the provided 375 * buffer. 376 * 377 * @param buffer The buffer to which a string representation of this 378 * extended result will be appended. 379 */ 380 @Override() 381 public void toString(final StringBuilder buffer) 382 { 383 buffer.append("GeneratePasswordExtendedResult(resultCode="); 384 buffer.append(getResultCode()); 385 386 final int messageID = getMessageID(); 387 if (messageID >= 0) 388 { 389 buffer.append(", messageID="); 390 buffer.append(messageID); 391 } 392 393 final String diagnosticMessage = getDiagnosticMessage(); 394 if (diagnosticMessage != null) 395 { 396 buffer.append(", diagnosticMessage='"); 397 buffer.append(diagnosticMessage); 398 buffer.append('\''); 399 } 400 401 final String matchedDN = getMatchedDN(); 402 if (matchedDN != null) 403 { 404 buffer.append(", matchedDN='"); 405 buffer.append(matchedDN); 406 buffer.append('\''); 407 } 408 409 final String[] referralURLs = getReferralURLs(); 410 if (referralURLs.length > 0) 411 { 412 buffer.append(", referralURLs={"); 413 for (int i=0; i < referralURLs.length; i++) 414 { 415 if (i > 0) 416 { 417 buffer.append(", "); 418 } 419 420 buffer.append('\''); 421 buffer.append(referralURLs[i]); 422 buffer.append('\''); 423 } 424 buffer.append('}'); 425 } 426 427 if (passwordPolicyDN != null) 428 { 429 buffer.append(", passwordPolicyDN='"); 430 buffer.append(passwordPolicyDN); 431 buffer.append('\''); 432 } 433 434 if (! generatedPasswords.isEmpty()) 435 { 436 buffer.append(", generatedPasswords={ "); 437 438 final Iterator<GeneratedPassword> iterator = 439 generatedPasswords.iterator(); 440 while (iterator.hasNext()) 441 { 442 iterator.next().toString(buffer); 443 444 if (iterator.hasNext()) 445 { 446 buffer.append(", "); 447 } 448 } 449 450 buffer.append(" }"); 451 } 452 453 final Control[] responseControls = getResponseControls(); 454 if (responseControls.length > 0) 455 { 456 buffer.append(", responseControls={"); 457 for (int i=0; i < responseControls.length; i++) 458 { 459 if (i > 0) 460 { 461 buffer.append(", "); 462 } 463 464 buffer.append(responseControls[i]); 465 } 466 buffer.append('}'); 467 } 468 469 buffer.append(')'); 470 } 471}