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}