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.controls; 037 038 039 040import java.util.List; 041 042import com.unboundid.asn1.ASN1OctetString; 043import com.unboundid.ldap.sdk.Control; 044import com.unboundid.ldap.sdk.DecodeableControl; 045import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 046import com.unboundid.ldap.sdk.LDAPException; 047import com.unboundid.ldap.sdk.LDAPResult; 048import com.unboundid.ldap.sdk.ResultCode; 049import com.unboundid.util.Debug; 050import com.unboundid.util.NotMutable; 051import com.unboundid.util.NotNull; 052import com.unboundid.util.Nullable; 053import com.unboundid.util.ThreadSafety; 054import com.unboundid.util.ThreadSafetyLevel; 055import com.unboundid.util.json.JSONField; 056import com.unboundid.util.json.JSONObject; 057 058import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 059 060 061 062/** 063 * This class provides an implementation of the expiring expiring control as 064 * described in draft-vchu-ldap-pwd-policy. It may be used to indicate that the 065 * authenticated user's password will expire in the near future. The value of 066 * this control includes the length of time in seconds until the user's 067 * password actually expires. 068 * <BR><BR> 069 * No request control is required to trigger the server to send the password 070 * expiring response control. If the server supports the use of this control 071 * and the user's password will expire within a time frame that the server 072 * considers to be the near future, then it will be included in the bind 073 * response returned to the client. 074 * <BR><BR> 075 * See the documentation for the {@link PasswordExpiredControl} to see an 076 * example that demonstrates the use of both the password expiring and password 077 * expired controls. 078 */ 079@NotMutable() 080@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 081public final class PasswordExpiringControl 082 extends Control 083 implements DecodeableControl 084{ 085 /** 086 * The OID (2.16.840.1.113730.3.4.5) for the password expiring response 087 * control. 088 */ 089 @NotNull public static final String PASSWORD_EXPIRING_OID = 090 "2.16.840.1.113730.3.4.5"; 091 092 093 094 /** 095 * The name of the field used to hold the seconds until expiration in the JSON 096 * representation of this control. 097 */ 098 @NotNull private static final String JSON_FIELD_SECONDS_UNTIL_EXPIRATION = 099 "seconds-until-expiration"; 100 101 102 103 /** 104 * The serial version UID for this serializable class. 105 */ 106 private static final long serialVersionUID = 1250220480854441338L; 107 108 109 110 // The length of time in seconds until the password expires. 111 private final int secondsUntilExpiration; 112 113 114 115 /** 116 * Creates a new empty control instance that is intended to be used only for 117 * decoding controls via the {@code DecodeableControl} interface. 118 */ 119 PasswordExpiringControl() 120 { 121 secondsUntilExpiration = -1; 122 } 123 124 125 126 /** 127 * Creates a new password expiring control with the provided information. 128 * 129 * @param secondsUntilExpiration The length of time in seconds until the 130 * password expires. 131 */ 132 public PasswordExpiringControl(final int secondsUntilExpiration) 133 { 134 super(PASSWORD_EXPIRING_OID, false, 135 new ASN1OctetString(String.valueOf(secondsUntilExpiration))); 136 137 this.secondsUntilExpiration = secondsUntilExpiration; 138 } 139 140 141 142 /** 143 * Creates a new password expiring control with the provided information. 144 * 145 * @param oid The OID for the control. 146 * @param isCritical Indicates whether the control should be marked 147 * critical. 148 * @param value The encoded value for the control. This may be 149 * {@code null} if no value was provided. 150 * 151 * @throws LDAPException If the provided control cannot be decoded as a 152 * password expiring response control. 153 */ 154 public PasswordExpiringControl(@NotNull final String oid, 155 final boolean isCritical, 156 @Nullable final ASN1OctetString value) 157 throws LDAPException 158 { 159 super(oid, isCritical, value); 160 161 if (value == null) 162 { 163 throw new LDAPException(ResultCode.DECODING_ERROR, 164 ERR_PW_EXPIRING_NO_VALUE.get()); 165 } 166 167 try 168 { 169 secondsUntilExpiration = Integer.parseInt(value.stringValue()); 170 } 171 catch (final NumberFormatException nfe) 172 { 173 Debug.debugException(nfe); 174 throw new LDAPException(ResultCode.DECODING_ERROR, 175 ERR_PW_EXPIRING_VALUE_NOT_INTEGER.get(), nfe); 176 } 177 } 178 179 180 181 /** 182 * {@inheritDoc} 183 */ 184 @Override() 185 @NotNull() 186 public PasswordExpiringControl decodeControl( 187 @NotNull final String oid, final boolean isCritical, 188 @Nullable final ASN1OctetString value) 189 throws LDAPException 190 { 191 return new PasswordExpiringControl(oid, isCritical, value); 192 } 193 194 195 196 /** 197 * Extracts a password expiring control from the provided result. 198 * 199 * @param result The result from which to retrieve the password expiring 200 * control. 201 * 202 * @return The password expiring control contained in the provided result, or 203 * {@code null} if the result did not contain a password expiring 204 * control. 205 * 206 * @throws LDAPException If a problem is encountered while attempting to 207 * decode the password expiring control contained in 208 * the provided result. 209 */ 210 @Nullable() 211 public static PasswordExpiringControl get(@NotNull final LDAPResult result) 212 throws LDAPException 213 { 214 final Control c = result.getResponseControl(PASSWORD_EXPIRING_OID); 215 if (c == null) 216 { 217 return null; 218 } 219 220 if (c instanceof PasswordExpiringControl) 221 { 222 return (PasswordExpiringControl) c; 223 } 224 else 225 { 226 return new PasswordExpiringControl(c.getOID(), c.isCritical(), 227 c.getValue()); 228 } 229 } 230 231 232 233 /** 234 * Retrieves the length of time in seconds until the password expires. 235 * 236 * @return The length of time in seconds until the password expires. 237 */ 238 public int getSecondsUntilExpiration() 239 { 240 return secondsUntilExpiration; 241 } 242 243 244 245 /** 246 * {@inheritDoc} 247 */ 248 @Override() 249 @NotNull() 250 public String getControlName() 251 { 252 return INFO_CONTROL_NAME_PW_EXPIRING.get(); 253 } 254 255 256 257 /** 258 * Retrieves a representation of this password expiring control as a JSON 259 * object. The JSON object uses the following fields: 260 * <UL> 261 * <LI> 262 * {@code oid} -- A mandatory string field whose value is the object 263 * identifier for this control. For the password expiring control, the 264 * OID is "2.16.840.1.113730.3.4.5". 265 * </LI> 266 * <LI> 267 * {@code control-name} -- An optional string field whose value is a 268 * human-readable name for this control. This field is only intended for 269 * descriptive purposes, and when decoding a control, the {@code oid} 270 * field should be used to identify the type of control. 271 * </LI> 272 * <LI> 273 * {@code criticality} -- A mandatory Boolean field used to indicate 274 * whether this control is considered critical. 275 * </LI> 276 * <LI> 277 * {@code value-base64} -- An optional string field whose value is a 278 * base64-encoded representation of the raw value for this password 279 * expiring control. Exactly one of the {@code value-base64} and 280 * {@code value-json} fields must be present. 281 * </LI> 282 * <LI> 283 * {@code value-json} -- An optional JSON object field whose value is a 284 * user-friendly representation of the value for this password expiring 285 * control. Exactly one of the {@code value-base64} and 286 * {@code value-json} fields must be present, and if the 287 * {@code value-json} field is used, then it will use the following 288 * fields: 289 * <UL> 290 * <LI> 291 * {@code seconds-until-expiration} -- An integer field whose value is 292 * the number of seconds until the user's password expires. 293 * </LI> 294 * </UL> 295 * </LI> 296 * </UL> 297 * 298 * @return A JSON object that contains a representation of this control. 299 */ 300 @Override() 301 @NotNull() 302 public JSONObject toJSONControl() 303 { 304 return new JSONObject( 305 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 306 PASSWORD_EXPIRING_OID), 307 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 308 INFO_CONTROL_NAME_PW_EXPIRING.get()), 309 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 310 isCritical()), 311 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 312 new JSONObject( 313 new JSONField(JSON_FIELD_SECONDS_UNTIL_EXPIRATION, 314 secondsUntilExpiration)))); 315 } 316 317 318 319 /** 320 * Attempts to decode the provided object as a JSON representation of a 321 * password expiring control. 322 * 323 * @param controlObject The JSON object to be decoded. It must not be 324 * {@code null}. 325 * @param strict Indicates whether to use strict mode when decoding 326 * the provided JSON object. If this is {@code true}, 327 * then this method will throw an exception if the 328 * provided JSON object contains any unrecognized 329 * fields. If this is {@code false}, then unrecognized 330 * fields will be ignored. 331 * 332 * @return The password expiring control that was decoded from the provided 333 * JSON object. 334 * 335 * @throws LDAPException If the provided JSON object cannot be parsed as a 336 * valid password expiring control. 337 */ 338 @NotNull() 339 public static PasswordExpiringControl decodeJSONControl( 340 @NotNull final JSONObject controlObject, 341 final boolean strict) 342 throws LDAPException 343 { 344 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 345 controlObject, strict, true, true); 346 347 final ASN1OctetString rawValue = jsonControl.getRawValue(); 348 if (rawValue != null) 349 { 350 return new PasswordExpiringControl(jsonControl.getOID(), 351 jsonControl.getCriticality(), rawValue); 352 } 353 354 355 final JSONObject valueObject = jsonControl.getValueObject(); 356 357 final Integer secondsUntilExpiration = 358 valueObject.getFieldAsInteger(JSON_FIELD_SECONDS_UNTIL_EXPIRATION); 359 if (secondsUntilExpiration == null) 360 { 361 throw new LDAPException(ResultCode.DECODING_ERROR, 362 ERR_PW_EXPIRING_JSON_MISSING_SECONDS_UNTIL_EXPIRATION.get( 363 controlObject.toSingleLineString(), 364 JSON_FIELD_SECONDS_UNTIL_EXPIRATION)); 365 } 366 367 368 if (strict) 369 { 370 final List<String> unrecognizedFields = 371 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 372 valueObject, JSON_FIELD_SECONDS_UNTIL_EXPIRATION); 373 if (! unrecognizedFields.isEmpty()) 374 { 375 throw new LDAPException(ResultCode.DECODING_ERROR, 376 ERR_PW_EXPIRING_JSON_UNRECOGNIZED_FIELD.get( 377 controlObject.toSingleLineString(), 378 unrecognizedFields.get(0))); 379 } 380 } 381 382 383 return new PasswordExpiringControl(secondsUntilExpiration); 384 } 385 386 387 388 /** 389 * {@inheritDoc} 390 */ 391 @Override() 392 public void toString(@NotNull final StringBuilder buffer) 393 { 394 buffer.append("PasswordExpiringControl(secondsUntilExpiration="); 395 buffer.append(secondsUntilExpiration); 396 buffer.append(", isCritical="); 397 buffer.append(isCritical()); 398 buffer.append(')'); 399 } 400}