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