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}