001/*
002 * Copyright 2020-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2020-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) 2020-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;
037
038
039
040import java.io.Serializable;
041
042import com.unboundid.ldap.sdk.Entry;
043import com.unboundid.ldap.sdk.LDAPException;
044import com.unboundid.ldap.sdk.LDAPInterface;
045import com.unboundid.ldap.sdk.ResultCode;
046import com.unboundid.ldap.sdk.SearchResultEntry;
047import com.unboundid.ldap.sdk.unboundidds.extensions.
048            PasswordPolicyStateExtendedRequest;
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;
056import com.unboundid.util.json.JSONNull;
057import com.unboundid.util.json.JSONObject;
058import com.unboundid.util.json.JSONString;
059import com.unboundid.util.json.JSONValue;
060
061import static com.unboundid.ldap.sdk.unboundidds.
062                   ModifiablePasswordPolicyStateJSONField.*;
063import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
064
065
066
067/**
068 * This class provides support for reading and decoding the value of the
069 * {@code ds-pwp-modifiable-state-json} virtual attribute, which may be used to
070 * manipulate elements of a user's password policy state.  The value of this
071 * attribute is a JSON object, and using an LDAP modify operation to replace the
072 * value with a new JSON object will cause the associated state elements to be
073 * updated in the user entry.  The
074 * {@link ModifiablePasswordPolicyStateJSONBuilder} class can be used to
075 * construct values to more easily manipulate that state.  Note that the
076 * {@link PasswordPolicyStateExtendedRequest} class provides a mechanism for
077 * manipulating an even broader range of password policy state elements.
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 *
089 * @see  ModifiablePasswordPolicyStateJSONBuilder
090 * @see  ModifiablePasswordPolicyStateJSONField
091 * @see  PasswordPolicyStateJSON
092 * @see  PasswordPolicyStateExtendedRequest
093 */
094@NotMutable()
095@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
096public final class ModifiablePasswordPolicyStateJSON
097       implements Serializable
098{
099  /**
100   * The name of the operational attribute that holds a JSON representation of
101   * the modifiable elements in a user's password policy state.
102   */
103  @NotNull public static final String
104       MODIFIABLE_PASSWORD_POLICY_STATE_JSON_ATTRIBUTE =
105            "ds-pwp-modifiable-state-json";
106
107
108
109  /**
110   * The serial version UID for this serializable class.
111   */
112  private static final long serialVersionUID = -5181314292507625116L;
113
114
115
116
117  // The JSON object that contains the modifiable password policy state
118  // information.
119  @NotNull private final JSONObject modifiablePasswordPolicyStateObject;
120
121
122
123  /**
124   * Creates a new instance of this object from the provided JSON object.
125   *
126   * @param  modifiablePasswordPolicyStateObject
127   *              The JSON object containing the encoded modifiable password
128   *              policy state.
129   */
130  public ModifiablePasswordPolicyStateJSON(
131       @NotNull final JSONObject modifiablePasswordPolicyStateObject)
132  {
133    this.modifiablePasswordPolicyStateObject =
134         modifiablePasswordPolicyStateObject;
135  }
136
137
138
139  /**
140   * Attempts to retrieve and decode the modifiable password policy state
141   * information for the specified user.
142   *
143   * @param  connection  The connection to use to communicate with the server.
144   *                     It must not be {@code null}, and it must be established
145   *                     and authenticated as an account with permission to
146   *                     access the target user's password policy state
147   *                     information.
148   * @param  userDN      The DN of the user for whom to retrieve the password
149   *                     policy state.  It must not be {@code null}.
150   *
151   * @return  The modifiable password policy state information for the specified
152   *          user, or {@code null} because no modifiable password policy state
153   *          information is available for the user.
154   *
155   * @throws  LDAPException  If a problem is encountered while trying to
156   *                         retrieve the user's entry or decode the modifiable
157   *                         password policy state JSON object.
158   */
159  @Nullable()
160  public static ModifiablePasswordPolicyStateJSON get(
161                     @NotNull final LDAPInterface connection,
162                     @NotNull final String userDN)
163         throws LDAPException
164  {
165    final SearchResultEntry userEntry = connection.getEntry(userDN,
166         MODIFIABLE_PASSWORD_POLICY_STATE_JSON_ATTRIBUTE);
167    if (userEntry == null)
168    {
169      throw new LDAPException(ResultCode.NO_SUCH_OBJECT,
170           ERR_MODIFIABLE_PW_POLICY_STATE_JSON_GET_NO_SUCH_USER.get(userDN));
171    }
172
173    return get(userEntry);
174  }
175
176
177
178  /**
179   * Attempts to retrieve and decode the modifiable password policy state
180   * information from the provided user entry.
181   *
182   * @param  userEntry  The entry for the user for whom to obtain the modifiable
183   *                    password policy state information.  It must not be
184   *                    {@code null}.
185   *
186   * @return  The modifiable password policy state information from the provided
187   *          user entry, or {@code null} if no modifiable password policy state
188   *          information is available for the user.
189   *
190   * @throws  LDAPException  If a problem is encountered while trying to decode
191   *                         the modifiable password policy state JSON object.
192   */
193  @Nullable()
194  public static ModifiablePasswordPolicyStateJSON get(
195              @NotNull final Entry userEntry)
196         throws LDAPException
197  {
198    final String valueString = userEntry.getAttributeValue(
199         MODIFIABLE_PASSWORD_POLICY_STATE_JSON_ATTRIBUTE);
200    if (valueString == null)
201    {
202      return null;
203    }
204
205    final JSONObject jsonObject;
206    try
207    {
208      jsonObject = new JSONObject(valueString);
209    }
210    catch (final Exception e)
211    {
212      Debug.debugException(e);
213      throw new LDAPException(ResultCode.DECODING_ERROR,
214           ERR_MODIFIABLE_PW_POLICY_STATE_JSON_GET_CANNOT_DECODE.get(
215                MODIFIABLE_PASSWORD_POLICY_STATE_JSON_ATTRIBUTE,
216                userEntry.getDN()),
217           e);
218    }
219
220    return new ModifiablePasswordPolicyStateJSON(jsonObject);
221  }
222
223
224
225  /**
226   * Retrieves the JSON object that contains the encoded modifiable password
227   * policy state information.
228   *
229   * @return  The JSON object that contains the encoded modifiable password
230   *          policy state information.
231   */
232  @NotNull()
233  public JSONObject getModifiablePasswordPolicyStateJSONObject()
234  {
235    return modifiablePasswordPolicyStateObject;
236  }
237
238
239
240  /**
241   * Retrieves a timestamp that indicates the time the user's password was last
242   * changed.
243   *
244   * @return  A non-negative value that represents the password changed time in
245   *          number of milliseconds since the epoch (the same format used by
246   *          {@code System.currentTimeMillis}), a negative value if the field
247   *          was present with a JSON null value (indicating that the user
248   *          doesn't have a password changed time), or {@code null} if the
249   *          field was not included in the JSON object.
250   */
251  @Nullable()
252  public Long getPasswordChangedTime()
253  {
254    return getTimestamp(PASSWORD_CHANGED_TIME);
255  }
256
257
258
259  /**
260   * Retrieves the value of a flag that indicates whether the user's account has
261   * been administratively disabled.
262   *
263   * @return  {@code Boolean.TRUE} if the account has been administratively
264   *          disabled, {@code Boolean.FALSE} if the account has not been
265   *          administratively disabled, or {@code null} if this flag was not
266   *          included in the password policy state JSON object.
267   */
268  @Nullable()
269  public Boolean getAccountIsDisabled()
270  {
271    return modifiablePasswordPolicyStateObject.getFieldAsBoolean(
272         ACCOUNT_IS_DISABLED.getFieldName());
273  }
274
275
276
277  /**
278   * Retrieves a timestamp that indicates the time the user's account became (or
279   * will become) active.
280   *
281   * @return  A non-negative value that represents the account activation time
282   *          in number of milliseconds since the epoch (the same format used by
283   *          {@code System.currentTimeMillis}), a negative value if the field
284   *          was present with a JSON null value (indicating that the user
285   *          doesn't have an account activation time), or {@code null} if the
286   *          field was not included in the JSON object.
287   */
288  @Nullable()
289  public Long getAccountActivationTime()
290  {
291    return getTimestamp(ACCOUNT_ACTIVATION_TIME);
292  }
293
294
295
296  /**
297   * Retrieves a timestamp that indicates the time the user's account will (or
298   * did) expire.
299   *
300   * @return  A non-negative value that represents the account expiration time
301   *          in number of milliseconds since the epoch (the same format used by
302   *          {@code System.currentTimeMillis}), a negative value if the field
303   *          was present with a JSON null value (indicating that the user
304   *          doesn't have an account expiration time), or {@code null} if the
305   *          field was not included in the JSON object.
306   */
307  @Nullable()
308  public Long getAccountExpirationTime()
309  {
310    return getTimestamp(ACCOUNT_EXPIRATION_TIME);
311  }
312
313
314
315  /**
316   * Retrieves the value of a flag that indicates whether the user account is
317   * currently locked as a result of too many failed authentication attempts.
318   *
319   * @return  {@code Boolean.TRUE} if the user account is locked as a result of
320   *          too many failed authentication attempts, {@code Boolean.FALSE} if
321   *          the user account is not locked because of too many failed
322   *          authentication attempts, or {@code null} if this flag was not
323   *          included in the password policy state JSON object.
324   */
325  @Nullable()
326  public Boolean getAccountIsFailureLocked()
327  {
328    return modifiablePasswordPolicyStateObject.getFieldAsBoolean(
329         ACCOUNT_IS_FAILURE_LOCKED.getFieldName());
330  }
331
332
333
334  /**
335   * Retrieves a timestamp that indicates the time the user was first warned
336   * about an upcoming password expiration.
337   *
338   * @return  A non-negative value that represents the password expiration
339   *          warned time in number of milliseconds since the epoch (the same
340   *          format used by {@code System.currentTimeMillis}), a negative value
341   *          if the field was present with a JSON null value (indicating that
342   *          the user doesn't have an password expiration warned time), or
343   *          {@code null} if the field was not included in the JSON object.
344   */
345  @Nullable()
346  public Long getPasswordExpirationWarnedTime()
347  {
348    return getTimestamp(PASSWORD_EXPIRATION_WARNED_TIME);
349  }
350
351
352
353  /**
354   * Retrieves the value of a flag that indicates whether the user must change
355   * their password before they will be allowed to perform any other operations
356   * in the server.
357   *
358   * @return  {@code Boolean.TRUE} if the user must change their password before
359   *          they will be allowed to perform any other operations in the
360   *          server, {@code Boolean.FALSE} if the user is not required to
361   *          change their password, or {@code null} if this flag was not
362   *          included in the password policy state JSON object.
363   */
364  @Nullable()
365  public Boolean getMustChangePassword()
366  {
367    return modifiablePasswordPolicyStateObject.getFieldAsBoolean(
368         MUST_CHANGE_PASSWORD.getFieldName());
369  }
370
371
372
373  /**
374   * Decodes the value of the specified field as a timestamp.  The field may
375   * have a value that is either a string containing an ISO 8601 timestamp in
376   * the format described in RFC 3339, or it may be a JSON null value to
377   * indicate that the user does not have the requested timestamp.
378   *
379   * @param  field  The field whose value is to be retrieved and parsed as a
380   *                timestamp.
381   *
382   * @return  A non-negative value providing the value of the timestamp (in the
383   *          number of milliseconds since the epoch, which is the same format
384   *          used by the {@code System.currentTimeMillis} method), a negative
385   *          value to indicate that the field was present with a value of
386   *          {@code null} (and therefore the user did not have that timestamp
387   *          in their state), or {@code null} if the field was not present in
388   *          the JSON object or if its string value could not be parsed as a
389   *          valid timestamp.
390   */
391  @Nullable()
392  private Long getTimestamp(
393       @NotNull final ModifiablePasswordPolicyStateJSONField field)
394  {
395    final JSONValue fieldValue =
396         modifiablePasswordPolicyStateObject.getField(field.getFieldName());
397    if (fieldValue == null)
398    {
399      return null;
400    }
401
402    if (fieldValue instanceof JSONNull)
403    {
404      return -1L;
405    }
406    else  if (fieldValue instanceof JSONString)
407    {
408      try
409      {
410        return StaticUtils.decodeRFC3339Time(
411             ((JSONString) fieldValue).stringValue()).getTime();
412      }
413      catch (final Exception e)
414      {
415        Debug.debugException(e);
416        return null;
417      }
418    }
419    else
420    {
421      return null;
422    }
423  }
424
425
426
427  /**
428   * Retrieves a string representation of the password policy state information.
429   *
430   * @return  A string representation of the password policy state information.
431   */
432  @Override()
433  @NotNull()
434  public String toString()
435  {
436    return modifiablePasswordPolicyStateObject.toSingleLineString();
437  }
438}