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