001/* 002 * 003 * Copyright (C) 2017 Ping Identity Corporation 004 * All rights reserved. 005 * 006 * The contents of this file are the property of Ping Identity Corporation. 007 * You may not copy or use this file, in either source code or executable 008 * form, except in compliance with terms set by Ping Identity Corporation. 009 * For further information please contact: 010 * 011 * Ping Identity Corporation 012 * 1099 18th St Suite 2950 013 * Denver, CO 80202 014 * 303.468.2900 015 * http://www.pingidentity.com 016 * 017 */ 018package com.pingidentity.ds.plugin; 019 020import com.unboundid.directory.sdk.common.internal.Reconfigurable; 021import com.unboundid.directory.sdk.common.operation.AddRequest; 022import com.unboundid.directory.sdk.common.operation.SearchRequest; 023import com.unboundid.directory.sdk.common.operation.*; 024import com.unboundid.directory.sdk.common.types.ActiveOperationContext; 025import com.unboundid.directory.sdk.common.types.ActiveSearchOperationContext; 026import com.unboundid.directory.sdk.common.types.LogSeverity; 027import com.unboundid.directory.sdk.common.types.UpdatableEntry; 028import com.unboundid.directory.sdk.ds.api.Plugin; 029import com.unboundid.directory.sdk.ds.config.PluginConfig; 030import com.unboundid.directory.sdk.ds.types.DirectoryServerContext; 031import com.unboundid.directory.sdk.ds.types.PostOperationPluginResult; 032import com.unboundid.directory.sdk.ds.types.PreParsePluginResult; 033import com.unboundid.directory.sdk.ds.types.SearchEntryPluginResult; 034import com.unboundid.ldap.sdk.*; 035import com.unboundid.ldap.sdk.ExtendedResult; 036import com.unboundid.ldap.sdk.unboundidds.extensions.PasswordPolicyStateExtendedRequest; 037import com.unboundid.ldap.sdk.unboundidds.extensions.PasswordPolicyStateExtendedResult; 038import com.unboundid.ldap.sdk.unboundidds.extensions.PasswordPolicyStateOperation; 039import com.unboundid.util.StaticUtils; 040import com.unboundid.util.args.*; 041 042import java.text.ParseException; 043import java.text.SimpleDateFormat; 044import java.util.*; 045import java.util.concurrent.CopyOnWriteArrayList; 046 047import static com.unboundid.ldap.sdk.unboundidds.extensions.PasswordPolicyStateOperation.*; 048 049 050/** 051 * This class provides an implementation of a Server SDK plugin that can be 052 * used to interact with password policy state without having recourse to using 053 * the PasswordPolicyStateExtendedRequest 054 * <p> 055 * This might be useful for developers interacting with our products through third-party 056 * LDAP libraries without extended operation support altogether, like LINQ to LDAP for .Net 057 */ 058public class PwpUserState extends Plugin implements Reconfigurable<PluginConfig> 059{ 060 061 /** 062 * The name of the argument used to specify the SimpleDateFormat date format to use 063 * when parsing provided dates 064 */ 065 private static final String ARG_TIME_FORMAT = "time-format"; 066 067 /** 068 * The default value for the date format 069 */ 070 private static final String ARG_TIME_FORMAT_DEFAULT = "yyyyMMddHHmmss.SSS'Z'"; 071 072 073 /** 074 * The consistent attribute prefix 075 */ 076 private static final String ATTR_EXTENSION = "ds-pwp-user-state"; 077 private static final String ATTR_SEPARATOR = "-"; 078 private static final String ATTR_PREFIX = ATTR_EXTENSION + ATTR_SEPARATOR; 079 080 /** 081 * The wildcard attribute to retrieve all available password policy account state attributes 082 */ 083 private static final String ATTR_GET_ALL = ATTR_PREFIX + "get-all"; 084 085 /** 086 * The attribute to get or set the account activation time 087 */ 088 private static final String ATTR_ACCOUNT_ACTIVATION_TIME = ATTR_PREFIX + "account-activation-time"; 089 090 /** 091 * The attribute to get or set the account disabled state 092 */ 093 private static final String ATTR_ACCOUNT_DISABLED = ATTR_PREFIX + "account-disabled"; 094 095 /** 096 * The attribute to get, set or clear the account expiration time 097 */ 098 private static final String ATTR_ACCOUNT_EXPIRATION_TIME = ATTR_PREFIX + "account-expiration-time"; 099 100 /** 101 * The attribute to get whether the account has expired 102 */ 103 private static final String ATTR_ACCOUNT_EXPIRED = ATTR_PREFIX + "account-expired"; 104 105 /** 106 * The attribute to get whether the account is locked due to excessive authentication failures 107 */ 108 private static final String ATTR_ACCOUNT_FAILURE_LOCKED = ATTR_PREFIX + "account-failure-locked"; 109 110 /** 111 * The attribute to get whether the account is locked due to excessive idle time 112 */ 113 private static final String ATTR_ACCOUNT_IDLE_LOCKED = ATTR_PREFIX + "account-idle-locked"; 114 115 /** 116 * 117 */ 118 private static final String ATTR_ACCOUNT_NOT_ACTIVE_YET = ATTR_PREFIX + "account-not-active-yet"; 119 private static final String ATTR_ACCOUNT_RESET_LOCKED = ATTR_PREFIX + "account-reset-locked"; 120 private static final String ATTR_ACCOUNT_USABILITY_ERROR = ATTR_PREFIX + "account-usability-error"; 121 private static final String ATTR_ACCOUNT_USABILITY_NOTICE = ATTR_PREFIX + "account-usability-notice"; 122 private static final String ATTR_ACCOUNT_USABILITY_WARNING = ATTR_PREFIX + "account-usability-warning"; 123 private static final String ATTR_ACCOUNT_USABLE = ATTR_PREFIX + "account-usable"; 124 private static final String ATTR_AUTH_FAILURE_TIME = ATTR_PREFIX + "auth-failure-time"; 125 private static final String ATTR_AVAILABLE_SASL_MECHANISM = ATTR_PREFIX + "available-sasl-mechanism"; 126 private static final String ATTR_AVAILABLE_TOTP_DELIVERY_MECHANISM = ATTR_PREFIX + 127 "available-totp-delivery-mechanism"; 128 private static final String ATTR_FAILURE_LOCKOUT_TIME = ATTR_PREFIX + "failure-lockout-time"; 129 private static final String ATTR_GRACE_LOGIN_USE_TIME = ATTR_PREFIX + "grace-login-use-time"; 130 private static final String ATTR_IDLE_LOCKOUT_TIME = ATTR_PREFIX + "idle-lockout-time"; 131 private static final String ATTR_LAST_LOGIN_IP_ADDRESS = ATTR_PREFIX + "last-login-ip-address"; 132 private static final String ATTR_LAST_LOGIN_TIME = ATTR_PREFIX + "last-login-time"; 133 private static final String ATTR_PW_CHANGED_BY_REQUIRED_TIME = ATTR_PREFIX + "pw-changed-by-required-time"; 134 private static final String ATTR_PW_CHANGED_TIME = ATTR_PREFIX + "pw-changed-time"; 135 private static final String ATTR_PW_EXPIRATION_TIME = ATTR_PREFIX + "pw-expiration-time"; 136 private static final String ATTR_PW_EXPIRATION_WARNED_TIME = ATTR_PREFIX + "pw-expiration-warned-time"; 137 private static final String ATTR_PW_EXPIRED = ATTR_PREFIX + "pw-expired"; 138 private static final String ATTR_PW_HISTORY = ATTR_PREFIX + "pw-history"; 139 private static final String ATTR_PW_HISTORY_COUNT = ATTR_PREFIX + "pw-history-count"; 140 private static final String ATTR_PW_RESET = ATTR_PREFIX + "pw-reset"; 141 private static final String ATTR_PW_RETIRED_TIME = ATTR_PREFIX + "pw-retired-time"; 142 private static final String ATTR_PWP_DN = ATTR_PREFIX + "pwp-dn"; 143 private static final String ATTR_REMAINING_AUTH_FAILURE_COUNT = ATTR_PREFIX + "remaining-auth-failure-count"; 144 private static final String ATTR_REMAINING_GRACE_LOGIN_COUNT = ATTR_PREFIX + "remaining-grace-login-count"; 145 //private static final String ATTR_RESET_LOCKOUT_TIME = ATTR_PREFIX + "reset-lockout-time"; 146 147 private static final String ATTR_HAS_RETIRED_PASSWORD = ATTR_PREFIX + "has-retired-password"; 148 private static final String ATTR_RETIRED_PASSWORD_EXPIRATION_TIME = ATTR_PREFIX + 149 "retired-password-expiration-time"; 150 private static final String ATTR_SECONDS_UNTIL_ACCOUNT_ACTIVATION = ATTR_PREFIX + 151 "seconds-until-account-activation"; 152 private static final String ATTR_SECONDS_UNTIL_ACCOUNT_EXPIRATION = ATTR_PREFIX + 153 "seconds-until-account-expiration"; 154 private static final String ATTR_SECONDS_UNTIL_AUTH_FAILURE_UNLOCK = ATTR_PREFIX + 155 "seconds-until-auth-failure-unlock"; 156 private static final String ATTR_SECONDS_UNTIL_IDLE_LOCKOUT = ATTR_PREFIX + "seconds-until-idle-lockout"; 157 private static final String ATTR_SECONDS_UNTIL_PW_EXPIRATION = ATTR_PREFIX + "seconds-until-pw-expiration"; 158 private static final String ATTR_SECONDS_UNTIL_PW_EXPIRATION_WARNING = ATTR_PREFIX + 159 "seconds-until-pw-expiration-warning"; 160 private static final String ATTR_SECONDS_UNTIL_PW_RESET_LOCKOUT = ATTR_PREFIX + "seconds-until-pw-reset-lockout"; 161 private static final String ATTR_SECONDS_UNTIL_REQUIRED_CHANGED_TIME = ATTR_PREFIX + 162 "seconds-until-required-changed-time"; 163 164 private static final String NOW = "now"; 165 166 167 // The date format to parse provided dates as configured 168 private volatile SimpleDateFormat dateFormatter; 169 170 // The server context for this instance of the product 171 private volatile DirectoryServerContext serverContext; 172 private HashMap<Integer, String> opsMap; 173 174 /** 175 * Retrieves the name for this extension. 176 * 177 * @return The name for this extension. 178 */ 179 @Override 180 public String getExtensionName() 181 { 182 return "Password Policy User State Plugin"; 183 } 184 185 /** 186 * Retrieves a description for this extension. 187 * 188 * @return A description for this extension. 189 */ 190 @Override 191 public String[] getExtensionDescription() 192 { 193 return new String[]{"This extension allows to retrieve or set user state without directly using the " + 194 "PasswordPolicyStateExtendedRequest", 195 "Providing a single trigger attribute (" + ATTR_GET_ALL + ") along with a search request will " + 196 "retrieve all available " + 197 "information. " + 198 "The following password policy state operations are supported:<br/>" + 199 "<ul>" + 200 "<li>OP_TYPE_GET_ACCOUNT_ACTIVATION_TIME</li>" + 201 "<li>OP_TYPE_GET_ACCOUNT_DISABLED_STATE</li>" + 202 "<li>OP_TYPE_GET_ACCOUNT_EXPIRATION_TIME</li>" + 203 "<li>OP_TYPE_GET_ACCOUNT_USABILITY_ERRORS</li>" + 204 "<li>OP_TYPE_GET_ACCOUNT_USABILITY_NOTICES</li>" + 205 "<li>OP_TYPE_GET_ACCOUNT_USABILITY_WARNINGS</li>" + 206 "<li>OP_TYPE_GET_AUTH_FAILURE_TIMES</li>" + 207 "<li>OP_TYPE_GET_GRACE_LOGIN_USE_TIMES</li>" + 208 "<li>OP_TYPE_GET_LAST_LOGIN_IP_ADDRESS</li>" + 209 "<li>OP_TYPE_GET_LAST_LOGIN_TIME</li>" + 210 "<li>OP_TYPE_GET_PASSWORD_RETIRED_TIME</li>" + 211 "<li>OP_TYPE_GET_PW_CHANGED_BY_REQUIRED_TIME</li>" + 212 "<li>OP_TYPE_GET_PW_CHANGED_TIME</li>" + 213 "<li>OP_TYPE_GET_PW_EXPIRATION_WARNED_TIME</li>" + 214 "<li>OP_TYPE_GET_PW_HISTORY</li>" + 215 "<li>OP_TYPE_GET_PW_POLICY_DN</li>" + 216 "<li>OP_TYPE_GET_PW_RESET_STATE</li>" + 217 "<li>OP_TYPE_GET_REMAINING_AUTH_FAILURE_COUNT</li>" + 218 "<li>OP_TYPE_GET_REMAINING_GRACE_LOGIN_COUNT</li>" + 219 "<li>OP_TYPE_GET_RETIRED_PASSWORD_EXPIRATION_TIME</li>" + 220 "<li>OP_TYPE_GET_SECONDS_UNTIL_ACCOUNT_ACTIVATION</li>" + 221 "<li>OP_TYPE_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION</li>" + 222 "<li>OP_TYPE_GET_SECONDS_UNTIL_AUTH_FAILURE_UNLOCK</li>" + 223 "<li>OP_TYPE_GET_SECONDS_UNTIL_IDLE_LOCKOUT</li>" + 224 "<li>OP_TYPE_GET_SECONDS_UNTIL_PW_EXPIRATION</li>" + 225 "<li>OP_TYPE_GET_SECONDS_UNTIL_PW_EXPIRATION_WARNING</li>" + 226 "<li>OP_TYPE_GET_SECONDS_UNTIL_PW_RESET_LOCKOUT</li>" + 227 "<li>OP_TYPE_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME</li>" + 228 "<li>OP_TYPE_HAS_RETIRED_PASSWORD</li>" + 229 "</ul>", 230 "The following attributes are available to use in a modify operation" + 231 ":<br/>" + 232 "<ul>" + 233 "<li>" + ATTR_ACCOUNT_ACTIVATION_TIME + "</li>" + 234 "<li>" + ATTR_ACCOUNT_DISABLED + "</li>" + 235 "<li>" + ATTR_ACCOUNT_EXPIRATION_TIME + "</li>" + 236 "<li>" + ATTR_ACCOUNT_EXPIRED + "</li>" + 237 "<li>" + ATTR_ACCOUNT_FAILURE_LOCKED + "</li>" + 238 "<li>" + ATTR_ACCOUNT_IDLE_LOCKED + "</li>" + 239 "<li>" + ATTR_ACCOUNT_NOT_ACTIVE_YET + "</li>" + 240 "<li>" + ATTR_ACCOUNT_RESET_LOCKED + "</li>" + 241 "<li>" + ATTR_ACCOUNT_USABILITY_ERROR + "</li>" + 242 "<li>" + ATTR_ACCOUNT_USABILITY_NOTICE + "</li>" + 243 "<li>" + ATTR_ACCOUNT_USABILITY_WARNING + "</li>" + 244 "<li>" + ATTR_ACCOUNT_USABLE + "</li>" + 245 "<li>" + ATTR_AUTH_FAILURE_TIME + "</li>" + 246 "<li>" + ATTR_AVAILABLE_SASL_MECHANISM + "</li>" + 247 "<li>" + ATTR_AVAILABLE_TOTP_DELIVERY_MECHANISM + "</li>" + 248 "<li>" + ATTR_FAILURE_LOCKOUT_TIME + "</li>" + 249 "<li>" + ATTR_GRACE_LOGIN_USE_TIME + "</li>" + 250 "<li>" + ATTR_IDLE_LOCKOUT_TIME + "</li>" + 251 "<li>" + ATTR_LAST_LOGIN_IP_ADDRESS + "</li>" + 252 "<li>" + ATTR_LAST_LOGIN_TIME + "</li>" + 253 "<li>" + ATTR_PW_CHANGED_BY_REQUIRED_TIME + "</li>" + 254 "<li>" + ATTR_PW_CHANGED_TIME + "</li>" + 255 "<li>" + ATTR_PW_EXPIRATION_TIME + "</li>" + 256 "<li>" + ATTR_PW_EXPIRATION_WARNED_TIME + "</li>" + 257 "<li>" + ATTR_PW_EXPIRED + "</li>" + 258 "<li>" + ATTR_PW_HISTORY + "</li>" + 259 "<li>" + ATTR_PW_HISTORY_COUNT + "</li>" + 260 "<li>" + ATTR_PW_RESET + "</li>" + 261 "<li>" + ATTR_PW_RETIRED_TIME + "</li>" + 262 "<li>" + ATTR_PWP_DN + "</li>" + 263 "<li>" + ATTR_REMAINING_AUTH_FAILURE_COUNT + "</li>" + 264 "<li>" + ATTR_REMAINING_GRACE_LOGIN_COUNT + "</li>" + 265 //"<li>" + ATTR_RESET_LOCKOUT_TIME + "</li>" + 266 "<li>" + ATTR_HAS_RETIRED_PASSWORD + "</li>" + 267 "<li>" + ATTR_RETIRED_PASSWORD_EXPIRATION_TIME + "</li>" + 268 "<li>" + ATTR_SECONDS_UNTIL_ACCOUNT_ACTIVATION + "</li>" + 269 "<li>" + ATTR_SECONDS_UNTIL_ACCOUNT_EXPIRATION + "</li>" + 270 "<li>" + ATTR_SECONDS_UNTIL_AUTH_FAILURE_UNLOCK + "</li>" + 271 "<li>" + ATTR_SECONDS_UNTIL_IDLE_LOCKOUT + "</li>" + 272 "<li>" + ATTR_SECONDS_UNTIL_PW_EXPIRATION + "</li>" + 273 "<li>" + ATTR_SECONDS_UNTIL_PW_EXPIRATION_WARNING + "</li>" + 274 "<li>" + ATTR_SECONDS_UNTIL_PW_RESET_LOCKOUT + "</li>" + 275 "<li>" + ATTR_SECONDS_UNTIL_REQUIRED_CHANGED_TIME + "</li>" + 276 "</ul>" + 277 "<p>Example usage: <br/>" + 278 "ldapmodify -D cn=directory\\ manager -w password<br/>" + 279 "dn: uid=user.0,ou=people,dc=example,dc=com</br/>" + 280 "changetype: modify<br/>" + 281 "add: " + ATTR_ACCOUNT_DISABLED + "<br/>" + 282 ATTR_ACCOUNT_DISABLED + ": true<br/>" + 283 "<br/>" + 284 "</p>"}; 285 } 286 287 /* 288bin/ldapmodify 289 290dn: uid=user.0,ou=people,o=data 291changetype: modify 292add: ds-pwp-user-state-set-account-disabled 293ds-pwp-user-state-set-account-disabled: false 294 295# Processing MODIFY request for uid=user.0,ou=people,o=data 296# MODIFY operation successful for DN uid=user.0,ou=people,o=data 297 298bin/ldapsearch -b uid=user.0,ou=people,o=data -s base "(&)getUserState 299 300dn: uid=user.0,ou=People,o=data 301ds-pwp-user-state-password-policy-dn: cn=Default Password Policy,cn=Password Policies,cn=config 302ds-pwp-user-state-account-usability-errors: code=1 name=account-disabled message=The account has been disabled 303by an administrator 304ds-pwp-user-state-password-changed-time: 19700101000000.000Z 305ds-pwp-user-state-account-disabled-state: true 306ds-pwp-user-state-password-reset-state: false 307ds-pwp-user-state-remaining-grace-login-count: 0 308ds-pwp-user-state-has-retired-password: false 309 310bin/ldapmodify 311dn: uid=user.0,ou=people,o=data 312changetype: modify 313add: ds-pwp-user-state-clear-account-disabled 314ds-pwp-user-state-clear-account-disabled: true 315 316 317bin/ldapsearch -b uid=user.0,ou=people,o=data -s base "(&)getUserState 318 319dn: uid=user.0,ou=People,o=data 320ds-pwp-user-state-password-policy-dn: cn=Default Password Policy,cn=Password Policies,cn=config 321ds-pwp-user-state-password-changed-time: 19700101000000.000Z 322ds-pwp-user-state-account-disabled-state: false 323ds-pwp-user-state-password-reset-state: false 324ds-pwp-user-state-remaining-grace-login-count: 0 325ds-pwp-user-state-has-retired-password: false 326 327 */ 328 329 /** 330 * Updates the provided argument parser to include the arguments for this 331 * extension. 332 * 333 * @param parser The argument parser to update. 334 */ 335 @Override 336 public void defineConfigArguments(final ArgumentParser parser) 337 throws ArgumentException 338 { 339 StringArgument dateFormatArgument = new StringArgument(ARG.NO_SHORTCUT, ARG_TIME_FORMAT, ARG.OPTIONAL, ARG 340 .UNIQUE, "{date-format}", "the" + 341 " " + 342 "date time format as " + 343 "per Java SimpleDateFormat specification. (Default: " + ARG_TIME_FORMAT_DEFAULT + ")", 344 ARG_TIME_FORMAT_DEFAULT); 345 /* 346 * ensure that the provided argument value is a valid pattern to avoid downstream issues 347 */ 348 dateFormatArgument.addValueValidator(new ArgumentValueValidator() 349 { 350 @Override 351 public void validateArgumentValue(Argument argument, String valueString) throws ArgumentException 352 { 353 try 354 { 355 new SimpleDateFormat(parser.getStringArgument(ARG_TIME_FORMAT).getValue()); 356 } catch (IllegalArgumentException iae) 357 { 358 throw new ArgumentException(StaticUtils.getExceptionMessage(iae)); 359 } 360 } 361 }); 362 parser.addArgument(dateFormatArgument); 363 } 364 365 /** 366 * This method allows to validate configuration for the instance of the plugin is going to allow it to perform 367 * its task correctly. 368 * 369 * @param config the plugin configuration 370 * @param parser the argument parser for this extension 371 * @param unacceptableReasons a list of reasons for which the configuration was not acceptable 372 * @return Boolean the result of evaluating the parameters of the provided parser 373 */ 374 @Override 375 public boolean isConfigurationAcceptable(final PluginConfig config, 376 final ArgumentParser parser, 377 List<String> unacceptableReasons) 378 { 379 return true; 380 } 381 382 /** 383 * This method is called when a configuration change is made to the instance of the plugin. 384 * It is called by initializePlugin when the plugin is first instantiated and upon any configuration change 385 * thereafter 386 * 387 * @param config the plugin configuration 388 * @param parser the argument parser for this extension 389 * @param adminActionsRequired A list of messages of administrative actions required in order for configuration to 390 * be applied 391 * @param messages A list of messages relating to applying the configuration for this extension 392 * @return an LDAP Result Code. ResultCode.SUCCESS if all arguments were satisfactory 393 */ 394 @Override 395 public ResultCode applyConfiguration(final PluginConfig config, 396 final ArgumentParser parser, 397 List<String> adminActionsRequired, 398 List<String> messages) 399 { 400 try 401 { 402 dateFormatter = new SimpleDateFormat(parser.getStringArgument(ARG_TIME_FORMAT).getValue()); 403 // disabling formatting leniency to avoid processing dates with inconsistent or unexpected formats 404 dateFormatter.setLenient(false); 405 } catch (IllegalArgumentException iae) 406 { 407 messages.add(StaticUtils.getExceptionMessage(iae)); 408 } 409 return ResultCode.SUCCESS; 410 } 411 412 /** 413 * Initializes this plugin. 414 * This method is called upon instantiation of the plugin and should 415 * ensure proper initialization. 416 * 417 * @param serverContext A handle to the server context for the server in 418 * which this extension is running. 419 * @param config The general configuration for this plugin. 420 * @param parser The argument parser which has been initialized from 421 * the configuration for this plugin. 422 * @throws LDAPException If a problem occurs while initializing this plugin. 423 */ 424 @Override 425 public void initializePlugin(final DirectoryServerContext serverContext, 426 final PluginConfig config, 427 final ArgumentParser parser) 428 throws LDAPException 429 { 430 this.serverContext = serverContext; 431 432 opsMap = new HashMap<>(); 433 lcAddToMap(opsMap, OP_TYPE_GET_ACCOUNT_ACTIVATION_TIME, ATTR_ACCOUNT_ACTIVATION_TIME); 434 lcAddToMap(opsMap, OP_TYPE_GET_ACCOUNT_DISABLED_STATE, ATTR_ACCOUNT_DISABLED); 435 lcAddToMap(opsMap, OP_TYPE_GET_ACCOUNT_EXPIRATION_TIME, ATTR_ACCOUNT_EXPIRATION_TIME); 436 lcAddToMap(opsMap, OP_TYPE_GET_ACCOUNT_USABILITY_ERRORS, ATTR_ACCOUNT_USABILITY_ERROR); 437 lcAddToMap(opsMap, OP_TYPE_GET_ACCOUNT_USABILITY_NOTICES, ATTR_ACCOUNT_USABILITY_NOTICE); 438 lcAddToMap(opsMap, OP_TYPE_GET_AVAILABLE_OTP_DELIVERY_MECHANISMS, ATTR_AVAILABLE_TOTP_DELIVERY_MECHANISM); 439 lcAddToMap(opsMap, OP_TYPE_GET_ACCOUNT_USABILITY_WARNINGS, ATTR_ACCOUNT_USABILITY_WARNING); 440 lcAddToMap(opsMap, OP_TYPE_GET_ACCOUNT_IS_USABLE, ATTR_ACCOUNT_USABLE); 441 lcAddToMap(opsMap, OP_TYPE_GET_ACCOUNT_IS_NOT_YET_ACTIVE, ATTR_ACCOUNT_NOT_ACTIVE_YET); 442 lcAddToMap(opsMap, OP_TYPE_GET_ACCOUNT_IS_RESET_LOCKED, ATTR_ACCOUNT_RESET_LOCKED); 443 lcAddToMap(opsMap, OP_TYPE_GET_IDLE_LOCKOUT_TIME, ATTR_IDLE_LOCKOUT_TIME); 444 lcAddToMap(opsMap, OP_TYPE_GET_FAILURE_LOCKOUT_TIME, ATTR_FAILURE_LOCKOUT_TIME); 445 lcAddToMap(opsMap, OP_TYPE_GET_AUTH_FAILURE_TIMES, ATTR_AUTH_FAILURE_TIME); 446 lcAddToMap(opsMap, OP_TYPE_GET_AVAILABLE_SASL_MECHANISMS, ATTR_AVAILABLE_SASL_MECHANISM); 447 lcAddToMap(opsMap, OP_TYPE_GET_GRACE_LOGIN_USE_TIMES, ATTR_GRACE_LOGIN_USE_TIME); 448 lcAddToMap(opsMap, OP_TYPE_GET_LAST_LOGIN_IP_ADDRESS, ATTR_LAST_LOGIN_IP_ADDRESS); 449 lcAddToMap(opsMap, OP_TYPE_GET_LAST_LOGIN_TIME, ATTR_LAST_LOGIN_TIME); 450 lcAddToMap(opsMap, OP_TYPE_GET_PASSWORD_RETIRED_TIME, ATTR_PW_RETIRED_TIME); 451 lcAddToMap(opsMap, OP_TYPE_GET_PW_CHANGED_BY_REQUIRED_TIME, ATTR_PW_CHANGED_BY_REQUIRED_TIME); 452 lcAddToMap(opsMap, OP_TYPE_GET_PW_CHANGED_TIME, ATTR_PW_CHANGED_TIME); 453 lcAddToMap(opsMap, OP_TYPE_GET_PW_EXPIRATION_WARNED_TIME, ATTR_PW_EXPIRATION_WARNED_TIME); 454 lcAddToMap(opsMap, OP_TYPE_GET_PW_HISTORY, ATTR_PW_HISTORY); 455 lcAddToMap(opsMap, OP_TYPE_GET_PW_POLICY_DN, ATTR_PWP_DN); 456 lcAddToMap(opsMap, OP_TYPE_GET_PW_RESET_STATE, ATTR_PW_RESET); 457 lcAddToMap(opsMap, OP_TYPE_GET_REMAINING_AUTH_FAILURE_COUNT, ATTR_REMAINING_AUTH_FAILURE_COUNT); 458 lcAddToMap(opsMap, OP_TYPE_GET_REMAINING_GRACE_LOGIN_COUNT, ATTR_REMAINING_GRACE_LOGIN_COUNT); 459 lcAddToMap(opsMap, OP_TYPE_GET_RETIRED_PASSWORD_EXPIRATION_TIME, ATTR_RETIRED_PASSWORD_EXPIRATION_TIME); 460 lcAddToMap(opsMap, OP_TYPE_GET_SECONDS_UNTIL_ACCOUNT_ACTIVATION, ATTR_SECONDS_UNTIL_ACCOUNT_ACTIVATION); 461 lcAddToMap(opsMap, OP_TYPE_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION, ATTR_SECONDS_UNTIL_ACCOUNT_EXPIRATION); 462 lcAddToMap(opsMap, OP_TYPE_GET_SECONDS_UNTIL_AUTH_FAILURE_UNLOCK, ATTR_SECONDS_UNTIL_AUTH_FAILURE_UNLOCK); 463 lcAddToMap(opsMap, OP_TYPE_GET_SECONDS_UNTIL_IDLE_LOCKOUT, ATTR_SECONDS_UNTIL_IDLE_LOCKOUT); 464 lcAddToMap(opsMap, OP_TYPE_GET_SECONDS_UNTIL_PW_EXPIRATION, ATTR_SECONDS_UNTIL_PW_EXPIRATION); 465 lcAddToMap(opsMap, OP_TYPE_GET_SECONDS_UNTIL_PW_EXPIRATION_WARNING, ATTR_SECONDS_UNTIL_PW_EXPIRATION_WARNING); 466 lcAddToMap(opsMap, OP_TYPE_GET_SECONDS_UNTIL_PW_RESET_LOCKOUT, ATTR_SECONDS_UNTIL_PW_RESET_LOCKOUT); 467 lcAddToMap(opsMap, OP_TYPE_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME, ATTR_SECONDS_UNTIL_REQUIRED_CHANGED_TIME); 468 lcAddToMap(opsMap, OP_TYPE_HAS_RETIRED_PASSWORD, ATTR_HAS_RETIRED_PASSWORD); 469 lcAddToMap(opsMap, OP_TYPE_GET_PW_EXPIRATION_TIME, ATTR_PW_EXPIRATION_TIME); 470 lcAddToMap(opsMap, OP_TYPE_GET_PW_IS_EXPIRED, ATTR_PW_EXPIRED); 471 lcAddToMap(opsMap, OP_TYPE_GET_PW_HISTORY_COUNT, ATTR_PW_HISTORY_COUNT); 472// lcAddToMap(opsMap,OP_TYPE_GET_RESET_LOCKOUT_TIME, ATTR_RESET_LOCKOUT_TIME); 473 lcAddToMap(opsMap, OP_TYPE_GET_ACCOUNT_IS_EXPIRED, ATTR_ACCOUNT_EXPIRED); 474 lcAddToMap(opsMap, OP_TYPE_GET_ACCOUNT_IS_FAILURE_LOCKED, ATTR_ACCOUNT_FAILURE_LOCKED); 475 lcAddToMap(opsMap, OP_TYPE_GET_ACCOUNT_IS_IDLE_LOCKED, ATTR_ACCOUNT_IDLE_LOCKED); 476 477 478 // Use the applyConfiguration method to set up the plugin. 479 final ArrayList<String> adminActionsRequired = new ArrayList<>(5); 480 final ArrayList<String> messages = new ArrayList<>(5); 481 final ResultCode resultCode = applyConfiguration(config, parser, adminActionsRequired, messages); 482 if (resultCode != ResultCode.SUCCESS) 483 { 484 throw new LDAPException(resultCode, 485 "One or more errors occurred while trying to initialize " + getExtensionName() + " Plugin in '" 486 + config.getConfigObjectDN() + ": " + 487 StaticUtils.concatenateStrings(messages)); 488 } 489 } 490 491 private void lcAddToMap(Map<Integer, String> map, Integer i, String s) 492 { 493 map.put(i, s.toLowerCase()); 494 } 495 496 /** 497 * Performs the necessary processing to add password policy state to entries resulting of an LDAP search request 498 * that includes a trigger attribute as per the configuration of this instance of the plugin. 499 * <p> 500 * If the trigger attribute is included, the extended operation is issued internally and each state return is 501 * added to 502 * the result entry as virtual attributes. 503 * 504 * @param operationContext the operation context 505 * @param request the search request 506 * @param entry the search result entry 507 * @param controls a list of controls provided with the search request 508 * @param result The result that will be returned to the client if 509 * the plugin result indicates that processing on 510 * the operation should be interrupted. It may be 511 * altered if desired. 512 * @return Information about the result of the plugin processing. 513 */ 514 @Override 515 public SearchEntryPluginResult doSearchEntry(final ActiveSearchOperationContext operationContext, 516 final SearchRequest request, 517 UpdatableSearchResult result, 518 UpdatableEntry entry, 519 List<Control> controls) 520 { 521 SearchEntryPluginResult pluginResult = SearchEntryPluginResult.SUCCESS; 522 // nothing to do if the request is null, moving on 523 if (request == null || request.getAttributes() == null || request.getAttributes().isEmpty()) 524 { 525 return pluginResult; 526 } 527 528 // verify that the request matches the criteria, i.e. that it includes the configured trigger attribute 529 List<String> requestedAttributes = request.getAttributes(); 530 531 if (!searchRequestMatch(requestedAttributes)) 532 { 533 return pluginResult; 534 } 535 536 boolean hallPass = false; 537 List<String> pwpUserStateAttributes = new ArrayList<>(); 538 for (int i = 0; i < requestedAttributes.size(); i++) 539 { 540 if (requestedAttributes.get(i).equalsIgnoreCase(ATTR_GET_ALL)) 541 { 542 hallPass = true; 543 break; 544 } else 545 { 546 pwpUserStateAttributes.add(requestedAttributes.get(i).toLowerCase()); 547 } 548 } 549 550 try 551 { 552 // if all criteria have been met, issue the extended operation for the search result entry 553 ExtendedResult extResult = operationContext.getInternalUserConnection().processExtendedOperation(new 554 PasswordPolicyStateExtendedRequest(entry.getDN())); 555 PasswordPolicyStateExtendedResult pwpResult = new PasswordPolicyStateExtendedResult(extResult); 556 if (pwpResult.getResultCode() != ResultCode.SUCCESS) 557 { 558 result.setResultCode(pwpResult.getResultCode()); 559 result.setDiagnosticMessage(pwpResult.getDiagnosticMessage()); 560 return new SearchEntryPluginResult(false, false, false, false); 561 } 562 563 /* 564 add each available password policy state operation result as attribute to the result entry before sending it 565 back to the client. Each attribute is preceded with a prefix as configured 566 */ 567 568 for (PasswordPolicyStateOperation pwpOp : pwpResult.getOperations()) 569 { 570 if (pwpOp == null) continue; 571 // this is always going to be lower case, we ensure that when populating the map 572 String attributeName = opsMap.get(pwpOp.getOperationType()); 573 // because the same precaution is taken when building the requestedAttribute list, case doesn't matter 574 // this is to ensure that if an attribute is explicitly provided as opposed to get-all 575 // we only return the requested attributes 576 // it might important for certain clients 577 if (hallPass || pwpUserStateAttributes.contains(attributeName)) 578 { 579 Attribute attribute = makeAttr(attributeName, pwpOp.getStringValues()); 580 if (attribute == null) continue; 581 entry.addAttribute(attribute); 582 } 583 } 584 } catch (LDAPException e) 585 { 586 serverContext.debugCaught(e); 587 serverContext.logMessage(LogSeverity.MILD_ERROR, e.getDiagnosticMessage()); 588 } 589 590 return pluginResult; 591 } 592 593 private Attribute makeAttr(String name, String... values) 594 { 595 // the attribute name is null? 596 if (name == null || name.isEmpty()) return null; 597 598 // no values provided ? 599 if (values == null) return null; 600 if (values.length == 0) return null; 601 // single null or empty value provided ? 602 if (values.length == 1 && (values[0] == null || values[0].isEmpty())) return null; 603 604 // return an attribute 605 return new Attribute(name, values); 606 } 607 608 /** 609 * Performs processing before the server attempts to parse the LDAP Modify request. 610 * If the request includes attributes that match what this instance of the plugin 611 * is configured to convert to a PasswordPolicyStateExtendedRequest then the 612 * attribute and possibly the attribute value will be parsed and stripped from the 613 * modify request. 614 * <p> 615 * If the instance of the plugin is configured to authenticate the request with TOTP then it fail altogether if 616 * no TOTP code is provided or if it is invalid. 617 * <p> 618 * If the PasswordPolicyStateExtendedRequest is successful and there are remaining modifications, the remaining 619 * modifications will be passed on for normal processing. 620 * 621 * @param operationContext the operation context 622 * @param request the modify request 623 * @param result The result that will be returned to the client if 624 * the plugin result indicates that processing on 625 * the operation should be interrupted. It may be 626 * altered if desired. 627 * @return Information about the result of the plugin processing. 628 */ 629 public PreParsePluginResult doPreParse(final ActiveOperationContext operationContext, 630 UpdatableModifyRequest request, 631 UpdatableModifyResult result) 632 { 633 PreParsePluginResult pluginResult = PreParsePluginResult.SUCCESS; 634 // nothing to do if the request is null, moving on 635 if (request == null) 636 { 637 return pluginResult; 638 } 639 640 // Check if any of the modifications included with the request match 641 if (!modifyRequestMatch(request)) 642 { 643 return pluginResult; 644 } 645 646 647 /* 648 * duplicate the modifications from the LDAP Modify request 649 */ 650 CopyOnWriteArrayList<Modification> mods = new CopyOnWriteArrayList<>(); 651 for (Modification mod : request.getModifications()) 652 { 653 if (mod == null) 654 { 655 continue; 656 } 657 mods.add(mod); 658 } 659 660 // nothing to do if there are no modifications, moving on 661 if (mods.isEmpty()) 662 { 663 return pluginResult; 664 } 665 666 667 /* 668 Build a list of operations to include in the PasswordPolicyStateExtendedRequest 669 */ 670 List<PasswordPolicyStateOperation> ops = new ArrayList<>(); 671 for (Modification mod : request.getModifications()) 672 { 673 if (mod == null) 674 { 675 continue; 676 } 677 678 Attribute modificationAttribute = mod.getAttribute(); 679 680 String attributeName = modificationAttribute.getName(); 681 if (attributeName == null || attributeName.isEmpty()) 682 { 683 mods.remove(mod); 684 continue; 685 } 686 687 String attributeValue = modificationAttribute.getValue(); 688 689 // Ignore add modifications with empty values 690 if (mod.getModificationType().equals(ModificationType.ADD) && (attributeValue == null || attributeValue 691 .isEmpty())) 692 { 693 mods.remove(mod); 694 continue; 695 } 696 697 /* 698 if (ATTR_PW_EXPIRED.equalsIgnoreCase(attributeName)) 699 { 700 mods.remove(mod); 701 if (isDelete(mod)) 702 { 703 ops.add(PasswordPolicyStateOperation.createClearAccountExpirationTimeOperation()); 704 } else 705 { 706 String[] values = mod.getValues(); 707 if (values.length > 1) 708 { 709 result.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 710 String message = "modification for " + ATTR_PW_EXPIRED 711 + " must have a single boolean value. Ignoring."; 712 result.setDiagnosticMessage(message); 713 serverContext.logMessage(LogSeverity.MILD_ERROR, message); 714 continue; 715 } 716 ops.add(PasswordPolicyStateOperation.createSetAccountExpirationTimeOperation((values[0]))); 717 } 718 } 719 */ 720 721 /* 722 manage the current time to the set of times that the user has unsuccessfully tried to authenticate since 723 the last successful authentication 724 */ 725 if (ATTR_AUTH_FAILURE_TIME.equalsIgnoreCase(attributeName)) 726 { 727 mods.remove(mod); 728 if (isDelete(mod)) 729 { 730 ops.add(PasswordPolicyStateOperation.createClearAuthenticationFailureTimesOperation()); 731 } else 732 { 733 // when here, the modification is neither a delete nor a replace with an empty value 734 String[] values = mod.getValues(); 735 Date[] dates = new Date[values.length]; 736 int i = 0; 737 for (String value : values) 738 { 739 if (isNow(value)) 740 { 741 dates[i++] = new Date(); 742 } else 743 { 744 try 745 { 746 Date d = dateFormatter.parse(value); 747 dates[i++] = d; 748 } catch (ParseException e) 749 { 750 result.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX); 751 result.setDiagnosticMessage("The syntax for " + ATTR_AUTH_FAILURE_TIME + " " + 752 "was not valid. The expected format is " + dateFormatter.toPattern()); 753 return new PreParsePluginResult(false, false, true, true); 754 } 755 } 756 if (mod.getModificationType() == ModificationType.REPLACE) 757 { 758 ops.add(PasswordPolicyStateOperation.createSetAuthenticationFailureTimesOperation 759 (dates)); 760 } else 761 { 762 ops.add(PasswordPolicyStateOperation.createAddAuthenticationFailureTimeOperation()); 763 } 764 } 765 } 766 continue; 767 } 768 769 if (ATTR_ACCOUNT_FAILURE_LOCKED.equalsIgnoreCase(attributeName)) 770 { 771 mods.remove(mod); 772 if (isDelete(mod)) 773 { 774 ops.add(PasswordPolicyStateOperation.createSetAccountIsFailureLockedOperation(false)); 775 } else 776 { 777 String[] values = mod.getValues(); 778 if (values.length > 1) 779 { 780 result.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 781 String message = "modification for " + ATTR_ACCOUNT_FAILURE_LOCKED 782 + " must have a single boolean value. Ignoring."; 783 result.setDiagnosticMessage(message); 784 serverContext.logMessage(LogSeverity.MILD_ERROR, message); 785 continue; 786 } 787 ops.add(PasswordPolicyStateOperation.createSetAccountIsFailureLockedOperation 788 (parseBooleanPermissive(values[0]))); 789 } 790 continue; 791 } 792 793 /* 794 add the current time to the set of times that the user has authenticated using grace logins since his/her 795 password expired 796 */ 797 if (ATTR_GRACE_LOGIN_USE_TIME.equalsIgnoreCase(attributeName)) 798 { 799 if (isDelete(mod)) 800 { 801 ops.add(PasswordPolicyStateOperation.createClearGraceLoginUseTimesOperation()); 802 } else 803 { 804 String[] values = mod.getValues(); 805 Date[] dates = new Date[values.length]; 806 int i = 0; 807 for (String value : values) 808 { 809 if (isNow(value)) 810 { 811 dates[i++] = new Date(); 812 } else 813 { 814 try 815 { 816 Date d = dateFormatter.parse(value); 817 dates[i++] = d; 818 } catch (ParseException e) 819 { 820 result.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX); 821 result.setDiagnosticMessage("The syntax for " + ATTR_GRACE_LOGIN_USE_TIME + " " + 822 "was not valid. The expected format is " + dateFormatter.toPattern()); 823 return new PreParsePluginResult(false, false, true, true); 824 } 825 } 826 if (mod.getModificationType() == ModificationType.REPLACE) 827 { 828 ops.add(PasswordPolicyStateOperation.createSetGraceLoginUseTimesOperation(dates)); 829 } else 830 { 831 ops.add(PasswordPolicyStateOperation.createAddGraceLoginUseTimeOperation()); 832 } 833 } 834 } 835 mods.remove(mod); 836 continue; 837 } 838 839 /* 840 clear the account expiration time in the user's entry 841 */ 842 if (ATTR_ACCOUNT_ACTIVATION_TIME.equalsIgnoreCase(attributeName)) 843 { 844 if (isDelete(mod)) 845 { 846 ops.add(PasswordPolicyStateOperation.createClearAccountActivationTimeOperation()); 847 } else 848 { 849 String[] values = mod.getValues(); 850 Date d; 851 if (isNow(values[0])) 852 { 853 d = new Date(); 854 } else 855 { 856 try 857 { 858 d = dateFormatter.parse(values[0]); 859 } catch (ParseException e) 860 { 861 result.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX); 862 result.setDiagnosticMessage("The syntax for " + ATTR_ACCOUNT_ACTIVATION_TIME + " " + 863 "was not valid. The expected format is " + dateFormatter.toPattern()); 864 return new PreParsePluginResult(false, false, true, true); 865 } 866 } 867 ops.add(PasswordPolicyStateOperation.createSetAccountActivationTimeOperation(d)); 868 } 869 870 mods.remove(mod); 871 continue; 872 } 873 874 /* 875 handle user account failure lockout time in the user entry 876 */ 877 if (ATTR_FAILURE_LOCKOUT_TIME.equalsIgnoreCase(attributeName)) 878 { 879 mods.remove(mod); 880 if (isDelete(mod)) 881 { 882 ops.add(PasswordPolicyStateOperation.createSetAccountIsFailureLockedOperation(false)); 883 } else 884 { 885 // TODO: allow to use replace with a boolean 886 } 887 continue; 888 } 889 890 /* 891 handle user account disabled state in the user's entry 892 */ 893 if (ATTR_ACCOUNT_DISABLED.equalsIgnoreCase(attributeName)) 894 { 895 mods.remove(mod); 896 String[] values = mod.getValues(); 897 if (isDelete(mod)) 898 { 899 ops.add(PasswordPolicyStateOperation.createClearAccountDisabledStateOperation()); 900 } else 901 { 902 if (values.length != 1) 903 { 904 result.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 905 String message = "modification for " + ATTR_ACCOUNT_DISABLED 906 + " must have a single boolean value. Ignoring."; 907 result.setDiagnosticMessage(message); 908 serverContext.logMessage(LogSeverity.MILD_ERROR, message); 909 continue; 910 } 911 912 ops.add(PasswordPolicyStateOperation.createSetAccountDisabledStateOperation 913 (parseBooleanPermissive(values[0]))); 914 915 } 916 continue; 917 } 918 919 /* 920 handle the account expiration time in the user's entry 921 */ 922 if (ATTR_ACCOUNT_EXPIRATION_TIME.equalsIgnoreCase(attributeName)) 923 { 924 mods.remove(mod); 925 if (isDelete(mod)) 926 { 927 ops.add(PasswordPolicyStateOperation.createClearAccountExpirationTimeOperation()); 928 } else 929 { 930 // TODO: this seems to be incomplete to function properly? We should parse the values to dates 931 ops.add(new PasswordPolicyStateOperation(OP_TYPE_SET_ACCOUNT_EXPIRATION_TIME, mod.getRawValues())); 932 } 933 continue; 934 } 935 936 937 /* 938 clear the last login IP address from the user's entry 939 */ 940 if (ATTR_LAST_LOGIN_IP_ADDRESS.equalsIgnoreCase(attributeName)) 941 { 942 mods.remove(mod); 943 if (isDelete(mod)) 944 { 945 ops.add(PasswordPolicyStateOperation.createClearLastLoginIPAddressOperation()); 946 } else 947 { 948 // TODO: throw an exception if multiple values are provided 949 ops.add(PasswordPolicyStateOperation.createSetLastLoginIPAddressOperation(mod.getValues()[0])); 950 } 951 continue; 952 } 953 954 /* 955 clear the last login time from the user's entry 956 */ 957 if (ATTR_LAST_LOGIN_TIME.equalsIgnoreCase(attributeName)) 958 { 959 mods.remove(mod); 960 if (isDelete(mod)) 961 { 962 ops.add(PasswordPolicyStateOperation.createClearLastLoginTimeOperation()); 963 } else 964 { 965 //TODO: throw an exception if multiple values are provided 966 Date d = null; 967 if (isNow(mod.getValues()[0])) 968 { 969 d = new Date(); 970 } else 971 { 972 try 973 { 974 d = dateFormatter.parse(mod.getValues()[0]); 975 } catch (ParseException e) 976 { 977 e.printStackTrace(); 978 } 979 } 980 ops.add(PasswordPolicyStateOperation.createSetLastLoginTimeOperation(d)); 981 } 982 continue; 983 } 984 985 /* 986 clear the password changed time in the user's account 987 */ 988 if (ATTR_PW_CHANGED_TIME.equalsIgnoreCase(attributeName)) 989 { 990 mods.remove(mod); 991 if (isDelete(mod)) 992 { 993 ops.add(new PasswordPolicyStateOperation(OP_TYPE_CLEAR_PW_CHANGED_TIME)); 994 } else 995 { 996 ops.add(new PasswordPolicyStateOperation(OP_TYPE_SET_PW_CHANGED_TIME, mod.getRawValues())); 997 } 998 continue; 999 } 1000 1001 /* 1002 clear the last required password change time from the user's entry 1003 */ 1004 /* 1005 if (attributeMatches(attributeName, )) 1006 { 1007 ops.add(PasswordPolicyStateOperation.createClearPasswordChangedByRequiredTimeOperation()); 1008 mods.remove(mod); 1009 continue; 1010 } 1011 */ 1012 1013 /* 1014 clear the password expiration warned time from the user's entry 1015 */ 1016 /* 1017 if (attributeMatches(attributeName, ATTR_CLEAR_PW_EXPIRATION_WARNED_TIME)) 1018 { 1019 ops.add(PasswordPolicyStateOperation.createClearPasswordExpirationWarnedTimeOperation()); 1020 mods.remove(mod); 1021 continue; 1022 } 1023 */ 1024 1025 /* 1026 clear the password history values stored in the user's entry 1027 */ 1028 if (ATTR_PW_HISTORY.equalsIgnoreCase(attributeName)) 1029 { 1030 mods.remove(mod); 1031 if (isDelete(mod)) 1032 { 1033 ops.add(PasswordPolicyStateOperation.createClearPasswordHistoryOperation()); 1034 } else 1035 { 1036 serverContext.logMessage(LogSeverity.MILD_WARNING, "unsupported operation type on " + 1037 ATTR_PW_HISTORY); 1038 } 1039 continue; 1040 } 1041 1042 /* 1043 clear the password reset state information in the user's entry 1044 */ 1045 if (ATTR_PW_RESET.equalsIgnoreCase(attributeName)) 1046 { 1047 mods.remove(mod); 1048 if (isDelete(mod)) 1049 { 1050 ops.add(PasswordPolicyStateOperation.createClearPasswordResetStateOperation()); 1051 } else 1052 { 1053 ops.add(PasswordPolicyStateOperation.createSetPasswordResetStateOperation(parseBooleanPermissive 1054 (mod.getValues()[0]))); 1055 } 1056 continue; 1057 } 1058 1059 /* 1060 purge any retired password from the user's entry 1061 */ 1062 if (ATTR_HAS_RETIRED_PASSWORD.equalsIgnoreCase(attributeName)) 1063 { 1064 mods.remove(mod); 1065 if (isDelete(mod)) 1066 { 1067 ops.add(PasswordPolicyStateOperation.createPurgeRetiredPasswordOperation()); 1068 } else 1069 { 1070 serverContext.logMessage(LogSeverity.MILD_WARNING, "unsupported operation type for " + 1071 ATTR_HAS_RETIRED_PASSWORD); 1072 } 1073 } 1074 } 1075 1076 if (!ops.isEmpty()) 1077 { 1078 try 1079 { 1080 1081 PasswordPolicyStateOperation[] operations = new PasswordPolicyStateOperation[ops.size()]; 1082 operations = ops.toArray(operations); 1083 PasswordPolicyStateExtendedRequest passwordPolicyStateExtendedRequest = new 1084 PasswordPolicyStateExtendedRequest(request.getDN(), operations); 1085 ExtendedResult extResult = operationContext.getInternalUserConnection().processExtendedOperation 1086 (passwordPolicyStateExtendedRequest); 1087 PasswordPolicyStateExtendedResult pwpResult = new PasswordPolicyStateExtendedResult(extResult); 1088 if (pwpResult.getResultCode() != ResultCode.SUCCESS) 1089 { 1090 1091 /* 1092 if there is an error during the processing of the extended operation we interrupt the flow 1093 to return the error to the client right away 1094 */ 1095 result.setResultCode(pwpResult.getResultCode()); 1096 result.setDiagnosticMessage(pwpResult.getDiagnosticMessage()); 1097 result.setAdditionalLogMessage("User state update error"); 1098 pluginResult = new PreParsePluginResult(false, false, true, true); 1099 } 1100 } catch (LDAPException e) 1101 { 1102 operationContext.getServerContext().debugCaught(e); 1103 } 1104 } 1105 1106 if (mods.isEmpty()) 1107 { 1108 /* 1109 if mods is empty, there are no remaining modifications to process for the core, we can return the 1110 result of the extended operation as is right away 1111 */ 1112 result.setResultCode(ResultCode.SUCCESS); 1113 pluginResult = new PreParsePluginResult(false, false, true, true); 1114 } else 1115 { 1116 /* 1117 if on the other hand there are some modifications that the core should process then we update then 1118 we update the original incoming request with that and let it do its job 1119 */ 1120 request.setModifications(mods); 1121 } 1122 return pluginResult; 1123 } 1124 1125 @Override 1126 public PreParsePluginResult doPreParse(ActiveOperationContext operationContext, UpdatableAddRequest request, 1127 UpdatableAddResult result) 1128 { 1129 1130 List<PasswordPolicyStateOperation> ops = new ArrayList<>(); 1131 // strip out the attribute from the incoming entry 1132 List<Attribute> attribute = request.getEntry().getAttribute(ATTR_PW_RESET); 1133 if (attribute != null) 1134 { 1135 ops.add(PasswordPolicyStateOperation.createSetPasswordResetStateOperation(parseBooleanPermissive 1136 (attribute.get(0).getValue()))); 1137 request.getEntry().removeAttribute(ATTR_PW_RESET); 1138 } 1139 operationContext.setAttachment(ATTR_EXTENSION, ops); 1140 1141 return PreParsePluginResult.SUCCESS; 1142 } 1143 1144 @Override 1145 public PostOperationPluginResult doPostOperation(ActiveOperationContext operationContext, AddRequest request, 1146 UpdatableAddResult result) 1147 { 1148 if (ResultCode.SUCCESS.equals(result.getResultCode())) 1149 { 1150 List<PasswordPolicyStateOperation> ops = (List<PasswordPolicyStateOperation>) operationContext 1151 .getAttachment(ATTR_EXTENSION); 1152 if (ops != null && ops.size() > 0) 1153 { 1154 PasswordPolicyStateOperation[] operations = new PasswordPolicyStateOperation[ops.size()]; 1155 operations = ops.toArray(operations); 1156 PasswordPolicyStateExtendedRequest passwordPolicyStateExtendedRequest = new 1157 PasswordPolicyStateExtendedRequest(request.getEntry().getDN(), operations); 1158 ExtendedResult extResult = null; 1159 try 1160 { 1161 extResult = operationContext.getInternalUserConnection().processExtendedOperation 1162 (passwordPolicyStateExtendedRequest); 1163 PasswordPolicyStateExtendedResult pwpResult = new PasswordPolicyStateExtendedResult(extResult); 1164 if (pwpResult.getResultCode() != ResultCode.SUCCESS) 1165 { 1166 result.setAdditionalLogMessage(pwpResult.getExtendedResultName()); 1167 } 1168 } catch (LDAPException e) 1169 { 1170 result.setResultCode(extResult.getResultCode()); 1171 result.setAdditionalLogMessage("The initial add of entry " + request.getEntry().getDN() + " " + 1172 "succeeded but the subsequent user state operation failed with the following error: " + 1173 extResult.getExtendedResultName() + " " + extResult.getDiagnosticMessage()); 1174 return new PostOperationPluginResult(false, false); 1175 } 1176 } 1177 } 1178 return PostOperationPluginResult.SUCCESS; 1179 } 1180 1181 /** 1182 * Performs all necessary processing to check if a search request matches the criteria for execution of this 1183 * instance of the plugin 1184 * 1185 * @param attributes a list of attribute names to evaluate 1186 * @return true if the search request satisfies the criteria for this instance of the plugin to attempt 1187 * processing 1188 */ 1189 1190 private boolean searchRequestMatch(List<String> attributes) 1191 { 1192 if (attributes != null) 1193 { 1194 String prefix = ATTR_PREFIX.toLowerCase(); 1195 1196 for (String s : attributes) 1197 { 1198 if (s == null || s.isEmpty()) continue; 1199 1200 if (s.equalsIgnoreCase(ATTR_GET_ALL) || s.startsWith(prefix)) 1201 { 1202 return true; 1203 } 1204 } 1205 } 1206 return false; 1207 } 1208 1209 /** 1210 * Performs all processing to check if a modify request matches the criteria for execution of this instance of the 1211 * plugin 1212 * 1213 * @param modifyRequest to modify request to evaluate 1214 * @return true if the modify request satisfies the criteria for this instance of the plugin to 1215 * attempt processing 1216 */ 1217 private boolean modifyRequestMatch(final UpdatableModifyRequest modifyRequest) 1218 { 1219 if (modifyRequest != null) 1220 { 1221 for (Modification mod : modifyRequest.getModifications()) 1222 { 1223 if (mod == null) continue; 1224 String name = mod.getAttributeName(); 1225 if (name == null || name.isEmpty()) continue; 1226 1227 if (name.equalsIgnoreCase(ATTR_GET_ALL) || name.toLowerCase().startsWith(ATTR_PREFIX.toLowerCase())) 1228 { 1229 return true; 1230 } 1231 } 1232 } 1233 return false; 1234 } 1235 1236 private boolean parseBooleanPermissive(String value) 1237 { 1238 if (value == null | value.isEmpty()) 1239 { 1240 return false; 1241 } 1242 1243 String v = value.trim(); 1244 1245 boolean result = 1246 "yes".equalsIgnoreCase(v) 1247 || "y".equalsIgnoreCase(v) 1248 || "true".equalsIgnoreCase(v) 1249 || "1".equalsIgnoreCase(v); 1250 return result; 1251 1252 } 1253 1254 private boolean isNow(String value) 1255 { 1256 return value == null || value.isEmpty() || NOW.equalsIgnoreCase(value); 1257 } 1258 1259 private boolean isDelete(Modification mod) 1260 { 1261 // That's the obvious case ... it is explicitly a delete 1262 if (mod.getModificationType().equals(ModificationType.DELETE)) 1263 { 1264 return true; 1265 } 1266 1267 // Less obvious, a replace with no value is a delete 1268 if (mod.getModificationType().equals(ModificationType.REPLACE)) 1269 { 1270 String[] values = mod.getValues(); 1271 if (values == null || values.length == 0) 1272 { 1273 return true; 1274 } 1275 1276 return values[0] == null || values[0].isEmpty(); 1277 } 1278 return false; 1279 } 1280 1281 static class ARG 1282 { 1283 static final Character NO_SHORTCUT = null; 1284 static final Integer UNIQUE = 1; 1285 static final Boolean OPTIONAL = Boolean.FALSE; 1286 } 1287}