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;
028import static org.opends.messages.ConfigMessages.*;
029
030import java.util.HashSet;
031import java.util.List;
032import java.util.Set;
033import java.util.concurrent.ConcurrentHashMap;
034
035import org.forgerock.i18n.LocalizableMessage;
036import org.forgerock.opendj.config.server.ConfigException;
037import org.forgerock.opendj.ldap.ResultCode;
038import org.opends.server.admin.server.ConfigurationAddListener;
039import org.opends.server.admin.server.ConfigurationChangeListener;
040import org.opends.server.admin.server.ConfigurationDeleteListener;
041import org.opends.server.admin.server.ServerManagementContext;
042import org.opends.server.admin.std.server.RootCfg;
043import org.opends.server.admin.std.server.RootDNCfg;
044import org.opends.server.admin.std.server.RootDNUserCfg;
045import org.forgerock.opendj.config.server.ConfigChangeResult;
046import org.opends.server.types.DN;
047import org.opends.server.types.DirectoryException;
048import org.opends.server.types.InitializationException;
049import org.opends.server.types.Privilege;
050
051/**
052 * This class defines a utility that will be used to manage the set of root
053 * users defined in the Directory Server.  It will handle both the
054 * "cn=Root DNs,cn=config" entry itself (through the root privilege change
055 * listener), and all of its children.
056 */
057public class RootDNConfigManager
058       implements ConfigurationChangeListener<RootDNUserCfg>,
059                  ConfigurationAddListener<RootDNUserCfg>,
060                  ConfigurationDeleteListener<RootDNUserCfg>
061
062{
063  /** A mapping between the actual root DNs and their alternate bind DNs. */
064  private ConcurrentHashMap<DN,HashSet<DN>> alternateBindDNs;
065
066  /**
067   * The root privilege change listener that will handle changes to the
068   * "cn=Root DNs,cn=config" entry itself.
069   */
070  private RootPrivilegeChangeListener rootPrivilegeChangeListener;
071
072  private final ServerContext serverContext;
073
074  /**
075   * Creates a new instance of this root DN config manager.
076   *
077   * @param serverContext
078   *          The server context.
079   */
080  public RootDNConfigManager(ServerContext serverContext)
081  {
082    this.serverContext = serverContext;
083    alternateBindDNs = new ConcurrentHashMap<>();
084    rootPrivilegeChangeListener = new RootPrivilegeChangeListener();
085  }
086
087  /**
088   * Initializes all of the root users currently defined in the Directory Server
089   * configuration, as well as the set of privileges that root users will
090   * inherit by default.
091   *
092   * @throws ConfigException
093   *           If a configuration problem causes the identity mapper
094   *           initialization process to fail.
095   * @throws InitializationException
096   *           If a problem occurs while initializing the identity mappers that
097   *           is not related to the server configuration.
098   */
099  public void initializeRootDNs()
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    // Get the root DN configuration object, use it to set the default root
110    // privileges, and register a change listener for it.
111    RootDNCfg rootDNCfg = rootConfiguration.getRootDN();
112    rootPrivilegeChangeListener.setDefaultRootPrivileges(rootDNCfg);
113    rootDNCfg.addChangeListener(rootPrivilegeChangeListener);
114
115
116    // Register as an add and delete listener for new root DN users.
117    rootDNCfg.addRootDNUserAddListener(this);
118    rootDNCfg.addRootDNUserDeleteListener(this);
119
120
121    // Get the set of root users defined below "cn=Root DNs,cn=config".  For
122    // each one, register as a change listener, and get the set of alternate
123    // bind DNs.
124    for (String name : rootDNCfg.listRootDNUsers())
125    {
126      RootDNUserCfg rootUserCfg = rootDNCfg.getRootDNUser(name);
127      rootUserCfg.addChangeListener(this);
128      DirectoryServer.registerRootDN(rootUserCfg.dn());
129
130      HashSet<DN> altBindDNs = new HashSet<>();
131      for (DN alternateBindDN : rootUserCfg.getAlternateBindDN())
132      {
133        try
134        {
135          altBindDNs.add(alternateBindDN);
136          DirectoryServer.registerAlternateRootDN(rootUserCfg.dn(),
137                                                  alternateBindDN);
138        }
139        catch (DirectoryException de)
140        {
141          throw new InitializationException(de.getMessageObject());
142        }
143      }
144
145      alternateBindDNs.put(rootUserCfg.dn(), altBindDNs);
146    }
147  }
148
149
150
151  /**
152   * Retrieves the set of privileges that will be granted to root users by
153   * default.
154   *
155   * @return  The set of privileges that will be granted to root users by
156   *          default.
157   */
158  public Set<Privilege> getRootPrivileges()
159  {
160    return rootPrivilegeChangeListener.getDefaultRootPrivileges();
161  }
162
163
164
165  /** {@inheritDoc} */
166  @Override
167  public boolean isConfigurationAddAcceptable(RootDNUserCfg configuration,
168                                              List<LocalizableMessage> unacceptableReasons)
169  {
170    // The new root user must not have an alternate bind DN that is already
171    // in use.
172    boolean configAcceptable = true;
173    for (DN altBindDN : configuration.getAlternateBindDN())
174    {
175      DN existingRootDN = DirectoryServer.getActualRootBindDN(altBindDN);
176      if (existingRootDN != null)
177      {
178        unacceptableReasons.add(ERR_CONFIG_ROOTDN_CONFLICTING_MAPPING.get(
179            altBindDN, configuration.dn(), existingRootDN));
180        configAcceptable = false;
181      }
182    }
183
184    return configAcceptable;
185  }
186
187
188
189  /** {@inheritDoc} */
190  @Override
191  public ConfigChangeResult applyConfigurationAdd(RootDNUserCfg configuration)
192  {
193    configuration.addChangeListener(this);
194
195    final ConfigChangeResult ccr = new ConfigChangeResult();
196
197    HashSet<DN> altBindDNs = new HashSet<>();
198    for (DN altBindDN : configuration.getAlternateBindDN())
199    {
200      try
201      {
202        DirectoryServer.registerAlternateRootDN(configuration.dn(), altBindDN);
203        altBindDNs.add(altBindDN);
204      }
205      catch (DirectoryException de)
206      {
207        // This shouldn't happen, since the set of DNs should have already been
208        // validated.
209        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
210        ccr.addMessage(de.getMessageObject());
211
212        for (DN dn : altBindDNs)
213        {
214          DirectoryServer.deregisterAlternateRootBindDN(dn);
215        }
216        break;
217      }
218    }
219
220    if (ccr.getResultCode() == ResultCode.SUCCESS)
221    {
222      DirectoryServer.registerRootDN(configuration.dn());
223      alternateBindDNs.put(configuration.dn(), altBindDNs);
224    }
225
226    return ccr;
227  }
228
229
230
231  /** {@inheritDoc} */
232  @Override
233  public boolean isConfigurationDeleteAcceptable(RootDNUserCfg configuration,
234                      List<LocalizableMessage> unacceptableReasons)
235  {
236    return true;
237  }
238
239
240
241  /** {@inheritDoc} */
242  @Override
243  public ConfigChangeResult applyConfigurationDelete(
244                                 RootDNUserCfg configuration)
245  {
246    DirectoryServer.deregisterRootDN(configuration.dn());
247    configuration.removeChangeListener(this);
248
249    final ConfigChangeResult ccr = new ConfigChangeResult();
250
251    HashSet<DN> altBindDNs = alternateBindDNs.remove(configuration.dn());
252    if (altBindDNs != null)
253    {
254      for (DN dn : altBindDNs)
255      {
256        DirectoryServer.deregisterAlternateRootBindDN(dn);
257      }
258    }
259
260    return ccr;
261  }
262
263
264
265  /** {@inheritDoc} */
266  @Override
267  public boolean isConfigurationChangeAcceptable(RootDNUserCfg configuration,
268                      List<LocalizableMessage> unacceptableReasons)
269  {
270    boolean configAcceptable = true;
271
272    // There must not be any new alternate bind DNs that are already in use by
273    // other root users.
274    for (DN altBindDN: configuration.getAlternateBindDN())
275    {
276      DN existingRootDN = DirectoryServer.getActualRootBindDN(altBindDN);
277      if (existingRootDN != null && !existingRootDN.equals(configuration.dn()))
278      {
279        unacceptableReasons.add(ERR_CONFIG_ROOTDN_CONFLICTING_MAPPING.get(
280            altBindDN, configuration.dn(), existingRootDN));
281        configAcceptable = false;
282      }
283    }
284
285    return configAcceptable;
286  }
287
288
289
290  /** {@inheritDoc} */
291  @Override
292  public ConfigChangeResult applyConfigurationChange(
293                                 RootDNUserCfg configuration)
294  {
295    final ConfigChangeResult ccr = new ConfigChangeResult();
296
297    HashSet<DN> setDNs = new HashSet<>();
298    HashSet<DN> addDNs = new HashSet<>();
299    HashSet<DN> delDNs = new HashSet<>(alternateBindDNs.get(configuration.dn()));
300
301    for (DN altBindDN : configuration.getAlternateBindDN())
302    {
303      setDNs.add(altBindDN);
304
305      if (! delDNs.remove(altBindDN))
306      {
307        addDNs.add(altBindDN);
308      }
309    }
310
311    for (DN dn : delDNs)
312    {
313      DirectoryServer.deregisterAlternateRootBindDN(dn);
314    }
315
316    HashSet<DN> addedDNs = new HashSet<>(addDNs.size());
317    for (DN dn : addDNs)
318    {
319      try
320      {
321        DirectoryServer.registerAlternateRootDN(configuration.dn(), dn);
322        addedDNs.add(dn);
323      }
324      catch (DirectoryException de)
325      {
326        // This shouldn't happen, since the set of DNs should have already been
327        // validated.
328        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
329        ccr.addMessage(de.getMessageObject());
330
331        for (DN addedDN : addedDNs)
332        {
333          DirectoryServer.deregisterAlternateRootBindDN(addedDN);
334        }
335
336        for (DN deletedDN : delDNs)
337        {
338          try
339          {
340            DirectoryServer.registerAlternateRootDN(configuration.dn(),
341                                                    deletedDN);
342          }
343          catch (Exception e)
344          {
345            // This should also never happen.
346            alternateBindDNs.get(configuration.dn()).remove(deletedDN);
347          }
348        }
349      }
350    }
351
352    if (ccr.getResultCode() == ResultCode.SUCCESS)
353    {
354      alternateBindDNs.put(configuration.dn(), setDNs);
355    }
356
357    return ccr;
358  }
359}
360