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