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 com.unboundid.asn1.ASN1OctetString; 041import com.unboundid.ldap.sdk.Control; 042import com.unboundid.ldap.sdk.DecodeableControl; 043import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 044import com.unboundid.ldap.sdk.LDAPException; 045import com.unboundid.ldap.sdk.LDAPResult; 046import com.unboundid.ldap.sdk.ResultCode; 047import com.unboundid.util.Debug; 048import com.unboundid.util.NotMutable; 049import com.unboundid.util.NotNull; 050import com.unboundid.util.Nullable; 051import com.unboundid.util.ThreadSafety; 052import com.unboundid.util.ThreadSafetyLevel; 053import com.unboundid.util.json.JSONField; 054import com.unboundid.util.json.JSONObject; 055 056import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 057 058 059 060/** 061 * This class provides an implementation of the password expired control as 062 * described in draft-vchu-ldap-pwd-policy. It may be included in the response 063 * for an unsuccessful bind operation to indicate that the reason for the 064 * failure is that the target user's password has expired and must be reset 065 * before the user will be allowed to authenticate. Some servers may also 066 * include this control in a successful bind response to indicate that the 067 * authenticated user must change his or her password before being allowed to 068 * perform any other operation. 069 * <BR><BR> 070 * No request control is required to trigger the server to send the password 071 * expired response control. If the server supports the use of this control and 072 * the corresponding bind operation meets the criteria for this control to be 073 * included in the response, then it will be returned to the client. 074 * <BR><BR> 075 * <H2>Example</H2> 076 * The following example demonstrates a process that may be used to perform a 077 * simple bind to authenticate against the server and handle any password 078 * expired or password expiring control that may be included in the response: 079 * <PRE> 080 * // Send a simple bind request to the directory server. 081 * BindRequest bindRequest = 082 * new SimpleBindRequest("uid=test.user,ou=People,dc=example,dc=com", 083 * "password"); 084 * BindResult bindResult; 085 * boolean bindSuccessful; 086 * boolean passwordExpired; 087 * boolean passwordAboutToExpire; 088 * try 089 * { 090 * bindResult = connection.bind(bindRequest); 091 * 092 * // If we got here, the bind was successful and we know the password was 093 * // not expired. However, we shouldn't ignore the result because the 094 * // password might be about to expire. To determine whether that is the 095 * // case, we should see if the bind result included a password expiring 096 * // control. 097 * bindSuccessful = true; 098 * passwordExpired = false; 099 * 100 * PasswordExpiringControl expiringControl = 101 * PasswordExpiringControl.get(bindResult); 102 * if (expiringControl != null) 103 * { 104 * passwordAboutToExpire = true; 105 * int secondsToExpiration = expiringControl.getSecondsUntilExpiration(); 106 * } 107 * else 108 * { 109 * passwordAboutToExpire = false; 110 * } 111 * } 112 * catch (LDAPException le) 113 * { 114 * // If we got here, then the bind failed. The failure may or may not have 115 * // been due to an expired password. To determine that, we should see if 116 * // the bind result included a password expired control. 117 * bindSuccessful = false; 118 * passwordAboutToExpire = false; 119 * bindResult = new BindResult(le.toLDAPResult()); 120 * ResultCode resultCode = le.getResultCode(); 121 * String errorMessageFromServer = le.getDiagnosticMessage(); 122 * 123 * PasswordExpiredControl expiredControl = 124 * PasswordExpiredControl.get(le); 125 * if (expiredControl != null) 126 * { 127 * passwordExpired = true; 128 * } 129 * else 130 * { 131 * passwordExpired = false; 132 * } 133 * } 134 * </PRE> 135 */ 136@NotMutable() 137@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 138public final class PasswordExpiredControl 139 extends Control 140 implements DecodeableControl 141{ 142 /** 143 * The OID (2.16.840.1.113730.3.4.4) for the password expired response 144 * control. 145 */ 146 @NotNull public static final String PASSWORD_EXPIRED_OID = 147 "2.16.840.1.113730.3.4.4"; 148 149 150 151 /** 152 * The serial version UID for this serializable class. 153 */ 154 private static final long serialVersionUID = -2731704592689892224L; 155 156 157 158 /** 159 * Creates a new password expired control. 160 */ 161 public PasswordExpiredControl() 162 { 163 super(PASSWORD_EXPIRED_OID, false, new ASN1OctetString("0")); 164 } 165 166 167 168 /** 169 * Creates a new password expired control with the provided information. 170 * 171 * @param oid The OID for the control. 172 * @param isCritical Indicates whether the control should be marked 173 * critical. 174 * @param value The encoded value for the control. This may be 175 * {@code null} if no value was provided. 176 * 177 * @throws LDAPException If the provided control cannot be decoded as a 178 * password expired response control. 179 */ 180 public PasswordExpiredControl(@NotNull final String oid, 181 final boolean isCritical, 182 @Nullable final ASN1OctetString value) 183 throws LDAPException 184 { 185 super(oid, isCritical, value); 186 187 if (value == null) 188 { 189 throw new LDAPException(ResultCode.DECODING_ERROR, 190 ERR_PW_EXPIRED_NO_VALUE.get()); 191 } 192 193 try 194 { 195 Integer.parseInt(value.stringValue()); 196 } 197 catch (final NumberFormatException nfe) 198 { 199 Debug.debugException(nfe); 200 throw new LDAPException(ResultCode.DECODING_ERROR, 201 ERR_PW_EXPIRED_VALUE_NOT_INTEGER.get(), nfe); 202 } 203 } 204 205 206 207 /** 208 * {@inheritDoc} 209 */ 210 @Override() 211 @NotNull() 212 public PasswordExpiredControl decodeControl( 213 @NotNull final String oid, final boolean isCritical, 214 @Nullable final ASN1OctetString value) 215 throws LDAPException 216 { 217 return new PasswordExpiredControl(oid, isCritical, value); 218 } 219 220 221 222 /** 223 * Extracts a password expired control from the provided result. 224 * 225 * @param result The result from which to retrieve the password expired 226 * control. 227 * 228 * @return The password expired control contained in the provided result, or 229 * {@code null} if the result did not contain a password expired 230 * control. 231 * 232 * @throws LDAPException If a problem is encountered while attempting to 233 * decode the password expired control contained in 234 * the provided result. 235 */ 236 @Nullable() 237 public static PasswordExpiredControl get(@NotNull final LDAPResult result) 238 throws LDAPException 239 { 240 final Control c = result.getResponseControl(PASSWORD_EXPIRED_OID); 241 if (c == null) 242 { 243 return null; 244 } 245 246 if (c instanceof PasswordExpiredControl) 247 { 248 return (PasswordExpiredControl) c; 249 } 250 else 251 { 252 return new PasswordExpiredControl(c.getOID(), c.isCritical(), 253 c.getValue()); 254 } 255 } 256 257 258 259 /** 260 * Extracts a password expired control from the provided exception. 261 * 262 * @param exception The exception from which to retrieve the password 263 * expired control. 264 * 265 * @return The password expired control contained in the provided exception, 266 * or {@code null} if the exception did not contain a password 267 * expired control. 268 * 269 * @throws LDAPException If a problem is encountered while attempting to 270 * decode the password expired control contained in 271 * the provided exception. 272 */ 273 @Nullable() 274 public static PasswordExpiredControl get( 275 @NotNull final LDAPException exception) 276 throws LDAPException 277 { 278 return get(exception.toLDAPResult()); 279 } 280 281 282 283 /** 284 * {@inheritDoc} 285 */ 286 @Override() 287 @NotNull() 288 public String getControlName() 289 { 290 return INFO_CONTROL_NAME_PW_EXPIRED.get(); 291 } 292 293 294 295 /** 296 * Retrieves a representation of this password expired control as a JSON 297 * object. The JSON object uses the following fields (note that since this 298 * control has a fixed value that is always exactly the same for all instances 299 * of the control, neither the {@code value-base64} nor {@code value-json} 300 * fields may be present): 301 * <UL> 302 * <LI> 303 * {@code oid} -- A mandatory string field whose value is the object 304 * identifier for this control. For the password expired control, the OID 305 * is "2.16.840.1.113730.3.4.4". 306 * </LI> 307 * <LI> 308 * {@code control-name} -- An optional string field whose value is a 309 * human-readable name for this control. This field is only intended for 310 * descriptive purposes, and when decoding a control, the {@code oid} 311 * field should be used to identify the type of control. 312 * </LI> 313 * <LI> 314 * {@code criticality} -- A mandatory Boolean field used to indicate 315 * whether this control is considered critical. 316 * </LI> 317 * </UL> 318 * 319 * @return A JSON object that contains a representation of this control. 320 */ 321 @Override() 322 @NotNull() 323 public JSONObject toJSONControl() 324 { 325 return new JSONObject( 326 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 327 PASSWORD_EXPIRED_OID), 328 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 329 INFO_CONTROL_NAME_PW_EXPIRED.get()), 330 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 331 isCritical())); 332 } 333 334 335 336 /** 337 * Attempts to decode the provided object as a JSON representation of a 338 * password expired control. 339 * 340 * @param controlObject The JSON object to be decoded. It must not be 341 * {@code null}. 342 * @param strict Indicates whether to use strict mode when decoding 343 * the provided JSON object. If this is {@code true}, 344 * then this method will throw an exception if the 345 * provided JSON object contains any unrecognized 346 * fields. If this is {@code false}, then unrecognized 347 * fields will be ignored. 348 * 349 * @return The password expired control that was decoded from the provided 350 * JSON object. 351 * 352 * @throws LDAPException If the provided JSON object cannot be parsed as a 353 * valid password expired control. 354 */ 355 @NotNull() 356 public static PasswordExpiredControl decodeJSONControl( 357 @NotNull final JSONObject controlObject, 358 final boolean strict) 359 throws LDAPException 360 { 361 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 362 controlObject, strict, false, false); 363 364 return new PasswordExpiredControl(); 365 } 366 367 368 369 /** 370 * {@inheritDoc} 371 */ 372 @Override() 373 public void toString(@NotNull final StringBuilder buffer) 374 { 375 buffer.append("PasswordExpiredControl(isCritical="); 376 buffer.append(isCritical()); 377 buffer.append(')'); 378 } 379}