001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2008 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS 026 */ 027package org.opends.server.core; 028 029import static org.opends.messages.ConfigMessages.*; 030import static org.opends.server.util.StaticUtils.*; 031 032import java.util.ArrayList; 033import java.util.List; 034import java.util.concurrent.ConcurrentHashMap; 035 036import org.forgerock.i18n.LocalizableMessage; 037import org.forgerock.i18n.slf4j.LocalizedLogger; 038import org.forgerock.opendj.config.server.ConfigException; 039import org.forgerock.opendj.ldap.ResultCode; 040import org.forgerock.util.Utils; 041import org.opends.server.admin.ClassPropertyDefinition; 042import org.opends.server.admin.server.ConfigurationAddListener; 043import org.opends.server.admin.server.ConfigurationChangeListener; 044import org.opends.server.admin.server.ConfigurationDeleteListener; 045import org.opends.server.admin.server.ServerManagementContext; 046import org.opends.server.admin.std.meta.PasswordValidatorCfgDefn; 047import org.opends.server.admin.std.server.PasswordValidatorCfg; 048import org.opends.server.admin.std.server.RootCfg; 049import org.opends.server.api.PasswordValidator; 050import org.forgerock.opendj.config.server.ConfigChangeResult; 051import org.opends.server.types.DN; 052import org.opends.server.types.InitializationException; 053 054/** 055 * This class defines a utility that will be used to manage the set of 056 * password validators defined in the Directory Server. It will initialize the 057 * validators when the server starts, and then will manage any additions, 058 * removals, or modifications to any password validators while the server is 059 * running. 060 */ 061public class PasswordValidatorConfigManager 062 implements ConfigurationChangeListener<PasswordValidatorCfg>, 063 ConfigurationAddListener<PasswordValidatorCfg>, 064 ConfigurationDeleteListener<PasswordValidatorCfg> 065 066{ 067 068 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 069 070 /** 071 * A mapping between the DNs of the config entries and the associated password 072 * validators. 073 */ 074 private final ConcurrentHashMap<DN,PasswordValidator> passwordValidators; 075 076 private final ServerContext serverContext; 077 078 /** 079 * Creates a new instance of this password validator config manager. 080 * 081 * @param serverContext 082 * The server context. 083 */ 084 public PasswordValidatorConfigManager(ServerContext serverContext) 085 { 086 this.serverContext = serverContext; 087 passwordValidators = new ConcurrentHashMap<>(); 088 } 089 090 /** 091 * Initializes all password validators currently defined in the Directory 092 * Server configuration. This should only be called at Directory Server 093 * startup. 094 * 095 * @throws ConfigException If a configuration problem causes the password 096 * validator initialization process to fail. 097 * 098 * @throws InitializationException If a problem occurs while initializing 099 * the password validators that is not 100 * related to the server configuration. 101 */ 102 public void initializePasswordValidators() 103 throws ConfigException, InitializationException 104 { 105 // Get the root configuration object. 106 ServerManagementContext managementContext = 107 ServerManagementContext.getInstance(); 108 RootCfg rootConfiguration = 109 managementContext.getRootConfiguration(); 110 111 112 // Register as an add and delete listener with the root configuration so we 113 // can be notified if any password validator entries are added or removed. 114 rootConfiguration.addPasswordValidatorAddListener(this); 115 rootConfiguration.addPasswordValidatorDeleteListener(this); 116 117 118 //Initialize the existing password validators. 119 for (String validatorName : rootConfiguration.listPasswordValidators()) 120 { 121 PasswordValidatorCfg validatorConfiguration = 122 rootConfiguration.getPasswordValidator(validatorName); 123 validatorConfiguration.addChangeListener(this); 124 125 if (validatorConfiguration.isEnabled()) 126 { 127 String className = validatorConfiguration.getJavaClass(); 128 try 129 { 130 PasswordValidator<? extends PasswordValidatorCfg> 131 validator = loadValidator(className, validatorConfiguration, 132 true); 133 passwordValidators.put(validatorConfiguration.dn(), validator); 134 DirectoryServer.registerPasswordValidator(validatorConfiguration.dn(), 135 validator); 136 } 137 catch (InitializationException ie) 138 { 139 logger.error(ie.getMessageObject()); 140 continue; 141 } 142 } 143 } 144 } 145 146 147 148 /** {@inheritDoc} */ 149 @Override 150 public boolean isConfigurationAddAcceptable( 151 PasswordValidatorCfg configuration, 152 List<LocalizableMessage> unacceptableReasons) 153 { 154 if (configuration.isEnabled()) 155 { 156 // Get the name of the class and make sure we can instantiate it as a 157 // password validator. 158 String className = configuration.getJavaClass(); 159 try 160 { 161 loadValidator(className, configuration, false); 162 } 163 catch (InitializationException ie) 164 { 165 unacceptableReasons.add(ie.getMessageObject()); 166 return false; 167 } 168 } 169 170 // If we've gotten here, then it's fine. 171 return true; 172 } 173 174 175 176 /** {@inheritDoc} */ 177 @Override 178 public ConfigChangeResult applyConfigurationAdd( 179 PasswordValidatorCfg configuration) 180 { 181 final ConfigChangeResult ccr = new ConfigChangeResult(); 182 183 configuration.addChangeListener(this); 184 185 if (! configuration.isEnabled()) 186 { 187 return ccr; 188 } 189 190 PasswordValidator<? extends PasswordValidatorCfg> 191 passwordValidator = null; 192 193 // Get the name of the class and make sure we can instantiate it as a 194 // password validator. 195 String className = configuration.getJavaClass(); 196 try 197 { 198 passwordValidator = loadValidator(className, configuration, true); 199 } 200 catch (InitializationException ie) 201 { 202 ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode()); 203 ccr.addMessage(ie.getMessageObject()); 204 } 205 206 if (ccr.getResultCode() == ResultCode.SUCCESS) 207 { 208 passwordValidators.put(configuration.dn(), passwordValidator); 209 DirectoryServer.registerPasswordValidator(configuration.dn(), passwordValidator); 210 } 211 212 return ccr; 213 } 214 215 216 217 /** {@inheritDoc} */ 218 @Override 219 public boolean isConfigurationDeleteAcceptable( 220 PasswordValidatorCfg configuration, 221 List<LocalizableMessage> unacceptableReasons) 222 { 223 // FIXME -- We should try to perform some check to determine whether the 224 // password validator is in use. 225 return true; 226 } 227 228 229 230 /** {@inheritDoc} */ 231 @Override 232 public ConfigChangeResult applyConfigurationDelete( 233 PasswordValidatorCfg configuration) 234 { 235 final ConfigChangeResult ccr = new ConfigChangeResult(); 236 237 DirectoryServer.deregisterPasswordValidator(configuration.dn()); 238 239 PasswordValidator passwordValidator = 240 passwordValidators.remove(configuration.dn()); 241 if (passwordValidator != null) 242 { 243 passwordValidator.finalizePasswordValidator(); 244 } 245 246 return ccr; 247 } 248 249 250 251 /** {@inheritDoc} */ 252 @Override 253 public boolean isConfigurationChangeAcceptable( 254 PasswordValidatorCfg configuration, 255 List<LocalizableMessage> unacceptableReasons) 256 { 257 if (configuration.isEnabled()) 258 { 259 // Get the name of the class and make sure we can instantiate it as a 260 // password validator. 261 String className = configuration.getJavaClass(); 262 try 263 { 264 loadValidator(className, configuration, false); 265 } 266 catch (InitializationException ie) 267 { 268 unacceptableReasons.add(ie.getMessageObject()); 269 return false; 270 } 271 } 272 273 // If we've gotten here, then it's fine. 274 return true; 275 } 276 277 278 279 /** {@inheritDoc} */ 280 @Override 281 public ConfigChangeResult applyConfigurationChange( 282 PasswordValidatorCfg configuration) 283 { 284 final ConfigChangeResult ccr = new ConfigChangeResult(); 285 286 287 // Get the existing validator if it's already enabled. 288 PasswordValidator existingValidator = 289 passwordValidators.get(configuration.dn()); 290 291 292 // If the new configuration has the validator disabled, then disable it if 293 // it is enabled, or do nothing if it's already disabled. 294 if (! configuration.isEnabled()) 295 { 296 if (existingValidator != null) 297 { 298 DirectoryServer.deregisterPasswordValidator(configuration.dn()); 299 300 PasswordValidator passwordValidator = 301 passwordValidators.remove(configuration.dn()); 302 if (passwordValidator != null) 303 { 304 passwordValidator.finalizePasswordValidator(); 305 } 306 } 307 308 return ccr; 309 } 310 311 312 // Get the class for the password validator. If the validator is already 313 // enabled, then we shouldn't do anything with it although if the class has 314 // changed then we'll at least need to indicate that administrative action 315 // is required. If the validator is disabled, then instantiate the class 316 // and initialize and register it as a password validator. 317 String className = configuration.getJavaClass(); 318 if (existingValidator != null) 319 { 320 if (! className.equals(existingValidator.getClass().getName())) 321 { 322 ccr.setAdminActionRequired(true); 323 } 324 325 return ccr; 326 } 327 328 PasswordValidator<? extends PasswordValidatorCfg> 329 passwordValidator = null; 330 try 331 { 332 passwordValidator = loadValidator(className, configuration, true); 333 } 334 catch (InitializationException ie) 335 { 336 ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode()); 337 ccr.addMessage(ie.getMessageObject()); 338 } 339 340 if (ccr.getResultCode() == ResultCode.SUCCESS) 341 { 342 passwordValidators.put(configuration.dn(), passwordValidator); 343 DirectoryServer.registerPasswordValidator(configuration.dn(), passwordValidator); 344 } 345 346 return ccr; 347 } 348 349 350 351 /** 352 * Loads the specified class, instantiates it as a password validator, and 353 * optionally initializes that instance. 354 * 355 * @param className The fully-qualified name of the password validator 356 * class to load, instantiate, and initialize. 357 * @param configuration The configuration to use to initialize the 358 * password validator. It must not be {@code null}. 359 * @param initialize Indicates whether the password validator instance 360 * should be initialized. 361 * 362 * @return The possibly initialized password validator. 363 * 364 * @throws InitializationException If a problem occurred while attempting to 365 * initialize the password validator. 366 */ 367 private <T extends PasswordValidatorCfg> PasswordValidator<T> 368 loadValidator(String className, 369 T configuration, 370 boolean initialize) 371 throws InitializationException 372 { 373 try 374 { 375 PasswordValidatorCfgDefn definition = 376 PasswordValidatorCfgDefn.getInstance(); 377 ClassPropertyDefinition propertyDefinition = 378 definition.getJavaClassPropertyDefinition(); 379 Class<? extends PasswordValidator> validatorClass = 380 propertyDefinition.loadClass(className, PasswordValidator.class); 381 PasswordValidator<T> validator = validatorClass.newInstance(); 382 383 if (initialize) 384 { 385 validator.initializePasswordValidator(configuration); 386 } 387 else 388 { 389 List<LocalizableMessage> unacceptableReasons = new ArrayList<>(); 390 if (!validator.isConfigurationAcceptable(configuration, unacceptableReasons)) 391 { 392 String reasons = Utils.joinAsString(". ", unacceptableReasons); 393 throw new InitializationException( 394 ERR_CONFIG_PWVALIDATOR_CONFIG_NOT_ACCEPTABLE.get(configuration.dn(), reasons)); 395 } 396 } 397 398 return validator; 399 } 400 catch (Exception e) 401 { 402 LocalizableMessage message = ERR_CONFIG_PWVALIDATOR_INITIALIZATION_FAILED. 403 get(className, configuration.dn(), stackTraceToSingleLineString(e)); 404 throw new InitializationException(message, e); 405 } 406 } 407} 408