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