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