001/*
002 * Copyright 2017-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2017-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) 2017-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.ASN1OctetString;
045import com.unboundid.asn1.ASN1Sequence;
046import com.unboundid.ldap.sdk.Control;
047import com.unboundid.ldap.sdk.LDAPException;
048import com.unboundid.ldap.sdk.ResultCode;
049import com.unboundid.util.Debug;
050import com.unboundid.util.NotMutable;
051import com.unboundid.util.NotNull;
052import com.unboundid.util.Nullable;
053import com.unboundid.util.StaticUtils;
054import com.unboundid.util.ThreadSafety;
055import com.unboundid.util.ThreadSafetyLevel;
056
057import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
058
059
060
061/**
062 * This class provides an implementation of a request control that can be
063 * included in an add request, modify request, or password modify extended
064 * request to control the way the server should behave when performing a
065 * password change.  The requester must have the password-reset privilege.
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 request control has an OID of 1.3.6.1.4.1.30221.2.5.51.  The criticality
078 * may be either true or false.  It must have a value, and the value should have
079 * the following encoding:
080 * <PRE>
081 *   PasswordUpdateBehaviorRequest ::= SEQUENCE {
082 *        isSelfChange                        [0] BOOLEAN OPTIONAL,
083 *        allowPreEncodedPassword             [1] BOOLEAN OPTIONAL,
084 *        skipPasswordValidation              [2] BOOLEAN OPTIONAL,
085 *        ignorePasswordHistory               [3] BOOLEAN OPTIONAL,
086 *        ignoreMinimumPasswordAge            [4] BOOLEAN OPTIONAL,
087 *        passwordStorageScheme               [5] OCTET STRING OPTIONAL,
088 *        mustChangePassword                  [6] BOOLEAN OPTIONAL,
089 *        ... }
090 * </PRE>
091 *
092 * @see  PasswordUpdateBehaviorRequestControlProperties
093 */
094@NotMutable()
095@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
096public final class PasswordUpdateBehaviorRequestControl
097       extends Control
098{
099  /**
100   * The OID (1.3.6.1.4.1.30221.2.5.51) for the password update behavior request
101   * control.
102   */
103  @NotNull public static final String PASSWORD_UPDATE_BEHAVIOR_REQUEST_OID =
104       "1.3.6.1.4.1.30221.2.5.51";
105
106
107
108  /**
109   * The BER type to use for the {@code isSelfChange} element in the encoded
110   * request.
111   */
112  private static final byte TYPE_IS_SELF_CHANGE = (byte) 0x80;
113
114
115
116  /**
117   * The BER type to use for the {@code allowPreEncodedPassword} element in the
118   * encoded request.
119   */
120  private static final byte TYPE_ALLOW_PRE_ENCODED_PASSWORD = (byte) 0x81;
121
122
123
124  /**
125   * The BER type to use for the {@code skipPasswordValidation} element in the
126   * encoded request.
127   */
128  private static final byte TYPE_SKIP_PASSWORD_VALIDATION = (byte) 0x82;
129
130
131
132  /**
133   * The BER type to use for the {@code ignorePasswordHistory} element in the
134   * encoded request.
135   */
136  private static final byte TYPE_IGNORE_PASSWORD_HISTORY = (byte) 0x83;
137
138
139
140  /**
141   * The BER type to use for the {@code ignoreMinimumPasswordAge} element in the
142   * encoded request.
143   */
144  private static final byte TYPE_IGNORE_MINIMUM_PASSWORD_AGE = (byte) 0x84;
145
146
147
148  /**
149   * The BER type to use for the {@code passwordStorageScheme} element in the
150   * encoded request.
151   */
152  private static final byte TYPE_PASSWORD_STORAGE_SCHEME = (byte) 0x85;
153
154
155
156  /**
157   * The BER type to use for the {@code mustChangePassword} element in the
158   * encoded request.
159   */
160  private static final byte TYPE_MUST_CHANGE_PASSWORD = (byte) 0x86;
161
162
163
164  /**
165   * The serial version UID for this serializable class.
166   */
167  private static final long serialVersionUID = -1915608505128236450L;
168
169
170
171  // Indicates whether the requester should be allowed to provide a pre-encoded
172  // password.
173  @Nullable private final Boolean allowPreEncodedPassword;
174
175  // Indicates whether to ignore any minimum password age configured in the
176  // password policy.
177  @Nullable private final Boolean ignoreMinimumPasswordAge;
178
179  // Indicates whether to skip the process of checking whether the provided
180  // password matches the new current password or is in the password history.
181  @Nullable private final Boolean ignorePasswordHistory;
182
183  // Indicates whether to treat the password change as a self change.
184  @Nullable private final Boolean isSelfChange;
185
186  // Indicates whether to update the user's account to indicate that they must
187  // change their password the next time they authenticate.
188  @Nullable private final Boolean mustChangePassword;
189
190  // Indicates whether to skip password validation for the new password.
191  @Nullable private final Boolean skipPasswordValidation;
192
193  // Specifies the password storage scheme to use for the new password.
194  @Nullable private final String passwordStorageScheme;
195
196
197
198  /**
199   * Creates a new password update behavior request control with the provided
200   * information.
201   *
202   * @param  properties  The set of properties to use for the request control.
203   *                     It must not be {@code null}.
204   * @param  isCritical  Indicates whether the control should be considered
205   *                     critical.
206   */
207  public PasswordUpdateBehaviorRequestControl(
208       @NotNull final PasswordUpdateBehaviorRequestControlProperties properties,
209       final boolean isCritical)
210  {
211    super(PASSWORD_UPDATE_BEHAVIOR_REQUEST_OID, isCritical,
212         encodeValue(properties));
213
214    isSelfChange = properties.getIsSelfChange();
215    allowPreEncodedPassword = properties.getAllowPreEncodedPassword();
216    skipPasswordValidation = properties.getSkipPasswordValidation();
217    ignorePasswordHistory = properties.getIgnorePasswordHistory();
218    ignoreMinimumPasswordAge = properties.getIgnoreMinimumPasswordAge();
219    passwordStorageScheme = properties.getPasswordStorageScheme();
220    mustChangePassword = properties.getMustChangePassword();
221  }
222
223
224
225  /**
226   * Creates a new password update behavior request control that is decoded from
227   * the provided generic control.
228   *
229   * @param  control  The control to be decoded as a password update behavior
230   *                  request control.  It must not be {@code null}.
231   *
232   * @throws  LDAPException  If the provided control cannot be parsed as a
233   *                         password update behavior request control.
234   */
235  public PasswordUpdateBehaviorRequestControl(@NotNull final Control control)
236         throws LDAPException
237  {
238    super(control);
239
240    final ASN1OctetString value = control.getValue();
241    if (value == null)
242    {
243      throw new LDAPException(ResultCode.DECODING_ERROR,
244           ERR_PW_UPDATE_BEHAVIOR_REQ_DECODE_NO_VALUE.get());
245    }
246
247    try
248    {
249      Boolean allowPreEncoded = null;
250      Boolean ignoreAge = null;
251      Boolean ignoreHistory = null;
252      Boolean mustChange = null;
253      Boolean selfChange = null;
254      Boolean skipValidation = null;
255      String scheme = null;
256      for (final ASN1Element e :
257           ASN1Sequence.decodeAsSequence(value.getValue()).elements())
258      {
259        switch (e.getType())
260        {
261          case TYPE_IS_SELF_CHANGE:
262            selfChange = ASN1Boolean.decodeAsBoolean(e).booleanValue();
263            break;
264          case TYPE_ALLOW_PRE_ENCODED_PASSWORD:
265            allowPreEncoded = ASN1Boolean.decodeAsBoolean(e).booleanValue();
266            break;
267          case TYPE_SKIP_PASSWORD_VALIDATION:
268            skipValidation = ASN1Boolean.decodeAsBoolean(e).booleanValue();
269            break;
270          case TYPE_IGNORE_PASSWORD_HISTORY:
271            ignoreHistory = ASN1Boolean.decodeAsBoolean(e).booleanValue();
272            break;
273          case TYPE_IGNORE_MINIMUM_PASSWORD_AGE:
274            ignoreAge = ASN1Boolean.decodeAsBoolean(e).booleanValue();
275            break;
276          case TYPE_PASSWORD_STORAGE_SCHEME:
277            scheme = ASN1OctetString.decodeAsOctetString(e).stringValue();
278            break;
279          case TYPE_MUST_CHANGE_PASSWORD:
280            mustChange = ASN1Boolean.decodeAsBoolean(e).booleanValue();
281            break;
282          default:
283            throw new LDAPException(ResultCode.DECODING_ERROR,
284                 ERR_PW_UPDATE_BEHAVIOR_REQ_DECODE_UNRECOGNIZED_ELEMENT_TYPE.
285                      get(StaticUtils.toHex(e.getType())));
286        }
287      }
288
289      isSelfChange = selfChange;
290      allowPreEncodedPassword = allowPreEncoded;
291      skipPasswordValidation = skipValidation;
292      ignorePasswordHistory = ignoreHistory;
293      ignoreMinimumPasswordAge = ignoreAge;
294      passwordStorageScheme = scheme;
295      mustChangePassword = mustChange;
296    }
297    catch (final Exception e)
298    {
299      Debug.debugException(e);
300      throw new LDAPException(ResultCode.DECODING_ERROR,
301           ERR_PW_UPDATE_BEHAVIOR_REQ_DECODE_ERROR.get(
302                StaticUtils.getExceptionMessage(e)),
303           e);
304    }
305  }
306
307
308
309  /**
310   * Encodes the provided properties into a form that can be used as the value
311   * for this control.
312   *
313   * @param  properties  The properties to be encoded.
314   *
315   * @return  An ASN.1 octet string that can be used as the request control
316   *          value.
317   */
318  @NotNull()
319  private static ASN1OctetString encodeValue(
320       @NotNull final PasswordUpdateBehaviorRequestControlProperties properties)
321  {
322    final ArrayList<ASN1Element> elements = new ArrayList<>(6);
323
324    if (properties.getIsSelfChange() != null)
325    {
326      elements.add(new ASN1Boolean(TYPE_IS_SELF_CHANGE,
327           properties.getIsSelfChange()));
328    }
329
330    if (properties.getAllowPreEncodedPassword() != null)
331    {
332      elements.add(new ASN1Boolean(TYPE_ALLOW_PRE_ENCODED_PASSWORD,
333           properties.getAllowPreEncodedPassword()));
334    }
335
336    if (properties.getSkipPasswordValidation() != null)
337    {
338      elements.add(new ASN1Boolean(TYPE_SKIP_PASSWORD_VALIDATION,
339           properties.getSkipPasswordValidation()));
340    }
341
342    if (properties.getIgnorePasswordHistory() != null)
343    {
344      elements.add(new ASN1Boolean(TYPE_IGNORE_PASSWORD_HISTORY,
345           properties.getIgnorePasswordHistory()));
346    }
347
348    if (properties.getIgnoreMinimumPasswordAge() != null)
349    {
350      elements.add(new ASN1Boolean(TYPE_IGNORE_MINIMUM_PASSWORD_AGE,
351           properties.getIgnoreMinimumPasswordAge()));
352    }
353
354    if (properties.getPasswordStorageScheme() != null)
355    {
356      elements.add(new ASN1OctetString(TYPE_PASSWORD_STORAGE_SCHEME,
357           properties.getPasswordStorageScheme()));
358    }
359
360    if (properties.getMustChangePassword() != null)
361    {
362      elements.add(new ASN1Boolean(TYPE_MUST_CHANGE_PASSWORD,
363           properties.getMustChangePassword()));
364    }
365
366    return new ASN1OctetString(new ASN1Sequence(elements).encode());
367  }
368
369
370
371  /**
372   * Indicates whether this control should override the server's automatic
373   * classification of the password update as a self change or an administrative
374   * reset, and if so, what the overridden value should be.
375   *
376   * @return  {@code Boolean.TRUE} if the server should treat the password
377   *          update as a self change, {@code Boolean.FALSE} if the server
378   *          should treat the password update as an administrative reset, or
379   *          {@code null} if the server should automatically determine whether
380   *          the password update is a self change or an administrative reset.
381   */
382  @Nullable()
383  public Boolean getIsSelfChange()
384  {
385    return isSelfChange;
386  }
387
388
389
390  /**
391   * Indicates whether this control should override the value of the
392   * {@code allow-pre-encoded-passwords} configuration property for the target
393   * user's password policy, and if so, what the overridden value should be.
394   *
395   * @return  {@code Boolean.TRUE} if the server should accept a pre-encoded
396   *          password in the password update even if the server's password
397   *          policy configuration would normally not permit this,
398   *          {@code Boolean.FALSE} if the server should reject a pre-encoded
399   *          password in the password update even if the server's password
400   *          policy configuration would normally accept it, or {@code null} if
401   *          the password policy configuration should be used to determine
402   *          whether to accept pre-encoded passwords.
403   */
404  @Nullable()
405  public Boolean getAllowPreEncodedPassword()
406  {
407    return allowPreEncodedPassword;
408  }
409
410
411
412  /**
413   * Indicates whether this control should override the server's normal behavior
414   * with regard to invoking password validators for any new passwords included
415   * in the password update, and if so, what the overridden behavior should be.
416   *
417   * @return  {@code Boolean.TRUE} if the server should skip invoking the
418   *          password validators configured in the target user's password
419   *          policy validators for any new passwords included in the password
420   *          update even if the server would normally perform password
421   *          validation, {@code Boolean.FALSE} if the server should invoke the
422   *          password validators even if it would normally skip them, or
423   *          {@code null} if the password policy configuration should be used
424   *          to determine whether to skip password validation.
425   */
426  @Nullable()
427  public Boolean getSkipPasswordValidation()
428  {
429    return skipPasswordValidation;
430  }
431
432
433
434  /**
435   * Indicates whether this control should override the server's normal behavior
436   * with regard to checking the password history for any new passwords included
437   * in the password update, and if so, what the overridden behavior should be.
438   *
439   * @return  {@code Boolean.TRUE} if the server should not check to see whether
440   *          any new password matches the current password or is in the user's
441   *          password history even if it would normally perform that check,
442   *          {@code Boolean.FALSE} if the server should check to see whether
443   *          any new password matches the current or previous password even if
444   *          it would normally not perform such a check, or {@code null} if the
445   *          password policy configuration should be used to determine whether
446   *          to ignore the password history.
447   */
448  @Nullable()
449  public Boolean getIgnorePasswordHistory()
450  {
451    return ignorePasswordHistory;
452  }
453
454
455
456  /**
457   * Indicates whether this control should override the server's normal behavior
458   * with regard to checking the minimum password age, and if so, what the
459   * overridden behavior should be.
460   *
461   * @return  {@code Boolean.TRUE} if the server should accept the password
462   *          change even if it has been less than the configured minimum
463   *          password age since the password was last changed,
464   *          {@code Boolean.FALSE} if the server should reject the password
465   *          change if it has been less than teh configured minimum password
466   *          age, or {@code null} if the password policy configuration should
467   *          be used to determine the appropriate behavior.
468   */
469  @Nullable()
470  public Boolean getIgnoreMinimumPasswordAge()
471  {
472    return ignoreMinimumPasswordAge;
473  }
474
475
476
477  /**
478   * Indicates whether this control should override the server's normal behavior
479   * with regard to selecting the password storage scheme to use to encode new
480   * password values, and if so, which password storage scheme should be used.
481   *
482   * @return  The name of the password storage scheme that should be used to
483   *          encode any new password values, or {@code null} if the target
484   *          user's password policy configuration should determine the
485   *          appropriate schemes for encoding new passwords.
486   */
487  @Nullable()
488  public String getPasswordStorageScheme()
489  {
490    return passwordStorageScheme;
491  }
492
493
494
495  /**
496   * Indicates whether this control should override the server's normal behavior
497   * with regard to requiring a password change, and if so, what that behavior
498   * should be.
499   *
500   * @return  {@code Boolean.TRUE} if the user will be required to change their
501   *          password before being allowed to perform any other operation,
502   *          {@code Boolean.FALSE} if the user will not be required to change
503   *          their password before being allowed to perform any other
504   *          operation, or {@code null} if the password policy configuration
505   *          should be used to control this behavior.
506   */
507  @Nullable()
508  public Boolean getMustChangePassword()
509  {
510    return mustChangePassword;
511  }
512
513
514
515  /**
516   * {@inheritDoc}
517   */
518  @Override()
519  @NotNull()
520  public String getControlName()
521  {
522    return INFO_PW_UPDATE_BEHAVIOR_REQ_CONTROL_NAME.get();
523  }
524
525
526
527  /**
528   * {@inheritDoc}
529   */
530  @Override()
531  public void toString(@NotNull final StringBuilder buffer)
532  {
533    buffer.append("PasswordUpdateBehaviorRequestControl(oid='");
534    buffer.append(PASSWORD_UPDATE_BEHAVIOR_REQUEST_OID);
535    buffer.append("', isCritical=");
536    buffer.append(isCritical());
537    buffer.append(", properties=");
538    new PasswordUpdateBehaviorRequestControlProperties(this).toString(buffer);
539    buffer.append(')');
540  }
541}