001/*
002 * Copyright 2008-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2008-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) 2008-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.unboundidds.controls;
037
038
039
040import java.util.ArrayList;
041import java.util.Collections;
042import java.util.LinkedHashMap;
043import java.util.List;
044import java.util.Map;
045
046import com.unboundid.asn1.ASN1Boolean;
047import com.unboundid.asn1.ASN1Element;
048import com.unboundid.asn1.ASN1Integer;
049import com.unboundid.asn1.ASN1OctetString;
050import com.unboundid.asn1.ASN1Sequence;
051import com.unboundid.ldap.sdk.Control;
052import com.unboundid.ldap.sdk.DecodeableControl;
053import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
054import com.unboundid.ldap.sdk.LDAPException;
055import com.unboundid.ldap.sdk.ResultCode;
056import com.unboundid.ldap.sdk.SearchResultEntry;
057import com.unboundid.util.Debug;
058import com.unboundid.util.NotMutable;
059import com.unboundid.util.NotNull;
060import com.unboundid.util.Nullable;
061import com.unboundid.util.StaticUtils;
062import com.unboundid.util.ThreadSafety;
063import com.unboundid.util.ThreadSafetyLevel;
064import com.unboundid.util.json.JSONBoolean;
065import com.unboundid.util.json.JSONField;
066import com.unboundid.util.json.JSONNumber;
067import com.unboundid.util.json.JSONObject;
068import com.unboundid.util.json.JSONValue;
069
070import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
071
072
073
074/**
075 * This class provides an implementation of the account usable response control,
076 * which may be returned with search result entries to provide information about
077 * the usability of the associated user accounts.
078 * <BR>
079 * <BLOCKQUOTE>
080 *   <B>NOTE:</B>  This class, and other classes within the
081 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
082 *   supported for use against Ping Identity, UnboundID, and
083 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
084 *   for proprietary functionality or for external specifications that are not
085 *   considered stable or mature enough to be guaranteed to work in an
086 *   interoperable way with other types of LDAP servers.
087 * </BLOCKQUOTE>
088 * <BR>
089 * Information that may be included in the account usable response control
090 * includes:
091 * <UL>
092 *   <LI>{@code accountIsActive} -- Indicates that the account is active and may
093 *       include the length of time in seconds until the password expires.</LI>
094 *   <LI>{@code accountIsInactive} -- Indicates that the account has been locked
095 *       or deactivated.</LI>
096 *   <LI>{@code mustChangePassword} -- Indicates that the user must change his
097 *       or her password before being allowed to perform any other
098 *       operations.</LI>
099 *   <LI>{@code passwordIsExpired} -- Indicates that the user's password has
100 *       expired.</LI>
101 *   <LI>{@code remainingGraceLogins} -- Indicates the number of grace logins
102 *       remaining for the user.</LI>
103 *   <LI>{@code secondsUntilUnlock} -- Indicates the length of time in seconds
104 *       until the account will be automatically unlocked.</LI>
105 * </UL>
106 * See the {@link AccountUsableRequestControl} documentation for an example
107 * demonstrating the use of the account usable request and response controls.
108 * <BR><BR>
109 * This control was designed by Sun Microsystems and is not based on any RFC or
110 * Internet draft.  The value of this control is encoded as follows:
111 * <BR><BR>
112 * <PRE>
113 * ACCOUNT_USABLE_RESPONSE ::= CHOICE {
114 *   isUsable     [0] INTEGER, -- Seconds until password expiration --
115 *   isNotUsable  [1] MORE_INFO }
116 *
117 * MORE_INFO ::= SEQUENCE {
118 *   accountIsInactive     [0] BOOLEAN DEFAULT FALSE,
119 *   mustChangePassword    [1] BOOLEAN DEFAULT FALSE,
120 *   passwordIsExpired     [2] BOOLEAN DEFAULT FALSE,
121 *   remainingGraceLogins  [3] INTEGER OPTIONAL,
122 *   secondsUntilUnlock    [4] INTEGER OPTIONAL }
123 * </PRE>
124 */
125@NotMutable()
126@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
127public final class AccountUsableResponseControl
128       extends Control
129       implements DecodeableControl
130{
131  /**
132   * The OID (1.3.6.1.4.1.42.2.27.9.5.8) for the account usable response
133   * control.
134   */
135  @NotNull public static final String ACCOUNT_USABLE_RESPONSE_OID =
136       "1.3.6.1.4.1.42.2.27.9.5.8";
137
138
139
140  /**
141   * The BER type that will be used for the element that indicates the account
142   * is usable and provides the number of seconds until expiration.
143   */
144  private static final byte TYPE_SECONDS_UNTIL_EXPIRATION = (byte) 0x80;
145
146
147
148  /**
149   * The BER type that will be used for the element that indicates the account
150   * is not usable and provides additional information about the reason.
151   */
152  private static final byte TYPE_MORE_INFO = (byte) 0xA1;
153
154
155
156  /**
157   * The BER type that will be used for the element that indicates whether the
158   * account is inactive.
159   */
160  private static final byte TYPE_IS_INACTIVE = (byte) 0x80;
161
162
163
164  /**
165   * The BER type that will be used for the element that indicates whether the
166   * user must change their password.
167   */
168  private static final byte TYPE_MUST_CHANGE = (byte) 0x81;
169
170
171
172  /**
173   * The BER type that will be used for the element that indicates whether the
174   * password is expired.
175   */
176  private static final byte TYPE_IS_EXPIRED = (byte) 0x82;
177
178
179
180  /**
181   * The BER type that will be used for the element that provides the number of
182   * remaining grace logins.
183   */
184  private static final byte TYPE_REMAINING_GRACE_LOGINS = (byte) 0x83;
185
186
187
188  /**
189   * The BER type that will be used for the element that provides the number of
190   * seconds until the account is unlocked.
191   */
192  private static final byte TYPE_SECONDS_UNTIL_UNLOCK = (byte) 0x84;
193
194
195
196  /**
197   * The name of the field used to indicate whether the account is inactive in
198   * the JSON representation of this control.
199   */
200  @NotNull private static final String JSON_FIELD_ACCOUNT_IS_INACTIVE =
201       "account-is-inactive";
202
203
204
205  /**
206   * The name of the field used to indicate whether the account is usable in the
207   * JSON representation of this control.
208   */
209  @NotNull private static final String JSON_FIELD_ACCOUNT_IS_USABLE =
210       "account-is-usable";
211
212
213
214  /**
215   * The name of the field used to indicate whether the user must change their
216   * password in the JSON representation of this control.
217   */
218  @NotNull private static final String JSON_FIELD_MUST_CHANGE_PASSWORD =
219       "must-change-password";
220
221
222
223  /**
224   * The name of the field used to indicate whether the password is expired in
225   * the JSON representation of this control.
226   */
227  @NotNull private static final String JSON_FIELD_PASSWORD_IS_EXPIRED =
228       "password-is-expired";
229
230
231
232  /**
233   * The name of the field used to indicate hold the number of grace logins
234   * remaining in the JSON representation of this control.
235   */
236  @NotNull private static final String JSON_FIELD_REMAINING_GRACE_LOGINS =
237       "remaining-grace-logins";
238
239
240
241  /**
242   * The name of the field used to hold the number of seconds until password
243   * expiration in the JSON representation of this control.
244   */
245  @NotNull private static final String JSON_FIELD_SECONDS_UNTIL_PW_EXPIRATION =
246       "seconds-until-password-expiration";
247
248
249
250  /**
251   * The name of the field used to hold the number of seconds until the account
252   * is unlocked in the JSON representation of this control.
253   */
254  @NotNull private static final String JSON_FIELD_SECONDS_UNTIL_UNLOCK =
255       "seconds-until-unlock";
256
257
258
259  /**
260   * The serial version UID for this serializable class.
261   */
262  private static final long serialVersionUID = -9150988495337467770L;
263
264
265
266  // Indicates whether the account has been inactivated.
267  private final boolean isInactive;
268
269  // Indicates whether the account is usable.
270  private final boolean isUsable;
271
272  // Indicates whether the user's password must be changed before other
273  // operations will be allowed.
274  private final boolean mustChangePassword;
275
276  // Indicates whether the user's password is expired.
277  private final boolean passwordIsExpired;
278
279  // The list of reasons that this account may be considered unusable.
280  @NotNull private final List<String> unusableReasons;
281
282  // The number of grace logins remaining.
283  private final int remainingGraceLogins;
284
285  // The length of time in seconds until the password expires.
286  private final int secondsUntilExpiration;
287
288  // The length of time in seconds until the account is unlocked.
289  private final int secondsUntilUnlock;
290
291
292
293  /**
294   * Creates a new empty control instance that is intended to be used only for
295   * decoding controls via the {@code DecodeableControl} interface.
296   */
297  AccountUsableResponseControl()
298  {
299    isUsable               = false;
300    secondsUntilExpiration = 0;
301    isInactive             = false;
302    mustChangePassword     = false;
303    passwordIsExpired      = false;
304    remainingGraceLogins   = 0;
305    secondsUntilUnlock     = 0;
306    unusableReasons        = Collections.emptyList();
307  }
308
309
310
311  /**
312   * Creates a new account usable response control which indicates that the
313   * account is usable.
314   *
315   * @param  secondsUntilExpiration  The length of time in seconds until the
316   *                                 user's password expires, or -1 if password
317   *                                 expiration is not enabled for the user.
318   */
319  public AccountUsableResponseControl(final int secondsUntilExpiration)
320  {
321    super(ACCOUNT_USABLE_RESPONSE_OID, false,
322          encodeValue(secondsUntilExpiration));
323
324    isUsable                    = true;
325    this.secondsUntilExpiration = secondsUntilExpiration;
326    isInactive                  = false;
327    mustChangePassword          = false;
328    passwordIsExpired           = false;
329    remainingGraceLogins        = -1;
330    secondsUntilUnlock          = -1;
331    unusableReasons             = Collections.emptyList();
332  }
333
334
335
336  /**
337   * Creates a new account usable response control which indicates that the
338   * account is not usable.
339   *
340   * @param  isInactive            Indicates whether the user account has been
341   *                               inactivated.
342   * @param  mustChangePassword    Indicates whether the user is required to
343   *                               change his/her password before any other
344   *                               operations will be allowed.
345   * @param  passwordIsExpired     Indicates whether the user's password has
346   *                               expired.
347   * @param  remainingGraceLogins  The number of remaining grace logins for the
348   *                               user.
349   * @param  secondsUntilUnlock    The length of time in seconds until the
350   *                               user's account will be automatically
351   *                               unlocked.
352   */
353  public AccountUsableResponseControl(final boolean isInactive,
354                                      final boolean mustChangePassword,
355                                      final boolean passwordIsExpired,
356                                      final int remainingGraceLogins,
357                                      final int secondsUntilUnlock)
358  {
359    super(ACCOUNT_USABLE_RESPONSE_OID, false,
360          encodeValue(isInactive, mustChangePassword, passwordIsExpired,
361                      remainingGraceLogins, secondsUntilUnlock));
362
363    isUsable                  = false;
364    secondsUntilExpiration    = -1;
365    this.isInactive           = isInactive;
366    this.mustChangePassword   = mustChangePassword;
367    this.passwordIsExpired    = passwordIsExpired;
368    this.remainingGraceLogins = remainingGraceLogins;
369    this.secondsUntilUnlock   = secondsUntilUnlock;
370
371    final ArrayList<String> unusableList = new ArrayList<>(5);
372    if (isInactive)
373    {
374      unusableList.add(ERR_ACCT_UNUSABLE_INACTIVE.get());
375    }
376
377    if (mustChangePassword)
378    {
379      unusableList.add(ERR_ACCT_UNUSABLE_MUST_CHANGE_PW.get());
380    }
381
382    if (passwordIsExpired)
383    {
384      unusableList.add(ERR_ACCT_UNUSABLE_PW_EXPIRED.get());
385    }
386
387    if (remainingGraceLogins >= 0)
388    {
389      switch (remainingGraceLogins)
390      {
391        case 0:
392          unusableList.add(ERR_ACCT_UNUSABLE_REMAINING_GRACE_NONE.get());
393          break;
394        case 1:
395          unusableList.add(ERR_ACCT_UNUSABLE_REMAINING_GRACE_ONE.get());
396          break;
397        default:
398          unusableList.add(ERR_ACCT_UNUSABLE_REMAINING_GRACE_MULTIPLE.get(
399                                remainingGraceLogins));
400          break;
401      }
402    }
403
404    if (secondsUntilUnlock > 0)
405    {
406      unusableList.add(
407           ERR_ACCT_UNUSABLE_SECONDS_UNTIL_UNLOCK.get(secondsUntilUnlock));
408    }
409
410    unusableReasons = Collections.unmodifiableList(unusableList);
411  }
412
413
414
415  /**
416   * Creates a new account usable response control with the provided
417   * information.
418   *
419   * @param  oid         The OID for the control.
420   * @param  isCritical  Indicates whether the control should be marked
421   *                     critical.
422   * @param  value       The encoded value for the control.  This may be
423   *                     {@code null} if no value was provided.
424   *
425   * @throws  LDAPException  If the provided control cannot be decoded as an
426   *                         account usable response control.
427   */
428  public AccountUsableResponseControl(@NotNull final String oid,
429                                      final boolean isCritical,
430                                      @Nullable final ASN1OctetString value)
431         throws LDAPException
432  {
433    super(oid, isCritical,  value);
434
435    if (value == null)
436    {
437      throw new LDAPException(ResultCode.DECODING_ERROR,
438                              ERR_ACCOUNT_USABLE_RESPONSE_NO_VALUE.get());
439    }
440
441    final ASN1Element valueElement;
442    try
443    {
444      valueElement = ASN1Element.decode(value.getValue());
445    }
446    catch (final Exception e)
447    {
448      Debug.debugException(e);
449      throw new LDAPException(ResultCode.DECODING_ERROR,
450                     ERR_ACCOUNT_USABLE_RESPONSE_VALUE_NOT_ELEMENT.get(e), e);
451    }
452
453
454    final boolean decodedIsUsable;
455    boolean decodedIsInactive             = false;
456    boolean decodedMustChangePassword     = false;
457    boolean decodedPasswordIsExpired      = false;
458    int     decodedRemainingGraceLogins   = -1;
459    int     decodedSecondsUntilExpiration = -1;
460    int     decodedSecondsUntilUnlock     = -1;
461
462    final List<String> decodedUnusableReasons = new ArrayList<>(5);
463
464
465    final byte type = valueElement.getType();
466    if (type == TYPE_SECONDS_UNTIL_EXPIRATION)
467    {
468      decodedIsUsable = true;
469
470      try
471      {
472        decodedSecondsUntilExpiration =
473             ASN1Integer.decodeAsInteger(valueElement).intValue();
474        if (decodedSecondsUntilExpiration < 0)
475        {
476          decodedSecondsUntilExpiration = -1;
477        }
478      }
479      catch (final Exception e)
480      {
481        Debug.debugException(e);
482        throw new LDAPException(ResultCode.DECODING_ERROR,
483                       ERR_ACCOUNT_USABLE_RESPONSE_STE_NOT_INT.get(e), e);
484      }
485    }
486    else if (type == TYPE_MORE_INFO)
487    {
488      decodedIsUsable = false;
489
490      final ASN1Element[] elements;
491      try
492      {
493        elements = ASN1Sequence.decodeAsSequence(valueElement).elements();
494      }
495      catch (final Exception e)
496      {
497        Debug.debugException(e);
498        throw new LDAPException(ResultCode.DECODING_ERROR,
499                       ERR_ACCOUNT_USABLE_RESPONSE_VALUE_NOT_SEQUENCE.get(e),
500                       e);
501      }
502
503      for (final ASN1Element element : elements)
504      {
505        switch (element.getType())
506        {
507          case TYPE_IS_INACTIVE:
508            try
509            {
510              decodedIsInactive =
511                   ASN1Boolean.decodeAsBoolean(element).booleanValue();
512              decodedUnusableReasons.add(ERR_ACCT_UNUSABLE_INACTIVE.get());
513            }
514            catch (final Exception e)
515            {
516              Debug.debugException(e);
517              throw new LDAPException(ResultCode.DECODING_ERROR,
518                   ERR_ACCOUNT_USABLE_RESPONSE_INACTIVE_NOT_BOOLEAN.get(e), e);
519            }
520            break;
521
522          case TYPE_MUST_CHANGE:
523            try
524            {
525              decodedMustChangePassword =
526                   ASN1Boolean.decodeAsBoolean(element).booleanValue();
527              decodedUnusableReasons.add(
528                   ERR_ACCT_UNUSABLE_MUST_CHANGE_PW.get());
529            }
530            catch (final Exception e)
531            {
532              Debug.debugException(e);
533              throw new LDAPException(ResultCode.DECODING_ERROR,
534                   ERR_ACCOUNT_USABLE_RESPONSE_MUST_CHANGE_NOT_BOOLEAN.get(e),
535                   e);
536            }
537            break;
538
539          case TYPE_IS_EXPIRED:
540            try
541            {
542              decodedPasswordIsExpired =
543                   ASN1Boolean.decodeAsBoolean(element).booleanValue();
544              decodedUnusableReasons.add(ERR_ACCT_UNUSABLE_PW_EXPIRED.get());
545            }
546            catch (final Exception e)
547            {
548              Debug.debugException(e);
549              throw new LDAPException(ResultCode.DECODING_ERROR,
550                   ERR_ACCOUNT_USABLE_RESPONSE_IS_EXP_NOT_BOOLEAN.get(e), e);
551            }
552            break;
553
554          case TYPE_REMAINING_GRACE_LOGINS:
555            try
556            {
557              decodedRemainingGraceLogins =
558                   ASN1Integer.decodeAsInteger(element).intValue();
559              if (decodedRemainingGraceLogins < 0)
560              {
561                decodedRemainingGraceLogins = -1;
562              }
563              else
564              {
565                switch (decodedRemainingGraceLogins)
566                {
567                  case 0:
568                    decodedUnusableReasons.add(
569                         ERR_ACCT_UNUSABLE_REMAINING_GRACE_NONE.get());
570                    break;
571                  case 1:
572                    decodedUnusableReasons.add(
573                         ERR_ACCT_UNUSABLE_REMAINING_GRACE_ONE.get());
574                    break;
575                  default:
576                    decodedUnusableReasons.add(
577                         ERR_ACCT_UNUSABLE_REMAINING_GRACE_MULTIPLE.get(
578                              decodedRemainingGraceLogins));
579                    break;
580                }
581              }
582            }
583            catch (final Exception e)
584            {
585              Debug.debugException(e);
586              throw new LDAPException(ResultCode.DECODING_ERROR,
587                   ERR_ACCOUNT_USABLE_RESPONSE_GRACE_LOGINS_NOT_INT.get(e), e);
588            }
589            break;
590
591          case TYPE_SECONDS_UNTIL_UNLOCK:
592            try
593            {
594              decodedSecondsUntilUnlock =
595                   ASN1Integer.decodeAsInteger(element).intValue();
596              if (decodedSecondsUntilUnlock < 0)
597              {
598                decodedSecondsUntilUnlock = -1;
599              }
600              else if (decodedSecondsUntilUnlock > 0)
601              {
602                decodedUnusableReasons.add(
603                     ERR_ACCT_UNUSABLE_SECONDS_UNTIL_UNLOCK.get(
604                          decodedSecondsUntilUnlock));
605              }
606            }
607            catch (final Exception e)
608            {
609              Debug.debugException(e);
610              throw new LDAPException(ResultCode.DECODING_ERROR,
611                   ERR_ACCOUNT_USABLE_RESPONSE_STU_NOT_INT.get(e), e);
612            }
613            break;
614
615          default:
616            throw new LDAPException(ResultCode.DECODING_ERROR,
617                 ERR_ACCOUNT_USABLE_RESPONSE_MORE_INFO_INVALID_TYPE.get(
618                      StaticUtils.toHex(element.getType())));
619        }
620      }
621    }
622    else
623    {
624      throw new LDAPException(ResultCode.DECODING_ERROR,
625           ERR_ACCOUNT_USABLE_RESPONSE_INVALID_TYPE.get(
626                StaticUtils.toHex(type)));
627    }
628
629    isUsable               = decodedIsUsable;
630    secondsUntilExpiration = decodedSecondsUntilExpiration;
631    isInactive             = decodedIsInactive;
632    mustChangePassword     = decodedMustChangePassword;
633    passwordIsExpired      = decodedPasswordIsExpired;
634    remainingGraceLogins   = decodedRemainingGraceLogins;
635    secondsUntilUnlock     = decodedSecondsUntilUnlock;
636    unusableReasons        =
637         Collections.unmodifiableList(decodedUnusableReasons);
638  }
639
640
641
642  /**
643   * Creates an ASN.1 octet string that may be used as the value of an account
644   * usable response control if the account is usable.
645   *
646   * @param  secondsUntilExpiration  The length of time in seconds until the
647   *                                 user's password expires, or -1 if password
648   *                                 expiration is not enabled for the user.
649   *
650   * @return  The ASN.1 octet string that may be used as the control value.
651   */
652  @NotNull()
653  private static ASN1OctetString encodeValue(final int secondsUntilExpiration)
654  {
655    final ASN1Integer sueInteger =
656         new ASN1Integer(TYPE_SECONDS_UNTIL_EXPIRATION, secondsUntilExpiration);
657
658    return new ASN1OctetString(sueInteger.encode());
659  }
660
661
662
663  /**
664   * Creates an ASN.1 octet string that may be used of the value of an account
665   * usable response control if the account is not usable.
666   *
667   * @param  isInactive            Indicates whether the user account has been
668   *                               inactivated.
669   * @param  mustChangePassword    Indicates whether the user is required to
670   *                               change his/her password before any other
671   *                               operations will be allowed.
672   * @param  passwordIsExpired     Indicates whether the user's password has
673   *                               expired.
674   * @param  remainingGraceLogins  The number of remaining grace logins for the
675   *                               user.
676   * @param  secondsUntilUnlock    The length of time in seconds until the
677   *                               user's account will be automatically
678   *                               unlocked.
679   *
680   * @return  The ASN.1 octet string that may be used as the control value.
681   */
682  @NotNull()
683  private static ASN1OctetString encodeValue(final boolean isInactive,
684                                             final boolean mustChangePassword,
685                                             final boolean passwordIsExpired,
686                                             final int remainingGraceLogins,
687                                             final int secondsUntilUnlock)
688  {
689    final ArrayList<ASN1Element> elements = new ArrayList<>(5);
690
691    if (isInactive)
692    {
693      elements.add(new ASN1Boolean(TYPE_IS_INACTIVE, true));
694    }
695
696    if (mustChangePassword)
697    {
698      elements.add(new ASN1Boolean(TYPE_MUST_CHANGE, true));
699    }
700
701    if (passwordIsExpired)
702    {
703      elements.add(new ASN1Boolean(TYPE_IS_EXPIRED, true));
704    }
705
706    if (remainingGraceLogins >= 0)
707    {
708      elements.add(new ASN1Integer(TYPE_REMAINING_GRACE_LOGINS,
709                                   remainingGraceLogins));
710    }
711
712    if (secondsUntilUnlock >= 0)
713    {
714      elements.add(new ASN1Integer(TYPE_SECONDS_UNTIL_UNLOCK,
715                                   secondsUntilUnlock));
716    }
717
718    final ASN1Sequence valueSequence =
719         new ASN1Sequence(TYPE_MORE_INFO, elements);
720    return new ASN1OctetString(valueSequence.encode());
721  }
722
723
724
725  /**
726   * {@inheritDoc}
727   */
728  @Override()
729  @NotNull()
730  public AccountUsableResponseControl decodeControl(@NotNull final String oid,
731              final boolean isCritical,
732              @Nullable final ASN1OctetString value)
733         throws LDAPException
734  {
735    return new AccountUsableResponseControl(oid, isCritical, value);
736  }
737
738
739
740  /**
741   * Extracts an account usable response control from the provided search result
742   * entry.
743   *
744   * @param  entry  The search result entry from which to retrieve the account
745   *                usable response control.
746   *
747   * @return  The account usable response control contained in the provided
748   *          search result entry, or {@code null} if the entry did not contain
749   *          an account usable response control.
750   *
751   * @throws  LDAPException  If a problem is encountered while attempting to
752   *                         decode the account usable response control
753   *                         contained in the provided result.
754   */
755  @Nullable()
756  public static AccountUsableResponseControl get(
757                     @NotNull final SearchResultEntry entry)
758         throws LDAPException
759  {
760    final Control c = entry.getControl(ACCOUNT_USABLE_RESPONSE_OID);
761    if (c == null)
762    {
763      return null;
764    }
765
766    if (c instanceof AccountUsableResponseControl)
767    {
768      return (AccountUsableResponseControl) c;
769    }
770    else
771    {
772      return new AccountUsableResponseControl(c.getOID(), c.isCritical(),
773           c.getValue());
774    }
775  }
776
777
778
779  /**
780   * Indicates whether the associated user account is usable.
781   *
782   * @return  {@code true} if the user account is usable, or {@code false} if
783   *          not.
784   */
785  public boolean isUsable()
786  {
787    return isUsable;
788  }
789
790
791
792  /**
793   * Retrieves the list of reasons that this account may be unusable.
794   *
795   * @return  The list of reasons that this account may be unusable, or an empty
796   *          list if the account is usable or no reasons are available.
797   */
798  @NotNull()
799  public List<String> getUnusableReasons()
800  {
801    return unusableReasons;
802  }
803
804
805
806  /**
807   * Retrieves the number of seconds until the user's password expires.  This
808   * will only available if the account is usable.
809   *
810   * @return  The number of seconds until the user's password expires, or -1 if
811   *          the user account is not usable, or if password expiration is not
812   *          enabled in the directory server.
813   */
814  public int getSecondsUntilExpiration()
815  {
816    return secondsUntilExpiration;
817  }
818
819
820
821  /**
822   * Indicates whether the user account has been inactivated by a server
823   * administrator.
824   *
825   * @return  {@code true} if the user account has been inactivated by a server
826   *          administrator, or {@code false} if not.
827   */
828  public boolean isInactive()
829  {
830    return isInactive;
831  }
832
833
834
835  /**
836   * Indicates whether the user must change his or her password before being
837   * allowed to perform any other operations.
838   *
839   * @return  {@code true} if the user must change his or her password before
840   *          being allowed to perform any other operations, or {@code false} if
841   *          not.
842   */
843  public boolean mustChangePassword()
844  {
845    return mustChangePassword;
846  }
847
848
849
850  /**
851   * Indicates whether the user's password is expired.
852   *
853   * @return  {@code true} if the user's password is expired, or {@code false}
854   *          if not.
855   */
856  public boolean passwordIsExpired()
857  {
858    return passwordIsExpired;
859  }
860
861
862
863  /**
864   * Retrieves the number of remaining grace logins for the user.  This will
865   * only be available if the user account is not usable.
866   *
867   * @return  The number of remaining grace logins for the user, or -1 if this
868   *          is not available (e.g., because the account is usable or grace
869   *          login functionality is disabled on the server).
870   */
871  public int getRemainingGraceLogins()
872  {
873    return remainingGraceLogins;
874  }
875
876
877
878  /**
879   * Retrieves the length of time in seconds until the user's account is
880   * automatically unlocked.  This will only be available if the user account is
881   * not usable.
882   *
883   * @return  The length of time in seconds until the user's account is
884   *          automatically unlocked, or -1 if this is not available (e.g.,
885   *          because the account is usable, or because the account is not
886   *          locked, or because automatic unlocking is disabled on the server).
887   */
888  public int getSecondsUntilUnlock()
889  {
890    return secondsUntilUnlock;
891  }
892
893
894
895  /**
896   * {@inheritDoc}
897   */
898  @Override()
899  @NotNull()
900  public String getControlName()
901  {
902    return INFO_CONTROL_NAME_ACCOUNT_USABLE_RESPONSE.get();
903  }
904
905
906
907  /**
908   * Retrieves a representation of this account usable response control as a
909   * JSON object.  The JSON object uses the following fields:
910   * <UL>
911   *   <LI>
912   *     {@code oid} -- A mandatory string field whose value is the object
913   *     identifier for this control.  For the account usable response control,
914   *     the OID is "1.3.6.1.4.1.42.2.27.9.5.8".
915   *   </LI>
916   *   <LI>
917   *     {@code control-name} -- An optional string field whose value is a
918   *     human-readable name for this control.  This field is only intended for
919   *     descriptive purposes, and when decoding a control, the {@code oid}
920   *     field should be used to identify the type of control.
921   *   </LI>
922   *   <LI>
923   *     {@code criticality} -- A mandatory Boolean field used to indicate
924   *     whether this control is considered critical.
925   *   </LI>
926   *   <LI>
927   *     {@code value-base64} -- An optional string field whose value is a
928   *     base64-encoded representation of the raw value for this account usable
929   *     response control.  Exactly one of the {@code value-base64} and
930   *     {@code value-json} fields must be present.
931   *   </LI>
932   *   <LI>
933   *     {@code value-json} -- An optional JSON object field whose value is a
934   *     user-friendly representation of the value for this account usable
935   *     response control.  Exactly one of the {@code value-base64} and
936   *     {@code value-json} fields must be present, and if the
937   *     {@code value-json} field is used, then it will use the following
938   *     fields:
939   *     <UL>
940   *       <LI>
941   *         {@code account-is-usable} -- A Boolean field that indicates whether
942   *         the account is in a usable state.
943   *       </LI>
944   *       <LI>
945   *         {@code seconds-until-password-expiration} -- An optional integer
946   *         field whose value is the number of seconds until the user's
947   *         password expires.
948   *       </LI>
949   *       <LI>
950   *         {@code account-is-inactive} -- A Boolean field that indicates
951   *         whether the account has been administratively disabled.
952   *       </LI>
953   *       <LI>
954   *         {@code must-change-password} -- A Boolean field that indicates
955   *         whether the user must change their password before they can request
956   *         any other operations
957   *       </LI>
958   *       <LI>
959   *         {@code password-is-expired} -- A Boolean field that indicates
960   *         whether the user's password is expired.
961   *       </LI>
962   *       <LI>
963   *         {@code remaining-grace-logins} -- An optional integer field whose
964   *         value is the number of remaining grace logins for the user.
965   *       </LI>
966   *       <LI>
967   *         {@code seconds-until-unlock} -- An optional integer field whose
968   *         value is the number of seconds until the user's account will be
969   *         automatically unlocked.
970   *       </LI>
971   *     </UL>
972   *   </LI>
973   * </UL>
974   *
975   * @return  A JSON object that contains a representation of this control.
976   */
977  @Override()
978  @NotNull()
979  public JSONObject toJSONControl()
980  {
981    final Map<String,JSONValue> jsonValueFields = new LinkedHashMap<>();
982    jsonValueFields.put(JSON_FIELD_ACCOUNT_IS_USABLE,
983         new JSONBoolean(isUsable));
984
985    if (secondsUntilExpiration >= 0)
986    {
987      jsonValueFields.put(JSON_FIELD_SECONDS_UNTIL_PW_EXPIRATION,
988           new JSONNumber(secondsUntilExpiration));
989    }
990
991    jsonValueFields.put(JSON_FIELD_ACCOUNT_IS_INACTIVE,
992         new JSONBoolean(isInactive));
993    jsonValueFields.put(JSON_FIELD_MUST_CHANGE_PASSWORD,
994         new JSONBoolean(mustChangePassword));
995    jsonValueFields.put(JSON_FIELD_PASSWORD_IS_EXPIRED,
996         new JSONBoolean(passwordIsExpired));
997
998    if (remainingGraceLogins >= 0)
999    {
1000      jsonValueFields.put(JSON_FIELD_REMAINING_GRACE_LOGINS,
1001           new JSONNumber(remainingGraceLogins));
1002    }
1003
1004    if (secondsUntilUnlock >= 0)
1005    {
1006      jsonValueFields.put(JSON_FIELD_SECONDS_UNTIL_UNLOCK,
1007           new JSONNumber(secondsUntilUnlock));
1008    }
1009
1010    return new JSONObject(
1011         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
1012              ACCOUNT_USABLE_RESPONSE_OID),
1013         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
1014              INFO_CONTROL_NAME_ACCOUNT_USABLE_RESPONSE.get()),
1015         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
1016              isCritical()),
1017         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
1018              new JSONObject(jsonValueFields)));
1019  }
1020
1021
1022
1023  /**
1024   * Attempts to decode the provided object as a JSON representation of an
1025   * account usable response control.
1026   *
1027   * @param  controlObject  The JSON object to be decoded.  It must not be
1028   *                        {@code null}.
1029   * @param  strict         Indicates whether to use strict mode when decoding
1030   *                        the provided JSON object.  If this is {@code true},
1031   *                        then this method will throw an exception if the
1032   *                        provided JSON object contains any unrecognized
1033   *                        fields.  If this is {@code false}, then unrecognized
1034   *                        fields will be ignored.
1035   *
1036   * @return  The account usable response control that was decoded from the
1037   *          provided JSON object.
1038   *
1039   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
1040   *                         valid account usable response control.
1041   */
1042  @NotNull()
1043  public static AccountUsableResponseControl decodeJSONControl(
1044              @NotNull final JSONObject controlObject,
1045              final boolean strict)
1046         throws LDAPException
1047  {
1048    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
1049         controlObject, strict, true, true);
1050
1051    final ASN1OctetString rawValue = jsonControl.getRawValue();
1052    if (rawValue != null)
1053    {
1054      return new AccountUsableResponseControl(jsonControl.getOID(),
1055           jsonControl.getCriticality(), rawValue);
1056    }
1057
1058
1059    Boolean isInactive = null;
1060    Boolean isUsable = null;
1061    Boolean mustChangePassword = null;
1062    Boolean passwordIsExpired = null;
1063    Integer remainingGraceLogins = null;
1064    Integer secondsUntilExpiration = null;
1065    Integer secondsUntilUnlock = null;
1066    final JSONObject valueObject = jsonControl.getValueObject();
1067
1068    isUsable = valueObject.getFieldAsBoolean(JSON_FIELD_ACCOUNT_IS_USABLE);
1069    if (isUsable == null)
1070    {
1071      throw new LDAPException(ResultCode.DECODING_ERROR,
1072           ERR_ACCOUNT_USABLE_RESPONSE_JSON_MISSING_FIELD.get(
1073                controlObject.toSingleLineString(),
1074                JSON_FIELD_ACCOUNT_IS_USABLE));
1075    }
1076
1077    secondsUntilExpiration = valueObject.getFieldAsInteger(
1078         JSON_FIELD_SECONDS_UNTIL_PW_EXPIRATION);
1079
1080    isInactive = valueObject.getFieldAsBoolean(JSON_FIELD_ACCOUNT_IS_INACTIVE);
1081    if (isInactive == null)
1082    {
1083      throw new LDAPException(ResultCode.DECODING_ERROR,
1084           ERR_ACCOUNT_USABLE_RESPONSE_JSON_MISSING_FIELD.get(
1085                controlObject.toSingleLineString(),
1086                JSON_FIELD_ACCOUNT_IS_INACTIVE));
1087    }
1088
1089    mustChangePassword =
1090         valueObject.getFieldAsBoolean(JSON_FIELD_MUST_CHANGE_PASSWORD);
1091    if (mustChangePassword == null)
1092    {
1093      throw new LDAPException(ResultCode.DECODING_ERROR,
1094           ERR_ACCOUNT_USABLE_RESPONSE_JSON_MISSING_FIELD.get(
1095                controlObject.toSingleLineString(),
1096                JSON_FIELD_MUST_CHANGE_PASSWORD));
1097    }
1098
1099    passwordIsExpired =
1100         valueObject.getFieldAsBoolean(JSON_FIELD_PASSWORD_IS_EXPIRED);
1101    if (passwordIsExpired == null)
1102    {
1103      throw new LDAPException(ResultCode.DECODING_ERROR,
1104           ERR_ACCOUNT_USABLE_RESPONSE_JSON_MISSING_FIELD.get(
1105                controlObject.toSingleLineString(),
1106                JSON_FIELD_PASSWORD_IS_EXPIRED));
1107    }
1108
1109    remainingGraceLogins = valueObject.getFieldAsInteger(
1110         JSON_FIELD_REMAINING_GRACE_LOGINS);
1111
1112    secondsUntilUnlock = valueObject.getFieldAsInteger(
1113         JSON_FIELD_SECONDS_UNTIL_UNLOCK);
1114
1115    if (isUsable)
1116    {
1117      if (isInactive)
1118      {
1119        throw new LDAPException(ResultCode.DECODING_ERROR,
1120             ERR_ACCOUNT_USABLE_RESPONSE_JSON_USABLE_BOOLEAN_CONFLICT.get(
1121                  controlObject.toSingleLineString(),
1122                  JSON_FIELD_ACCOUNT_IS_USABLE,
1123                  JSON_FIELD_ACCOUNT_IS_INACTIVE));
1124      }
1125      else if (mustChangePassword)
1126      {
1127        throw new LDAPException(ResultCode.DECODING_ERROR,
1128             ERR_ACCOUNT_USABLE_RESPONSE_JSON_USABLE_BOOLEAN_CONFLICT.get(
1129                  controlObject.toSingleLineString(),
1130                  JSON_FIELD_ACCOUNT_IS_USABLE,
1131                  JSON_FIELD_MUST_CHANGE_PASSWORD));
1132      }
1133      else if (passwordIsExpired)
1134      {
1135        throw new LDAPException(ResultCode.DECODING_ERROR,
1136             ERR_ACCOUNT_USABLE_RESPONSE_JSON_USABLE_BOOLEAN_CONFLICT.get(
1137                  controlObject.toSingleLineString(),
1138                  JSON_FIELD_ACCOUNT_IS_USABLE,
1139                  JSON_FIELD_PASSWORD_IS_EXPIRED));
1140      }
1141      else if (remainingGraceLogins != null)
1142      {
1143        throw new LDAPException(ResultCode.DECODING_ERROR,
1144             ERR_ACCOUNT_USABLE_RESPONSE_JSON_USABLE_INT_CONFLICT.get(
1145                  controlObject.toSingleLineString(),
1146                  JSON_FIELD_ACCOUNT_IS_USABLE,
1147                  JSON_FIELD_REMAINING_GRACE_LOGINS));
1148      }
1149      else if (secondsUntilUnlock != null)
1150      {
1151        throw new LDAPException(ResultCode.DECODING_ERROR,
1152             ERR_ACCOUNT_USABLE_RESPONSE_JSON_USABLE_INT_CONFLICT.get(
1153                  controlObject.toSingleLineString(),
1154                  JSON_FIELD_ACCOUNT_IS_USABLE,
1155                  JSON_FIELD_SECONDS_UNTIL_UNLOCK));
1156      }
1157    }
1158    else if (secondsUntilExpiration != null)
1159    {
1160      throw new LDAPException(ResultCode.DECODING_ERROR,
1161           ERR_ACCOUNT_USABLE_RESPONSE_JSON_UNUSABLE_CONFLICT.get(
1162                controlObject.toSingleLineString(),
1163                JSON_FIELD_ACCOUNT_IS_USABLE,
1164                JSON_FIELD_SECONDS_UNTIL_PW_EXPIRATION));
1165    }
1166
1167
1168    if (strict)
1169    {
1170      final List<String> unrecognizedFields =
1171           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
1172                valueObject, JSON_FIELD_ACCOUNT_IS_USABLE,
1173                JSON_FIELD_SECONDS_UNTIL_PW_EXPIRATION,
1174                JSON_FIELD_ACCOUNT_IS_INACTIVE, JSON_FIELD_MUST_CHANGE_PASSWORD,
1175                JSON_FIELD_PASSWORD_IS_EXPIRED,
1176                JSON_FIELD_REMAINING_GRACE_LOGINS,
1177                JSON_FIELD_SECONDS_UNTIL_UNLOCK);
1178      if (! unrecognizedFields.isEmpty())
1179      {
1180        throw new LDAPException(ResultCode.DECODING_ERROR,
1181             ERR_ACCOUNT_USABLE_RESPONSE_JSON_CONTROL_UNRECOGNIZED_FIELD.get(
1182                  controlObject.toSingleLineString(),
1183                  unrecognizedFields.get(0)));
1184      }
1185    }
1186
1187
1188    if (isUsable)
1189    {
1190      return new AccountUsableResponseControl(
1191           (secondsUntilExpiration == null) ? -1 : secondsUntilExpiration);
1192    }
1193    else
1194    {
1195      return new AccountUsableResponseControl(isInactive, mustChangePassword,
1196           passwordIsExpired,
1197           (remainingGraceLogins == null) ? -1 : remainingGraceLogins,
1198           (secondsUntilUnlock == null) ? -1 : secondsUntilUnlock);
1199    }
1200  }
1201
1202
1203
1204  /**
1205   * {@inheritDoc}
1206   */
1207  @Override()
1208  public void toString(@NotNull final StringBuilder buffer)
1209  {
1210    buffer.append("AccountUsableResponseControl(isUsable=");
1211    buffer.append(isUsable);
1212
1213    if (isUsable)
1214    {
1215      if (secondsUntilExpiration >= 0)
1216      {
1217        buffer.append(", secondsUntilExpiration=");
1218        buffer.append(secondsUntilExpiration);
1219      }
1220    }
1221    else
1222    {
1223      buffer.append(", isInactive=");
1224      buffer.append(isInactive);
1225      buffer.append(", mustChangePassword=");
1226      buffer.append(mustChangePassword);
1227      buffer.append(", passwordIsExpired=");
1228      buffer.append(passwordIsExpired);
1229
1230      if (remainingGraceLogins >= 0)
1231      {
1232        buffer.append(", remainingGraceLogins=");
1233        buffer.append(remainingGraceLogins);
1234      }
1235
1236      if (secondsUntilUnlock >= 0)
1237      {
1238        buffer.append(", secondsUntilUnlock=");
1239        buffer.append(secondsUntilUnlock);
1240      }
1241    }
1242
1243    buffer.append(')');
1244  }
1245}