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}