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.CertificateMapperCfgDefn;
047import org.opends.server.admin.std.server.CertificateMapperCfg;
048import org.opends.server.admin.std.server.RootCfg;
049import org.opends.server.api.CertificateMapper;
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
056 * certificate mappers defined in the Directory Server.  It will initialize the
057 * certificate mappers when the server starts, and then will manage any
058 * additions, removals, or modifications to any certificate mappers while the
059 * server is running.
060 */
061public class CertificateMapperConfigManager
062       implements ConfigurationChangeListener<CertificateMapperCfg>,
063                  ConfigurationAddListener<CertificateMapperCfg>,
064                  ConfigurationDeleteListener<CertificateMapperCfg>
065
066{
067
068  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
069
070  /**
071   * A mapping between the DNs of the config entries and the associated
072   * certificate mappers.
073   */
074  private ConcurrentHashMap<DN,CertificateMapper> certificateMappers;
075
076  private final ServerContext serverContext;
077
078  /**
079   * Creates a new instance of this certificate mapper config manager.
080   *
081   * @param serverContext
082   *          The server context.
083   */
084  public CertificateMapperConfigManager(ServerContext serverContext)
085  {
086    this.serverContext = serverContext;
087    certificateMappers = new ConcurrentHashMap<>();
088  }
089
090  /**
091   * Initializes all certificate mappers currently defined in the Directory
092   * Server configuration.  This should only be called at Directory Server
093   * startup.
094   *
095   * @throws  ConfigException  If a configuration problem causes the certificate
096   *                           mapper initialization process to fail.
097   *
098   * @throws  InitializationException  If a problem occurs while initializing
099   *                                   the certificate mappers that is not
100   *                                   related to the server configuration.
101   */
102  public void initializeCertificateMappers()
103         throws ConfigException, InitializationException
104  {
105    // Get the root configuration object.
106    ServerManagementContext managementContext =
107         ServerManagementContext.getInstance();
108    RootCfg rootConfiguration =
109         managementContext.getRootConfiguration();
110
111
112    // Register as an add and delete listener with the root configuration so we
113    // can be notified if any certificate mapper entries are added or removed.
114    rootConfiguration.addCertificateMapperAddListener(this);
115    rootConfiguration.addCertificateMapperDeleteListener(this);
116
117
118    //Initialize the existing certificate mappers.
119    for (String mapperName : rootConfiguration.listCertificateMappers())
120    {
121      CertificateMapperCfg mapperConfiguration =
122           rootConfiguration.getCertificateMapper(mapperName);
123      mapperConfiguration.addChangeListener(this);
124
125      if (mapperConfiguration.isEnabled())
126      {
127        String className = mapperConfiguration.getJavaClass();
128        try
129        {
130          CertificateMapper mapper = loadMapper(className, mapperConfiguration,
131                                                true);
132          certificateMappers.put(mapperConfiguration.dn(), mapper);
133          DirectoryServer.registerCertificateMapper(mapperConfiguration.dn(),
134                                                    mapper);
135        }
136        catch (InitializationException ie)
137        {
138          logger.error(ie.getMessageObject());
139          continue;
140        }
141      }
142    }
143  }
144
145  /** {@inheritDoc} */
146  @Override
147  public boolean isConfigurationAddAcceptable(
148                      CertificateMapperCfg configuration,
149                      List<LocalizableMessage> unacceptableReasons)
150  {
151    if (configuration.isEnabled())
152    {
153      // Get the name of the class and make sure we can instantiate it as a
154      // certificate mapper.
155      String className = configuration.getJavaClass();
156      try
157      {
158        loadMapper(className, configuration, false);
159      }
160      catch (InitializationException ie)
161      {
162        unacceptableReasons.add(ie.getMessageObject());
163        return false;
164      }
165    }
166
167    // If we've gotten here, then it's fine.
168    return true;
169  }
170
171  /** {@inheritDoc} */
172  @Override
173  public ConfigChangeResult applyConfigurationAdd(
174                                 CertificateMapperCfg configuration)
175  {
176    final ConfigChangeResult ccr = new ConfigChangeResult();
177
178    configuration.addChangeListener(this);
179
180    if (! configuration.isEnabled())
181    {
182      return ccr;
183    }
184
185    CertificateMapper certificateMapper = null;
186
187    // Get the name of the class and make sure we can instantiate it as a
188    // certificate mapper.
189    String className = configuration.getJavaClass();
190    try
191    {
192      certificateMapper = loadMapper(className, configuration, true);
193    }
194    catch (InitializationException ie)
195    {
196      ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode());
197      ccr.addMessage(ie.getMessageObject());
198    }
199
200    if (ccr.getResultCode() == ResultCode.SUCCESS)
201    {
202      certificateMappers.put(configuration.dn(), certificateMapper);
203      DirectoryServer.registerCertificateMapper(configuration.dn(), certificateMapper);
204    }
205
206    return ccr;
207  }
208
209  /** {@inheritDoc} */
210  @Override
211  public boolean isConfigurationDeleteAcceptable(
212                      CertificateMapperCfg configuration,
213                      List<LocalizableMessage> unacceptableReasons)
214  {
215    // FIXME -- We should try to perform some check to determine whether the
216    // certificate mapper is in use.
217    return true;
218  }
219
220  /** {@inheritDoc} */
221  @Override
222  public ConfigChangeResult applyConfigurationDelete(
223                                 CertificateMapperCfg configuration)
224  {
225    final ConfigChangeResult ccr = new ConfigChangeResult();
226
227    DirectoryServer.deregisterCertificateMapper(configuration.dn());
228
229    CertificateMapper certificateMapper =
230         certificateMappers.remove(configuration.dn());
231    if (certificateMapper != null)
232    {
233      certificateMapper.finalizeCertificateMapper();
234    }
235
236    return ccr;
237  }
238
239  /** {@inheritDoc} */
240  @Override
241  public boolean isConfigurationChangeAcceptable(
242                      CertificateMapperCfg configuration,
243                      List<LocalizableMessage> unacceptableReasons)
244  {
245    if (configuration.isEnabled())
246    {
247      // Get the name of the class and make sure we can instantiate it as a
248      // certificate mapper.
249      String className = configuration.getJavaClass();
250      try
251      {
252        loadMapper(className, configuration, false);
253      }
254      catch (InitializationException ie)
255      {
256        unacceptableReasons.add(ie.getMessageObject());
257        return false;
258      }
259    }
260
261    // If we've gotten here, then it's fine.
262    return true;
263  }
264
265  /** {@inheritDoc} */
266  @Override
267  public ConfigChangeResult applyConfigurationChange(
268                                 CertificateMapperCfg configuration)
269  {
270    final ConfigChangeResult ccr = new ConfigChangeResult();
271
272
273    // Get the existing mapper if it's already enabled.
274    CertificateMapper existingMapper =
275         certificateMappers.get(configuration.dn());
276
277
278    // If the new configuration has the mapper disabled, then disable it if it
279    // is enabled, or do nothing if it's already disabled.
280    if (! configuration.isEnabled())
281    {
282      if (existingMapper != null)
283      {
284        DirectoryServer.deregisterCertificateMapper(configuration.dn());
285
286        CertificateMapper certificateMapper =
287             certificateMappers.remove(configuration.dn());
288        if (certificateMapper != null)
289        {
290          certificateMapper.finalizeCertificateMapper();
291        }
292      }
293
294      return ccr;
295    }
296
297
298    // Get the class for the certificate mapper.  If the mapper is already
299    // enabled, then we shouldn't do anything with it although if the class has
300    // changed then we'll at least need to indicate that administrative action
301    // is required.  If the mapper is disabled, then instantiate the class and
302    // initialize and register it as a certificate mapper.
303    String className = configuration.getJavaClass();
304    if (existingMapper != null)
305    {
306      if (! className.equals(existingMapper.getClass().getName()))
307      {
308        ccr.setAdminActionRequired(true);
309      }
310
311      return ccr;
312    }
313
314    CertificateMapper certificateMapper = null;
315    try
316    {
317      certificateMapper = loadMapper(className, configuration, true);
318    }
319    catch (InitializationException ie)
320    {
321      ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode());
322      ccr.addMessage(ie.getMessageObject());
323    }
324
325    if (ccr.getResultCode() == ResultCode.SUCCESS)
326    {
327      certificateMappers.put(configuration.dn(), certificateMapper);
328      DirectoryServer.registerCertificateMapper(configuration.dn(), certificateMapper);
329    }
330
331    return ccr;
332  }
333
334
335
336  /**
337   * Loads the specified class, instantiates it as a certificate mapper, and
338   * optionally initializes that instance.
339   *
340   * @param  className      The fully-qualified name of the certificate mapper
341   *                        class to load, instantiate, and initialize.
342   * @param  configuration  The configuration to use to initialize the
343   *                        certificate mapper.  It must not be {@code null}.
344   * @param  initialize     Indicates whether the certificate mapper instance
345   *                        should be initialized.
346   *
347   * @return  The possibly initialized certificate mapper.
348   *
349   * @throws  InitializationException  If a problem occurred while attempting to
350   *                                   initialize the certificate mapper.
351   */
352  private CertificateMapper loadMapper(String className,
353                                       CertificateMapperCfg configuration,
354                                       boolean initialize)
355          throws InitializationException
356  {
357    try
358    {
359      CertificateMapperCfgDefn definition =
360           CertificateMapperCfgDefn.getInstance();
361      ClassPropertyDefinition propertyDefinition =
362           definition.getJavaClassPropertyDefinition();
363      Class<? extends CertificateMapper> mapperClass =
364           propertyDefinition.loadClass(className, CertificateMapper.class);
365      CertificateMapper mapper = mapperClass.newInstance();
366
367      if (initialize)
368      {
369        mapper.initializeCertificateMapper(configuration);
370      }
371      else
372      {
373        List<LocalizableMessage> unacceptableReasons = new ArrayList<>();
374        if (!mapper.isConfigurationAcceptable(configuration, unacceptableReasons))
375        {
376          String reasons = Utils.joinAsString(".  ", unacceptableReasons);
377          throw new InitializationException(
378              ERR_CONFIG_CERTMAPPER_CONFIG_NOT_ACCEPTABLE.get(configuration.dn(), reasons));
379        }
380      }
381
382      return mapper;
383    }
384    catch (InitializationException e) {
385      // Avoid re-wrapping the initialization exception.
386      throw e;
387    }
388    catch (Exception e)
389    {
390      LocalizableMessage message = ERR_CONFIG_CERTMAPPER_INITIALIZATION_FAILED.
391          get(className, configuration.dn(), stackTraceToSingleLineString(e));
392      throw new InitializationException(message, e);
393    }
394  }
395}
396