001/* 002 * Copyright 2019-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2019-2022 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-2022 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.controls; 037 038 039 040import java.util.ArrayList; 041 042import com.unboundid.asn1.ASN1Boolean; 043import com.unboundid.asn1.ASN1Element; 044import com.unboundid.asn1.ASN1Long; 045import com.unboundid.asn1.ASN1OctetString; 046import com.unboundid.asn1.ASN1Sequence; 047import com.unboundid.ldap.sdk.Control; 048import com.unboundid.ldap.sdk.DecodeableControl; 049import com.unboundid.ldap.sdk.LDAPException; 050import com.unboundid.ldap.sdk.LDAPResult; 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; 059 060import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 061 062 063 064/** 065 * This class provides a response control that may be used to convey the 066 * password (and other associated information) generated in response to a 067 * {@link GeneratePasswordRequestControl}. 068 * <BR> 069 * <BLOCKQUOTE> 070 * <B>NOTE:</B> This class, and other classes within the 071 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 072 * supported for use against Ping Identity, UnboundID, and 073 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 074 * for proprietary functionality or for external specifications that are not 075 * considered stable or mature enough to be guaranteed to work in an 076 * interoperable way with other types of LDAP servers. 077 * </BLOCKQUOTE> 078 * <BR> 079 * This control has an OID of "1.3.6.1.4.1.30221.2.5.59", a criticality of 080 * false, and a value with the following encoding: 081 * <PRE> 082 * GeneratePasswordResponse ::= SEQUENCE { 083 * generatedPassword OCTET STRING, 084 * mustChangePassword BOOLEAN, 085 * secondsUntilExpiration [0] INTEGER OPTIONAL, 086 * ... } 087 * </PRE> 088 * 089 * @see GeneratePasswordRequestControl 090 */ 091@NotMutable() 092@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 093public final class GeneratePasswordResponseControl 094 extends Control 095 implements DecodeableControl 096{ 097 /** 098 * The OID (1.3.6.1.4.1.30221.2.5.59) for the generate password response 099 * control. 100 */ 101 @NotNull public static final String GENERATE_PASSWORD_RESPONSE_OID = 102 "1.3.6.1.4.1.30221.2.5.59"; 103 104 105 106 /** 107 * The BER type for the {@code secondsUntilExpiration} element. 108 */ 109 private static final byte TYPE_SECONDS_UNTIL_EXPIRATION = (byte) 0x80; 110 111 112 113 /** 114 * The serial version UID for this serializable class. 115 */ 116 private static final long serialVersionUID = 7542512192838228238L; 117 118 119 120 // The generated password included in the control. 121 @NotNull private final ASN1OctetString generatedPassword; 122 123 // Indicates whether the user will be required to choose a new password the 124 // first time they authenticate. 125 private final boolean mustChangePassword; 126 127 // The number of seconds until the new password will expire. 128 @Nullable private final Long secondsUntilExpiration; 129 130 131 132 /** 133 * Creates a new empty control instance that is intended to be used only for 134 * decoding controls via the {@code DecodeableControl} interface. 135 */ 136 GeneratePasswordResponseControl() 137 { 138 generatedPassword = null; 139 mustChangePassword = false; 140 secondsUntilExpiration = null; 141 } 142 143 144 145 /** 146 * Creates a new generate password response control with the provided 147 * information. 148 * 149 * @param generatedPassword The password generated by the server. It 150 * must not be {@code null}. 151 * @param mustChangePassword Indicates whether the user will be required 152 * to choose a new password the first time 153 * they authenticate. 154 * @param secondsUntilExpiration The number of seconds until the new 155 * password will expire. It may be 156 * {@code null} if the new password will not 157 * expire. 158 */ 159 public GeneratePasswordResponseControl( 160 @NotNull final String generatedPassword, 161 final boolean mustChangePassword, 162 @Nullable final Long secondsUntilExpiration) 163 { 164 this(new ASN1OctetString(generatedPassword), mustChangePassword, 165 secondsUntilExpiration); 166 } 167 168 169 170 /** 171 * Creates a new generate password response control with the provided 172 * information. 173 * 174 * @param generatedPassword The password generated by the server. It 175 * must not be {@code null}. 176 * @param mustChangePassword Indicates whether the user will be required 177 * to choose a new password the first time 178 * they authenticate. 179 * @param secondsUntilExpiration The number of seconds until the new 180 * password will expire. It may be 181 * {@code null} if the new password will not 182 * expire. 183 */ 184 public GeneratePasswordResponseControl( 185 @NotNull final byte[] generatedPassword, 186 final boolean mustChangePassword, 187 @Nullable final Long secondsUntilExpiration) 188 { 189 this(new ASN1OctetString(generatedPassword), mustChangePassword, 190 secondsUntilExpiration); 191 } 192 193 194 195 /** 196 * Creates a new generate password response control with the provided 197 * information. 198 * 199 * @param generatedPassword The password generated by the server. It 200 * must not be {@code null}. 201 * @param mustChangePassword Indicates whether the user will be required 202 * to choose a new password the first time 203 * they authenticate. 204 * @param secondsUntilExpiration The number of seconds until the new 205 * password will expire. It may be 206 * {@code null} if the new password will not 207 * expire. 208 */ 209 private GeneratePasswordResponseControl( 210 @NotNull final ASN1OctetString generatedPassword, 211 final boolean mustChangePassword, 212 @Nullable final Long secondsUntilExpiration) 213 { 214 super(GENERATE_PASSWORD_RESPONSE_OID, false, 215 encodeValue(generatedPassword, mustChangePassword, 216 secondsUntilExpiration)); 217 218 this.generatedPassword = generatedPassword; 219 this.mustChangePassword = mustChangePassword; 220 this.secondsUntilExpiration = secondsUntilExpiration; 221 } 222 223 224 225 /** 226 * Creates a new generate password response control with the provided 227 * information. 228 * 229 * @param oid The OID for the control. 230 * @param isCritical Indicates whether the control should be marked 231 * critical. 232 * @param value The encoded value for the control. This may be 233 * {@code null} if no value was provided. 234 * 235 * @throws LDAPException If the provided control cannot be decoded as a 236 * generate password response control. 237 */ 238 public GeneratePasswordResponseControl(@NotNull final String oid, 239 final boolean isCritical, 240 @Nullable final ASN1OctetString value) 241 throws LDAPException 242 { 243 super(oid, isCritical, value); 244 245 if (value == null) 246 { 247 throw new LDAPException(ResultCode.DECODING_ERROR, 248 ERR_GENERATE_PASSWORD_RESPONSE_NO_VALUE.get()); 249 } 250 251 try 252 { 253 final ASN1Element valElement = ASN1Element.decode(value.getValue()); 254 final ASN1Element[] elements = 255 ASN1Sequence.decodeAsSequence(valElement).elements(); 256 generatedPassword = ASN1OctetString.decodeAsOctetString(elements[0]); 257 mustChangePassword = 258 ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue(); 259 260 Long secsUntilExp = null; 261 for (int i=2; i < elements.length; i++) 262 { 263 final ASN1Element e = elements[i]; 264 switch (e.getType()) 265 { 266 case TYPE_SECONDS_UNTIL_EXPIRATION: 267 secsUntilExp = ASN1Long.decodeAsLong(e).longValue(); 268 break; 269 default: 270 // This is a field we don't currently recognize but might be defined 271 // in the future. 272 break; 273 } 274 } 275 276 secondsUntilExpiration = secsUntilExp; 277 } 278 catch (final Exception e) 279 { 280 Debug.debugException(e); 281 throw new LDAPException(ResultCode.DECODING_ERROR, 282 ERR_GENERATE_PASSWORD_RESPONSE_CANNOT_DECODE_VALUE.get( 283 StaticUtils.getExceptionMessage(e)), 284 e); 285 } 286 } 287 288 289 290 /** 291 * {@inheritDoc} 292 */ 293 @Override() 294 @NotNull() 295 public GeneratePasswordResponseControl decodeControl( 296 @NotNull final String oid, 297 final boolean isCritical, 298 @Nullable final ASN1OctetString value) 299 throws LDAPException 300 { 301 return new GeneratePasswordResponseControl(oid, isCritical, value); 302 } 303 304 305 306 /** 307 * Extracts a generate password response control from the provided result. 308 * 309 * @param result The result from which to retrieve the generate password 310 * response control. 311 * 312 * @return The generate password response control contained in the provided 313 * result, or {@code null} if the result did not contain a generate 314 * password response control. 315 * 316 * @throws LDAPException If a problem is encountered while attempting to 317 * decode the generate password response control 318 * contained in the provided result. 319 */ 320 @Nullable() 321 public static GeneratePasswordResponseControl get( 322 @NotNull final LDAPResult result) 323 throws LDAPException 324 { 325 final Control c = result.getResponseControl(GENERATE_PASSWORD_RESPONSE_OID); 326 if (c == null) 327 { 328 return null; 329 } 330 331 if (c instanceof GeneratePasswordResponseControl) 332 { 333 return (GeneratePasswordResponseControl) c; 334 } 335 else 336 { 337 return new GeneratePasswordResponseControl(c.getOID(), c.isCritical(), 338 c.getValue()); 339 } 340 } 341 342 343 344 /** 345 * Encodes the provided information appropriately for use as the value of this 346 * control. 347 * 348 * @param generatedPassword The password generated by the server. It 349 * must not be {@code null}. 350 * @param mustChangePassword Indicates whether the user will be required 351 * to choose a new password the first time 352 * they authenticate. 353 * @param secondsUntilExpiration The number of seconds until the new 354 * password will expire. It may be 355 * {@code null} if the new password will not 356 * expire. 357 * 358 * @return The ASN.1 octet string suitable for use as the control value. 359 */ 360 @NotNull() 361 private static ASN1OctetString encodeValue( 362 @NotNull final ASN1OctetString generatedPassword, 363 final boolean mustChangePassword, 364 @Nullable final Long secondsUntilExpiration) 365 { 366 final ArrayList<ASN1Element> elements = new ArrayList<>(3); 367 elements.add(generatedPassword); 368 elements.add(mustChangePassword 369 ? ASN1Boolean.UNIVERSAL_BOOLEAN_TRUE_ELEMENT 370 : ASN1Boolean.UNIVERSAL_BOOLEAN_FALSE_ELEMENT); 371 372 if (secondsUntilExpiration != null) 373 { 374 elements.add(new ASN1Long(TYPE_SECONDS_UNTIL_EXPIRATION, 375 secondsUntilExpiration)); 376 } 377 378 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 379 } 380 381 382 383 /** 384 * Retrieves the password that was generated by the server. 385 * 386 * @return The password that was generated by the server. 387 */ 388 @NotNull() 389 public ASN1OctetString getGeneratedPassword() 390 { 391 return generatedPassword; 392 } 393 394 395 396 /** 397 * Retrieves a string representation of the password that was generated by the 398 * server. 399 * 400 * @return A string representation of the password that was generated by the 401 * server. 402 */ 403 @NotNull() 404 public String getGeneratedPasswordString() 405 { 406 return generatedPassword.stringValue(); 407 } 408 409 410 411 /** 412 * Retrieves the bytes that comprise the password that was generated by the 413 * server. 414 * 415 * @return The bytes that comprise the password that was generated by the 416 * server. 417 */ 418 @NotNull() 419 public byte[] getGeneratedPasswordBytes() 420 { 421 return generatedPassword.getValue(); 422 } 423 424 425 426 /** 427 * Indicates whether the user will be required to change their password the 428 * first time they authenticate. 429 * 430 * @return {@code true} if the user will be required to change their password 431 * the first time they authenticate, or {@code false} if not. 432 */ 433 public boolean mustChangePassword() 434 { 435 return mustChangePassword; 436 } 437 438 439 440 /** 441 * Retrieves the length of time, in seconds, until the generated password will 442 * expire. 443 * 444 * @return The length of time, in seconds, until the generated password will 445 * expire, or {@code null} if this is not available (e.g., because 446 * the generated password will not expire). 447 */ 448 @Nullable() 449 public Long getSecondsUntilExpiration() 450 { 451 return secondsUntilExpiration; 452 } 453 454 455 456 /** 457 * {@inheritDoc} 458 */ 459 @Override() 460 @NotNull() 461 public String getControlName() 462 { 463 return INFO_CONTROL_NAME_GENERATE_PASSWORD_RESPONSE.get(); 464 } 465 466 467 468 /** 469 * {@inheritDoc} 470 */ 471 @Override() 472 public void toString(@NotNull final StringBuilder buffer) 473 { 474 buffer.append("GeneratePasswordResponseControl(mustChangePassword="); 475 buffer.append(mustChangePassword); 476 477 if (secondsUntilExpiration != null) 478 { 479 buffer.append(", secondsUntilExpiration="); 480 buffer.append(secondsUntilExpiration); 481 } 482 483 buffer.append(')'); 484 } 485}