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