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