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 static org.opends.messages.ConfigMessages.*;
033
034 import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
035
036 import java.lang.reflect.Method;
037 import java.util.ArrayList;
038 import java.util.Iterator;
039 import java.util.List;
040 import java.util.concurrent.ConcurrentHashMap;
041
042 import org.opends.server.admin.ClassPropertyDefinition;
043 import org.opends.server.admin.server.ConfigurationAddListener;
044 import org.opends.server.admin.server.ConfigurationChangeListener;
045 import org.opends.server.admin.server.ConfigurationDeleteListener;
046 import org.opends.server.admin.server.ServerManagementContext;
047 import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
048 import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
049 import org.opends.server.admin.std.server.RootCfg;
050 import org.opends.server.api.PasswordStorageScheme;
051 import org.opends.server.config.ConfigException;
052 import org.opends.server.types.ConfigChangeResult;
053 import org.opends.server.types.DN;
054 import org.opends.server.types.InitializationException;
055 import org.opends.server.types.ResultCode;
056
057
058
059 /**
060 * This class defines a utility that will be used to manage the set of password
061 * storage schemes defined in the Directory Server. It will initialize the
062 * storage schemes when the server starts, and then will manage any additions,
063 * removals, or modifications to any schemes while the server is running.
064 */
065 public class PasswordStorageSchemeConfigManager
066 implements
067 ConfigurationChangeListener <PasswordStorageSchemeCfg>,
068 ConfigurationAddListener <PasswordStorageSchemeCfg>,
069 ConfigurationDeleteListener <PasswordStorageSchemeCfg>
070 {
071 // A mapping between the DNs of the config entries and the associated password
072 // storage schemes.
073 private ConcurrentHashMap<DN,PasswordStorageScheme> storageSchemes;
074
075
076 /**
077 * Creates a new instance of this password storage scheme config manager.
078 */
079 public PasswordStorageSchemeConfigManager()
080 {
081 storageSchemes = new ConcurrentHashMap<DN,PasswordStorageScheme>();
082 }
083
084
085
086 /**
087 * Initializes all password storage schemes currently defined in the Directory
088 * Server configuration. This should only be called at Directory Server
089 * startup.
090 *
091 * @throws ConfigException If a configuration problem causes the password
092 * storage scheme initialization process to fail.
093 *
094 * @throws InitializationException If a problem occurs while initializing
095 * the password storage scheme that is not
096 * related to the server configuration.
097 */
098 public void initializePasswordStorageSchemes()
099 throws ConfigException, InitializationException
100 {
101 // Get the root configuration object.
102 ServerManagementContext managementContext =
103 ServerManagementContext.getInstance();
104 RootCfg rootConfiguration =
105 managementContext.getRootConfiguration();
106
107 // Register as an add and delete listener with the root configuration so we
108 // can be notified if any entry cache entry is added or removed.
109 rootConfiguration.addPasswordStorageSchemeAddListener (this);
110 rootConfiguration.addPasswordStorageSchemeDeleteListener (this);
111
112 // Initialize existing password storage schemes.
113 for (String schemeName: rootConfiguration.listPasswordStorageSchemes())
114 {
115 // Get the password storage scheme's configuration.
116 PasswordStorageSchemeCfg config =
117 rootConfiguration.getPasswordStorageScheme (schemeName);
118
119 // Register as a change listener for this password storage scheme
120 // entry so that we will be notified of any changes that may be
121 // made to it.
122 config.addChangeListener (this);
123
124 // Ignore this password storage scheme if it is disabled.
125 if (config.isEnabled())
126 {
127 // Load the password storage scheme implementation class.
128 String className = config.getJavaClass();
129 loadAndInstallPasswordStorageScheme (className, config);
130 }
131 }
132 }
133
134
135
136 /**
137 * {@inheritDoc}
138 */
139 public boolean isConfigurationChangeAcceptable(
140 PasswordStorageSchemeCfg configuration,
141 List<Message> unacceptableReasons
142 )
143 {
144 // returned status -- all is fine by default
145 boolean status = true;
146
147 if (configuration.isEnabled())
148 {
149 // Get the name of the class and make sure we can instantiate it as
150 // a password storage scheme.
151 String className = configuration.getJavaClass();
152 try
153 {
154 // Load the class but don't initialize it.
155 loadPasswordStorageScheme (className, configuration, false);
156 }
157 catch (InitializationException ie)
158 {
159 unacceptableReasons.add(ie.getMessageObject());
160 status = false;
161 }
162 }
163
164 return status;
165 }
166
167
168
169 /**
170 * {@inheritDoc}
171 */
172 public ConfigChangeResult applyConfigurationChange(
173 PasswordStorageSchemeCfg configuration
174 )
175 {
176 // Returned result.
177 ConfigChangeResult changeResult = new ConfigChangeResult(
178 ResultCode.SUCCESS, false, new ArrayList<Message>()
179 );
180
181 // Get the configuration entry DN and the associated
182 // password storage scheme class.
183 DN configEntryDN = configuration.dn();
184 PasswordStorageScheme storageScheme = storageSchemes.get(
185 configEntryDN
186 );
187
188 // If the new configuration has the password storage scheme disabled,
189 // then remove it from the mapping list and clean it.
190 if (! configuration.isEnabled())
191 {
192 if (storageScheme != null)
193 {
194 uninstallPasswordStorageScheme (configEntryDN);
195 }
196
197 return changeResult;
198 }
199
200 // At this point, new configuration is enabled...
201 // If the current password storage scheme is already enabled then we
202 // don't do anything unless the class has changed in which case we
203 // should indicate that administrative action is required.
204 String newClassName = configuration.getJavaClass();
205 if (storageScheme != null)
206 {
207 String curClassName = storageScheme.getClass().getName();
208 boolean classIsNew = (! newClassName.equals (curClassName));
209 if (classIsNew)
210 {
211 changeResult.setAdminActionRequired (true);
212 }
213 return changeResult;
214 }
215
216 // New entry cache is enabled and there were no previous one.
217 // Instantiate the new class and initalize it.
218 try
219 {
220 loadAndInstallPasswordStorageScheme (newClassName, configuration);
221 }
222 catch (InitializationException ie)
223 {
224 changeResult.addMessage (ie.getMessageObject());
225 changeResult.setResultCode (DirectoryServer.getServerErrorResultCode());
226 return changeResult;
227 }
228
229 return changeResult;
230 }
231
232
233
234 /**
235 * {@inheritDoc}
236 */
237 public boolean isConfigurationAddAcceptable(
238 PasswordStorageSchemeCfg configuration,
239 List<Message> unacceptableReasons
240 )
241 {
242 // returned status -- all is fine by default
243 boolean status = true;
244
245 // Make sure that no entry already exists with the specified DN.
246 DN configEntryDN = configuration.dn();
247 if (storageSchemes.containsKey(configEntryDN))
248 {
249 Message message = ERR_CONFIG_PWSCHEME_EXISTS.get(
250 String.valueOf(configEntryDN));
251 unacceptableReasons.add (message);
252 status = false;
253 }
254 // If configuration is enabled then check that password storage scheme
255 // class can be instantiated.
256 else if (configuration.isEnabled())
257 {
258 // Get the name of the class and make sure we can instantiate it as
259 // an entry cache.
260 String className = configuration.getJavaClass();
261 try
262 {
263 // Load the class but don't initialize it.
264 loadPasswordStorageScheme (className, configuration, false);
265 }
266 catch (InitializationException ie)
267 {
268 unacceptableReasons.add (ie.getMessageObject());
269 status = false;
270 }
271 }
272
273 return status;
274 }
275
276
277
278 /**
279 * {@inheritDoc}
280 */
281 public ConfigChangeResult applyConfigurationAdd(
282 PasswordStorageSchemeCfg configuration
283 )
284 {
285 // Returned result.
286 ConfigChangeResult changeResult = new ConfigChangeResult(
287 ResultCode.SUCCESS, false, new ArrayList<Message>()
288 );
289
290 // Register a change listener with it so we can be notified of changes
291 // to it over time.
292 configuration.addChangeListener(this);
293
294 if (configuration.isEnabled())
295 {
296 // Instantiate the class as password storage scheme
297 // and initialize it.
298 String className = configuration.getJavaClass();
299 try
300 {
301 loadAndInstallPasswordStorageScheme (className, configuration);
302 }
303 catch (InitializationException ie)
304 {
305 changeResult.addMessage (ie.getMessageObject());
306 changeResult.setResultCode (DirectoryServer.getServerErrorResultCode());
307 return changeResult;
308 }
309 }
310
311 return changeResult;
312 }
313
314
315
316 /**
317 * {@inheritDoc}
318 */
319 public boolean isConfigurationDeleteAcceptable(
320 PasswordStorageSchemeCfg configuration,
321 List<Message> unacceptableReasons
322 )
323 {
324 // A delete should always be acceptable, so just return true.
325 return true;
326 }
327
328
329
330 /**
331 * {@inheritDoc}
332 */
333 public ConfigChangeResult applyConfigurationDelete(
334 PasswordStorageSchemeCfg configuration
335 )
336 {
337 // Returned result.
338 ConfigChangeResult changeResult = new ConfigChangeResult(
339 ResultCode.SUCCESS, false, new ArrayList<Message>()
340 );
341
342 uninstallPasswordStorageScheme (configuration.dn());
343
344 return changeResult;
345 }
346
347
348
349 /**
350 * Loads the specified class, instantiates it as a password storage scheme,
351 * and optionally initializes that instance. Any initialized password
352 * storage scheme is registered in the server.
353 *
354 * @param className The fully-qualified name of the password storage
355 * scheme class to load, instantiate, and initialize.
356 * @param configuration The configuration to use to initialize the
357 * password storage scheme, or {@code null} if the
358 * password storage scheme should not be initialized.
359 *
360 * @throws InitializationException If a problem occurred while attempting
361 * to initialize the class.
362 */
363 private void loadAndInstallPasswordStorageScheme(
364 String className,
365 PasswordStorageSchemeCfg configuration
366 )
367 throws InitializationException
368 {
369 // Load the password storage scheme class...
370 PasswordStorageScheme
371 <? extends PasswordStorageSchemeCfg> schemeClass;
372 schemeClass = loadPasswordStorageScheme (className, configuration, true);
373
374 // ... and install the password storage scheme in the server.
375 DN configEntryDN = configuration.dn();
376 storageSchemes.put (configEntryDN, schemeClass);
377 DirectoryServer.registerPasswordStorageScheme (configEntryDN, schemeClass);
378 }
379
380
381 /**
382 * Loads the specified class, instantiates it as a password storage scheme,
383 * and optionally initializes that instance.
384 *
385 * @param className The fully-qualified name of the class
386 * to load, instantiate, and initialize.
387 * @param configuration The configuration to use to initialize the
388 * class. It must not be {@code null}.
389 * @param initialize Indicates whether the password storage scheme
390 * instance should be initialized.
391 *
392 * @return The possibly initialized password storage scheme.
393 *
394 * @throws InitializationException If a problem occurred while attempting
395 * to initialize the class.
396 */
397 private PasswordStorageScheme <? extends PasswordStorageSchemeCfg>
398 loadPasswordStorageScheme(
399 String className,
400 PasswordStorageSchemeCfg configuration,
401 boolean initialize)
402 throws InitializationException
403 {
404 try
405 {
406 PasswordStorageSchemeCfgDefn definition;
407 ClassPropertyDefinition propertyDefinition;
408 Class<? extends PasswordStorageScheme> schemeClass;
409 PasswordStorageScheme<? extends PasswordStorageSchemeCfg>
410 passwordStorageScheme;
411
412 definition = PasswordStorageSchemeCfgDefn.getInstance();
413 propertyDefinition = definition.getJavaClassPropertyDefinition();
414 schemeClass = propertyDefinition.loadClass(
415 className,
416 PasswordStorageScheme.class
417 );
418 passwordStorageScheme =
419 (PasswordStorageScheme<? extends PasswordStorageSchemeCfg>)
420 schemeClass.newInstance();
421
422 if (initialize)
423 {
424 Method method = passwordStorageScheme.getClass().getMethod(
425 "initializePasswordStorageScheme",
426 configuration.configurationClass());
427 method.invoke(passwordStorageScheme, configuration);
428 }
429 else
430 {
431 Method method = passwordStorageScheme.getClass().getMethod(
432 "isConfigurationAcceptable",
433 PasswordStorageSchemeCfg.class, List.class);
434
435 List<Message> unacceptableReasons = new ArrayList<Message>();
436 Boolean acceptable = (Boolean) method.invoke(passwordStorageScheme,
437 configuration,
438 unacceptableReasons);
439 if (! acceptable)
440 {
441 StringBuilder buffer = new StringBuilder();
442 if (! unacceptableReasons.isEmpty())
443 {
444 Iterator<Message> iterator = unacceptableReasons.iterator();
445 buffer.append(iterator.next());
446 while (iterator.hasNext())
447 {
448 buffer.append(". ");
449 buffer.append(iterator.next());
450 }
451 }
452
453 Message message = ERR_CONFIG_PWSCHEME_CONFIG_NOT_ACCEPTABLE.get(
454 String.valueOf(configuration.dn()), buffer.toString());
455 throw new InitializationException(message);
456 }
457 }
458
459 return passwordStorageScheme;
460 }
461 catch (Exception e)
462 {
463 Message message = ERR_CONFIG_PWSCHEME_INITIALIZATION_FAILED.get(className,
464 String.valueOf(configuration.dn()),
465 stackTraceToSingleLineString(e)
466 );
467 throw new InitializationException(message, e);
468 }
469 }
470
471
472 /**
473 * Remove a password storage that has been installed in the server.
474 *
475 * @param configEntryDN the DN of the configuration enry associated to
476 * the password storage scheme to remove
477 */
478 private void uninstallPasswordStorageScheme(
479 DN configEntryDN
480 )
481 {
482 PasswordStorageScheme scheme =
483 storageSchemes.remove (configEntryDN);
484 if (scheme != null)
485 {
486 DirectoryServer.deregisterPasswordStorageScheme(configEntryDN);
487 scheme.finalizePasswordStorageScheme();
488 }
489 }
490 }
491