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