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