001/*
002 * Copyright 2007-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2024 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) 2007-2024 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.experimental;
037
038
039
040import java.util.ArrayList;
041
042import com.unboundid.asn1.ASN1Element;
043import com.unboundid.asn1.ASN1Enumerated;
044import com.unboundid.asn1.ASN1Exception;
045import com.unboundid.asn1.ASN1Integer;
046import com.unboundid.asn1.ASN1OctetString;
047import com.unboundid.asn1.ASN1Sequence;
048import com.unboundid.ldap.sdk.Control;
049import com.unboundid.ldap.sdk.DecodeableControl;
050import com.unboundid.ldap.sdk.LDAPException;
051import com.unboundid.ldap.sdk.LDAPResult;
052import com.unboundid.ldap.sdk.ResultCode;
053import com.unboundid.util.Debug;
054import com.unboundid.util.NotMutable;
055import com.unboundid.util.NotNull;
056import com.unboundid.util.Nullable;
057import com.unboundid.util.StaticUtils;
058import com.unboundid.util.ThreadSafety;
059import com.unboundid.util.ThreadSafetyLevel;
060
061import static com.unboundid.ldap.sdk.experimental.ExperimentalMessages.*;
062
063
064
065/**
066 * This class provides an implementation of the password policy response control
067 * as described in draft-behera-ldap-password-policy-10.  It may be used to
068 * provide information related to a user's password policy.  It may include at
069 * most one warning from the set of
070 * {@link DraftBeheraLDAPPasswordPolicy10WarningType} values and at most one
071 * error from the set of {@link DraftBeheraLDAPPasswordPolicy10ErrorType}
072 * values.  See the documentation for those classes for more information on the
073 * information that may be included.  See the
074 * {@link DraftBeheraLDAPPasswordPolicy10RequestControl} documentation for an
075 * example that demonstrates the use of the password policy request and response
076 * controls.
077 */
078@NotMutable()
079@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
080public final class DraftBeheraLDAPPasswordPolicy10ResponseControl
081       extends Control
082       implements DecodeableControl
083{
084  /**
085   * The OID (1.3.6.1.4.1.42.2.27.8.5.1) for the password policy response
086   * control.
087   */
088  @NotNull public static final String PASSWORD_POLICY_RESPONSE_OID =
089       "1.3.6.1.4.1.42.2.27.8.5.1";
090
091
092
093  /**
094   * The BER type for the password policy warning element.
095   */
096  private static final byte TYPE_WARNING = (byte) 0xA0;
097
098
099
100  /**
101   * The BER type for the password policy error element.
102   */
103  private static final byte TYPE_ERROR = (byte) 0x81;
104
105
106
107  /**
108   * The BER type for the "time before expiration" warning element.
109   */
110  private static final byte TYPE_TIME_BEFORE_EXPIRATION = (byte) 0x80;
111
112
113
114  /**
115   * The BER type for the "grace logins remaining" warning element.
116   */
117  private static final byte TYPE_GRACE_LOGINS_REMAINING = (byte) 0x81;
118
119
120
121  /**
122   * The serial version UID for this serializable class.
123   */
124  private static final long serialVersionUID = 1835830253434331833L;
125
126
127
128  // The password policy warning value, if applicable.
129  private final int warningValue;
130
131  // The password policy error type, if applicable.
132  @Nullable private final DraftBeheraLDAPPasswordPolicy10ErrorType errorType;
133
134  // The password policy warning type, if applicable.
135  @Nullable private final DraftBeheraLDAPPasswordPolicy10WarningType
136       warningType;
137
138
139
140  /**
141   * Creates a new empty control instance that is intended to be used only for
142   * decoding controls via the {@code DecodeableControl} interface.
143   */
144  DraftBeheraLDAPPasswordPolicy10ResponseControl()
145  {
146    warningType  = null;
147    errorType    = null;
148    warningValue = -1;
149  }
150
151
152
153  /**
154   * Creates a new password policy response control with the provided
155   * information.  It will not be critical.
156   *
157   * @param  warningType   The password policy warning type for this response
158   *                       control, or {@code null} if there should be no
159   *                       warning type.
160   * @param  warningValue  The value for the password policy warning type, or -1
161   *                       if there is no warning type.
162   * @param  errorType     The password policy error type for this response
163   *                       control, or {@code null} if there should be no error
164   *                       type.
165   */
166  public DraftBeheraLDAPPasswordPolicy10ResponseControl(
167       @Nullable final DraftBeheraLDAPPasswordPolicy10WarningType warningType,
168       final int warningValue,
169       @Nullable final DraftBeheraLDAPPasswordPolicy10ErrorType errorType)
170  {
171    this(warningType, warningValue, errorType, false);
172  }
173
174
175
176  /**
177   * Creates a new password policy response control with the provided
178   * information.
179   *
180   * @param  oid         The OID for the control.
181   * @param  isCritical  Indicates whether the control should be marked
182   *                     critical.
183   * @param  value       The encoded value for the control.  This may be
184   *                     {@code null} if no value was provided.
185   *
186   * @throws  LDAPException  If the provided control cannot be decoded as a
187   *                         password policy response control.
188   */
189  public DraftBeheraLDAPPasswordPolicy10ResponseControl(
190              @NotNull final String oid,
191              final boolean isCritical, @Nullable final ASN1OctetString value)
192         throws LDAPException
193  {
194    super(oid, isCritical, value);
195
196    if (value == null)
197    {
198      throw new LDAPException(ResultCode.DECODING_ERROR,
199                              ERR_PWP_RESPONSE_NO_VALUE.get());
200    }
201
202    final ASN1Sequence valueSequence;
203    try
204    {
205      final ASN1Element valueElement = ASN1Element.decode(value.getValue());
206      valueSequence = ASN1Sequence.decodeAsSequence(valueElement);
207    }
208    catch (final ASN1Exception ae)
209    {
210      Debug.debugException(ae);
211      throw new LDAPException(ResultCode.DECODING_ERROR,
212                              ERR_PWP_RESPONSE_VALUE_NOT_SEQUENCE.get(ae), ae);
213    }
214
215    final ASN1Element[] valueElements = valueSequence.elements();
216    if (valueElements.length > 2)
217    {
218      throw new LDAPException(ResultCode.DECODING_ERROR,
219                              ERR_PWP_RESPONSE_INVALID_ELEMENT_COUNT.get(
220                                   valueElements.length));
221    }
222
223    int                                        wv = -1;
224    DraftBeheraLDAPPasswordPolicy10ErrorType   et = null;
225    DraftBeheraLDAPPasswordPolicy10WarningType wt = null;
226    for (final ASN1Element e : valueElements)
227    {
228      switch (e.getType())
229      {
230        case TYPE_WARNING:
231          if (wt == null)
232          {
233            try
234            {
235              final ASN1Element warningElement =
236                   ASN1Element.decode(e.getValue());
237              wv = ASN1Integer.decodeAsInteger(warningElement).intValue();
238              switch (warningElement.getType())
239              {
240                case TYPE_TIME_BEFORE_EXPIRATION:
241                  wt = DraftBeheraLDAPPasswordPolicy10WarningType.
242                       TIME_BEFORE_EXPIRATION;
243                  break;
244
245                case TYPE_GRACE_LOGINS_REMAINING:
246                  wt = DraftBeheraLDAPPasswordPolicy10WarningType.
247                       GRACE_LOGINS_REMAINING;
248                  break;
249
250                default:
251                  throw new LDAPException(ResultCode.DECODING_ERROR,
252                       ERR_PWP_RESPONSE_INVALID_WARNING_TYPE.get(
253                            StaticUtils.toHex(warningElement.getType())));
254              }
255            }
256            catch (final ASN1Exception ae)
257            {
258              Debug.debugException(ae);
259              throw new LDAPException(ResultCode.DECODING_ERROR,
260                   ERR_PWP_RESPONSE_CANNOT_DECODE_WARNING.get(ae), ae);
261            }
262          }
263          else
264          {
265            throw new LDAPException(ResultCode.DECODING_ERROR,
266                 ERR_PWP_RESPONSE_MULTIPLE_WARNING.get());
267          }
268          break;
269
270        case TYPE_ERROR:
271          if (et == null)
272          {
273            try
274            {
275              final ASN1Enumerated errorElement =
276                   ASN1Enumerated.decodeAsEnumerated(e);
277              et = DraftBeheraLDAPPasswordPolicy10ErrorType.valueOf(
278                   errorElement.intValue());
279              if (et == null)
280              {
281                  throw new LDAPException(ResultCode.DECODING_ERROR,
282                                 ERR_PWP_RESPONSE_INVALID_ERROR_TYPE.get(
283                                      errorElement.intValue()));
284              }
285            }
286            catch (final ASN1Exception ae)
287            {
288              Debug.debugException(ae);
289              throw new LDAPException(ResultCode.DECODING_ERROR,
290                   ERR_PWP_RESPONSE_CANNOT_DECODE_ERROR.get(ae), ae);
291            }
292          }
293          else
294          {
295            throw new LDAPException(ResultCode.DECODING_ERROR,
296                 ERR_PWP_RESPONSE_MULTIPLE_ERROR.get());
297          }
298          break;
299
300        default:
301          throw new LDAPException(ResultCode.DECODING_ERROR,
302               ERR_PWP_RESPONSE_INVALID_TYPE.get(
303                    StaticUtils.toHex(e.getType())));
304      }
305    }
306
307    warningType  = wt;
308    warningValue = wv;
309    errorType    = et;
310  }
311
312
313
314  /**
315   * Creates a new password policy response control with the provided
316   * information.
317   *
318   * @param  warningType   The password policy warning type for this response
319   *                       control, or {@code null} if there should be no
320   *                       warning type.
321   * @param  warningValue  The value for the password policy warning type, or -1
322   *                       if there is no warning type.
323   * @param  errorType     The password policy error type for this response
324   *                       control, or {@code null} if there should be no error
325   *                       type.
326   * @param  isCritical    Indicates whether this control should be marked
327   *                       critical.
328   */
329  public DraftBeheraLDAPPasswordPolicy10ResponseControl(
330       @Nullable final DraftBeheraLDAPPasswordPolicy10WarningType warningType,
331       final int warningValue,
332       @Nullable final DraftBeheraLDAPPasswordPolicy10ErrorType errorType,
333       final boolean isCritical)
334  {
335    super(PASSWORD_POLICY_RESPONSE_OID, isCritical,
336         encodeValue(warningType, warningValue, errorType));
337
338    this.warningType = warningType;
339    this.errorType   = errorType;
340
341    if (warningType == null)
342    {
343      this.warningValue = -1;
344    }
345    else
346    {
347      this.warningValue = warningValue;
348    }
349  }
350
351
352
353  /**
354   * {@inheritDoc}
355   */
356  @Override()
357  @NotNull()
358  public DraftBeheraLDAPPasswordPolicy10ResponseControl decodeControl(
359              @NotNull final String oid, final boolean isCritical,
360              @Nullable final ASN1OctetString value)
361         throws LDAPException
362  {
363    return new DraftBeheraLDAPPasswordPolicy10ResponseControl(oid, isCritical,
364         value);
365  }
366
367
368
369  /**
370   * Extracts a password policy response control from the provided result.
371   *
372   * @param  result  The result from which to retrieve the password policy
373   *                 response control.
374   *
375   * @return  The password policy response control contained in the provided
376   *          result, or {@code null} if the result did not contain a password
377   *          policy response control.
378   *
379   * @throws  LDAPException  If a problem is encountered while attempting to
380   *                         decode the password policy response control
381   *                         contained in the provided result.
382   */
383  @Nullable()
384  public static DraftBeheraLDAPPasswordPolicy10ResponseControl get(
385                     @NotNull final LDAPResult result)
386         throws LDAPException
387  {
388    final Control c = result.getResponseControl(PASSWORD_POLICY_RESPONSE_OID);
389    if (c == null)
390    {
391      return null;
392    }
393
394    if (c instanceof DraftBeheraLDAPPasswordPolicy10ResponseControl)
395    {
396      return (DraftBeheraLDAPPasswordPolicy10ResponseControl) c;
397    }
398    else
399    {
400      return new DraftBeheraLDAPPasswordPolicy10ResponseControl(c.getOID(),
401           c.isCritical(), c.getValue());
402    }
403  }
404
405
406
407  /**
408   * Encodes the provided information as appropriate for use as the value of a
409   * password policy response control.
410   *
411   * @param  warningType   The warning type to use for the warning element, or
412   *                       {@code null} if there is not to be a warning element.
413   * @param  warningValue  The value to use for the warning element.
414   * @param  errorType     The error type to use for the error element, or
415   *                       {@code null} if there is not to be an error element.
416   *
417   * @return  The ASN.1 octet string containing the encoded control value.
418   */
419  @NotNull()
420  private static ASN1OctetString encodeValue(
421       @Nullable final DraftBeheraLDAPPasswordPolicy10WarningType warningType,
422       final int warningValue,
423       @Nullable final DraftBeheraLDAPPasswordPolicy10ErrorType errorType)
424  {
425    final ArrayList<ASN1Element> valueElements = new ArrayList<>(2);
426
427    if (warningType != null)
428    {
429      switch (warningType)
430      {
431        case TIME_BEFORE_EXPIRATION:
432          valueElements.add(new ASN1Element(TYPE_WARNING,
433               new ASN1Integer(TYPE_TIME_BEFORE_EXPIRATION,
434                               warningValue).encode()));
435          break;
436
437        case GRACE_LOGINS_REMAINING:
438          valueElements.add(new ASN1Element(TYPE_WARNING,
439               new ASN1Integer(TYPE_GRACE_LOGINS_REMAINING,
440                               warningValue).encode()));
441          break;
442      }
443    }
444
445    if (errorType != null)
446    {
447      valueElements.add(new ASN1Enumerated(TYPE_ERROR, errorType.intValue()));
448    }
449
450    return new ASN1OctetString(new ASN1Sequence(valueElements).encode());
451  }
452
453
454
455  /**
456   * Retrieves the warning type for this password policy response control, if
457   * available.
458   *
459   * @return  The warning type for this password policy response control, or
460   *          {@code null} if there is no warning type.
461   */
462  @Nullable()
463  public DraftBeheraLDAPPasswordPolicy10WarningType getWarningType()
464  {
465    return warningType;
466  }
467
468
469
470  /**
471   * Retrieves the warning value for this password policy response control, if
472   * available.
473   *
474   * @return  The warning value for this password policy response control, or -1
475   *          if there is no warning type.
476   */
477  public int getWarningValue()
478  {
479    return warningValue;
480  }
481
482
483
484  /**
485   * Retrieves the error type for this password policy response control, if
486   * available.
487   *
488   * @return  The error type for this password policy response control, or
489   *          {@code null} if there is no error type.
490   */
491  @Nullable()
492  public DraftBeheraLDAPPasswordPolicy10ErrorType getErrorType()
493  {
494    return errorType;
495  }
496
497
498
499  /**
500   * {@inheritDoc}
501   */
502  @Override()
503  @NotNull()
504  public String getControlName()
505  {
506    return INFO_CONTROL_NAME_PW_POLICY_RESPONSE.get();
507  }
508
509
510
511  /**
512   * {@inheritDoc}
513   */
514  @Override()
515  public void toString(@NotNull final StringBuilder buffer)
516  {
517    boolean elementAdded = false;
518
519    buffer.append("PasswordPolicyResponseControl(");
520
521    if (warningType != null)
522    {
523      buffer.append("warningType='");
524      buffer.append(warningType.getName());
525      buffer.append("', warningValue=");
526      buffer.append(warningValue);
527      elementAdded = true;
528    }
529
530    if (errorType != null)
531    {
532      if (elementAdded)
533      {
534        buffer.append(", ");
535      }
536
537      buffer.append("errorType='");
538      buffer.append(errorType.getName());
539      buffer.append('\'');
540      elementAdded = true;
541    }
542
543    if (elementAdded)
544    {
545      buffer.append(", ");
546    }
547
548    buffer.append("isCritical=");
549    buffer.append(isCritical());
550    buffer.append(')');
551  }
552}