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