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