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