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