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 legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027package org.opends.server.core;
028
029import static org.opends.messages.ConfigMessages.*;
030import static org.opends.server.util.StaticUtils.*;
031
032import java.util.ArrayList;
033import java.util.List;
034import java.util.concurrent.ConcurrentHashMap;
035
036import org.forgerock.i18n.LocalizableMessage;
037import org.forgerock.i18n.slf4j.LocalizedLogger;
038import org.forgerock.opendj.config.server.ConfigException;
039import org.forgerock.opendj.ldap.ResultCode;
040import org.forgerock.util.Utils;
041import org.opends.server.admin.ClassPropertyDefinition;
042import org.opends.server.admin.server.ConfigurationAddListener;
043import org.opends.server.admin.server.ConfigurationChangeListener;
044import org.opends.server.admin.server.ConfigurationDeleteListener;
045import org.opends.server.admin.server.ServerManagementContext;
046import org.opends.server.admin.std.meta.PasswordGeneratorCfgDefn;
047import org.opends.server.admin.std.server.PasswordGeneratorCfg;
048import org.opends.server.admin.std.server.RootCfg;
049import org.opends.server.api.PasswordGenerator;
050import org.forgerock.opendj.config.server.ConfigChangeResult;
051import org.opends.server.types.DN;
052import org.opends.server.types.InitializationException;
053
054/**
055 * This class defines a utility that will be used to manage the set of password
056 * generators defined in the Directory Server.  It will initialize the
057 * generators when the server starts, and then will manage any additions,
058 * removals, or modifications to any password generators while the server is
059 * running.
060 */
061public class PasswordGeneratorConfigManager
062       implements ConfigurationAddListener<PasswordGeneratorCfg>,
063       ConfigurationDeleteListener<PasswordGeneratorCfg>,
064       ConfigurationChangeListener<PasswordGeneratorCfg>
065{
066
067  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
068
069  /**
070   * A mapping between the DNs of the config entries and the associated password
071   * generators.
072   */
073  private final ConcurrentHashMap<DN,PasswordGenerator> passwordGenerators;
074
075  private final ServerContext serverContext;
076
077  /**
078   * Creates a new instance of this password generator config manager.
079   *
080   * @param serverContext
081   *            The server context.
082   */
083  public PasswordGeneratorConfigManager(ServerContext serverContext)
084  {
085    this.serverContext = serverContext;
086    passwordGenerators = new ConcurrentHashMap<>();
087  }
088
089  /**
090   * Initializes all password generators 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   *                           generator initialization process to fail.
096   *
097   * @throws  InitializationException  If a problem occurs while initializing
098   *                                   the password generators that is not
099   *                                   related to the server configuration.
100   */
101  public void initializePasswordGenerators()
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    // Register as an add and delete listener with the root configuration so we
111    // can be notified if any password generator entries are added or removed.
112    rootConfiguration.addPasswordGeneratorAddListener(this);
113    rootConfiguration.addPasswordGeneratorDeleteListener(this);
114
115
116    //Initialize the existing password generators.
117    for (String generatorName : rootConfiguration.listPasswordGenerators())
118    {
119      PasswordGeneratorCfg generatorConfiguration =
120           rootConfiguration.getPasswordGenerator(generatorName);
121      generatorConfiguration.addChangeListener(this);
122
123      if (generatorConfiguration.isEnabled())
124      {
125        String className = generatorConfiguration.getJavaClass();
126        try
127        {
128          PasswordGenerator<? extends PasswordGeneratorCfg>
129               generator = loadGenerator(className, generatorConfiguration,
130                                         true);
131          passwordGenerators.put(generatorConfiguration.dn(), generator);
132          DirectoryServer.registerPasswordGenerator(generatorConfiguration.dn(),
133              generator);
134        }
135        catch (InitializationException ie)
136        {
137          logger.error(ie.getMessageObject());
138          continue;
139        }
140      }
141    }
142  }
143
144  /** {@inheritDoc} */
145  @Override
146  public boolean isConfigurationChangeAcceptable(
147                      PasswordGeneratorCfg configuration,
148                      List<LocalizableMessage> unacceptableReasons)
149  {
150    if (configuration.isEnabled())
151    {
152      // Get the name of the class and make sure we can instantiate it as a
153      // password generator.
154      String className = configuration.getJavaClass();
155      try
156      {
157        loadGenerator(className, configuration, false);
158      }
159      catch (InitializationException ie)
160      {
161        unacceptableReasons.add(ie.getMessageObject());
162        return false;
163      }
164    }
165
166    // If we've gotten here, then it's fine.
167    return true;
168  }
169
170
171  /** {@inheritDoc} */
172  @Override
173  public ConfigChangeResult applyConfigurationChange(
174                                 PasswordGeneratorCfg configuration)
175  {
176    final ConfigChangeResult ccr = new ConfigChangeResult();
177
178
179    // Get the existing generator if it's already enabled.
180    PasswordGenerator existingGenerator =
181         passwordGenerators.get(configuration.dn());
182
183
184    // If the new configuration has the generator disabled, then disable it if
185    // it is enabled, or do nothing if it's already disabled.
186    if (! configuration.isEnabled())
187    {
188      if (existingGenerator != null)
189      {
190        DirectoryServer.deregisterPasswordGenerator(configuration.dn());
191
192        PasswordGenerator passwordGenerator =
193             passwordGenerators.remove(configuration.dn());
194        if (passwordGenerator != null)
195        {
196          passwordGenerator.finalizePasswordGenerator();
197        }
198      }
199
200      return ccr;
201    }
202
203
204    // Get the class for the password generator.  If the generator is already
205    // enabled, then we shouldn't do anything with it although if the class has
206    // changed then we'll at least need to indicate that administrative action
207    // is required.  If the generator is disabled, then instantiate the class
208    // and initialize and register it as a password generator.
209    String className = configuration.getJavaClass();
210    if (existingGenerator != null)
211    {
212      if (! className.equals(existingGenerator.getClass().getName()))
213      {
214        ccr.setAdminActionRequired(true);
215      }
216
217      return ccr;
218    }
219
220    PasswordGenerator<? extends PasswordGeneratorCfg>
221         passwordGenerator = null;
222    try
223    {
224      passwordGenerator = loadGenerator(className, configuration, true);
225    }
226    catch (InitializationException ie)
227    {
228      ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode());
229      ccr.addMessage(ie.getMessageObject());
230    }
231
232    if (ccr.getResultCode() == ResultCode.SUCCESS)
233    {
234      passwordGenerators.put(configuration.dn(), passwordGenerator);
235      DirectoryServer.registerPasswordGenerator(configuration.dn(),
236                                                passwordGenerator);
237    }
238
239    return ccr;
240  }
241  /** {@inheritDoc} */
242  @Override
243  public boolean isConfigurationAddAcceptable(
244                      PasswordGeneratorCfg configuration,
245                      List<LocalizableMessage> unacceptableReasons)
246  {
247    if (configuration.isEnabled())
248    {
249      // Get the name of the class and make sure we can instantiate it as a
250      // password generator.
251      String className = configuration.getJavaClass();
252      try
253      {
254        loadGenerator(className, configuration, false);
255      }
256      catch (InitializationException ie)
257      {
258        unacceptableReasons.add(ie.getMessageObject());
259        return false;
260      }
261    }
262
263    // If we've gotten here, then it's fine.
264    return true;
265  }
266
267
268  /** {@inheritDoc} */
269  @Override
270  public ConfigChangeResult applyConfigurationAdd(
271                                 PasswordGeneratorCfg configuration)
272  {
273    final ConfigChangeResult ccr = new ConfigChangeResult();
274
275    configuration.addChangeListener(this);
276
277    if (! configuration.isEnabled())
278    {
279      return ccr;
280    }
281
282    PasswordGenerator<? extends PasswordGeneratorCfg>
283         passwordGenerator = null;
284
285    // Get the name of the class and make sure we can instantiate it as a
286    // password generator.
287    String className = configuration.getJavaClass();
288    try
289    {
290      passwordGenerator = loadGenerator(className, configuration, true);
291    }
292    catch (InitializationException ie)
293    {
294      ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode());
295      ccr.addMessage(ie.getMessageObject());
296    }
297
298    if (ccr.getResultCode() == ResultCode.SUCCESS)
299    {
300      passwordGenerators.put(configuration.dn(), passwordGenerator);
301      DirectoryServer.registerPasswordGenerator(configuration.dn(),
302                                                passwordGenerator);
303    }
304
305    return ccr;
306  }
307
308  /** {@inheritDoc} */
309  @Override
310  public boolean isConfigurationDeleteAcceptable(
311      PasswordGeneratorCfg configuration, List<LocalizableMessage> unacceptableReasons)
312  {
313    // A delete should always be acceptable, so just return true.
314    return true;
315  }
316
317
318  /** {@inheritDoc} */
319  @Override
320  public ConfigChangeResult applyConfigurationDelete(
321      PasswordGeneratorCfg configuration)
322  {
323    final ConfigChangeResult ccr = new ConfigChangeResult();
324
325    // See if the entry is registered as a password generator.
326    // If so, deregister it and stop the generator.
327    PasswordGenerator generator = passwordGenerators.remove(configuration.dn());
328    if (generator != null)
329    {
330      DirectoryServer.deregisterPasswordGenerator(configuration.dn());
331
332      generator.finalizePasswordGenerator();
333    }
334
335    return ccr;
336  }
337
338  /**
339   * Loads the specified class, instantiates it as a password generator, and
340   * optionally initializes that instance.
341   *
342   * @param  className      The fully-qualified name of the password generator
343   *                        class to load, instantiate, and initialize.
344   * @param  configuration  The configuration to use to initialize the
345   *                        password generator, or {@code null} if the
346   *                        password generator should not be initialized.
347   * @param  initialize     Indicates whether the password generator instance
348   *                        should be initialized.
349   *
350   * @return  The possibly initialized password generator.
351   *
352   * @throws  InitializationException  If a problem occurred while attempting to
353   *                                   initialize the password generator.
354   */
355  private <T extends PasswordGeneratorCfg> PasswordGenerator<T>
356               loadGenerator(String className,
357                             T configuration,
358                             boolean initialize)
359          throws InitializationException
360  {
361    try
362    {
363      PasswordGeneratorCfgDefn definition =
364           PasswordGeneratorCfgDefn.getInstance();
365      ClassPropertyDefinition propertyDefinition =
366           definition.getJavaClassPropertyDefinition();
367      Class<? extends PasswordGenerator> generatorClass =
368           propertyDefinition.loadClass(className, PasswordGenerator.class);
369      PasswordGenerator<T> generator = generatorClass.newInstance();
370
371      if (initialize)
372      {
373        generator.initializePasswordGenerator(configuration);
374      }
375      else
376      {
377        List<LocalizableMessage> unacceptableReasons = new ArrayList<>();
378        if (!generator.isConfigurationAcceptable(configuration, unacceptableReasons))
379        {
380          String reasons = Utils.joinAsString(".  ", unacceptableReasons);
381          throw new InitializationException(
382              ERR_CONFIG_PWGENERATOR_CONFIG_NOT_ACCEPTABLE.get(configuration.dn(), reasons));
383        }
384      }
385
386      return generator;
387    }
388    catch (Exception e)
389    {
390      LocalizableMessage message = ERR_CONFIG_PWGENERATOR_INITIALIZATION_FAILED.
391          get(className, configuration.dn(), stackTraceToSingleLineString(e));
392      throw new InitializationException(message, e);
393    }
394  }
395}
396