mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

dugan
13.57.2007 b52ab703908a5d76a22169f35781b52e90302591
Add support for nested static groups and nesting of a dynamic groups
in a static group. This implementation does not use a different
attribute to define a nested group. Issue 423
4 files modified
837 ■■■■■ changed files
opends/src/server/org/opends/server/core/GroupManager.java 45 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/StaticGroup.java 251 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ExtensionsMessages.java 41 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java 500 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/GroupManager.java
@@ -104,6 +104,10 @@
  private static final DebugTracer TRACER = getTracer();
  //Used by group instances to determine if new groups have been
  //registered or groups deleted.
  private long refreshToken=0;
  // A mapping between the DNs of the config entries and the associated
  // group implementations.
@@ -711,8 +715,11 @@
        }
      }
    }
    createAndRegisterGroup(entry);
    synchronized (groupInstances)
    {
      createAndRegisterGroup(entry);
      refreshToken++;
    }
  }
@@ -735,8 +742,11 @@
        }
      }
    }
    groupInstances.remove(entry.getDN());
    synchronized (groupInstances)
    {
      groupInstances.remove(entry.getDN());
      refreshToken++;
    }
  }
@@ -773,6 +783,7 @@
        }
        createAndRegisterGroup(newEntry);
        refreshToken++;
      }
    }
  }
@@ -807,6 +818,7 @@
      {
        createAndRegisterGroup(newEntry);
        groupInstances.remove(oldEntry.getDN());
        refreshToken++;
      }
    }
  }
@@ -855,5 +867,30 @@
  {
    groupInstances.clear();
  }
  /**
   * Compare the specified token against the current group manager
   * token value. Can be used to reload cached group instances if there has
   * been a group instance change.
   *
   * @param token The current token that the group class holds.
   *
   * @return {@code true} if the group class should reload its nested groups,
   *         or {@code false} if it shouldn't.
   */
  public boolean hasInstancesChanged(long token)  {
    return token != this.refreshToken;
  }
  /**
   * Return the current refresh token value. Can be used to
   * reload cached group instances if there has been a group instance change.
   *
   * @return The current token value.
   */
  public long refreshToken() {
    return this.refreshToken;
  }
}
opends/src/server/org/opends/server/extensions/StaticGroup.java
@@ -28,42 +28,21 @@
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.*;
import org.opends.server.admin.std.server.GroupImplementationCfg;
import org.opends.server.api.Group;
import org.opends.server.core.ModifyOperationBasis;
import org.opends.server.core.DirectoryServer;
import org.opends.server.config.ConfigException;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.Control;
import org.opends.server.types.DirectoryConfig;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.InitializationException;
import org.opends.server.types.MemberList;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.Validator.*;
@@ -94,7 +73,13 @@
  // The set of the DNs of the members for this group.
  private LinkedHashSet<DN> memberDNs;
  //The list of nested group DNs for this group.
  private LinkedList<DN> nestedGroups = new LinkedList<DN>();
  //Passed to the group manager to see if the nested group list needs to be
  //refreshed.
  private long nestedGroupRefreshToken =
                              DirectoryServer.getGroupManager().refreshToken();
  /**
   * Creates a new, uninitialized static group instance.  This is intended for
@@ -311,8 +296,7 @@
  @Override()
  public boolean supportsNestedGroups()
  {
    // FIXME -- We should add support for nested groups.
    return false;
    return true;
  }
@@ -323,8 +307,12 @@
  @Override()
  public List<DN> getNestedGroupDNs()
  {
    // FIXME -- We should add support for nested groups.
    return Collections.<DN>emptyList();
    try {
       reloadIfNeeded();
    } catch (DirectoryException ex) {
      return Collections.<DN>emptyList();
    }
    return nestedGroups;
  }
@@ -336,8 +324,62 @@
  public void addNestedGroup(DN nestedGroupDN)
         throws UnsupportedOperationException, DirectoryException
  {
    // FIXME -- We should add support for nested groups.
    throw new UnsupportedOperationException();
     ensureNotNull(nestedGroupDN);
    synchronized (this)
    {
      if (nestedGroups.contains(nestedGroupDN))
      {
        int    msgID   = MSGID_STATICGROUP_ADD_NESTED_GROUP_ALREADY_EXISTS;
        String message = getMessage(msgID,String.valueOf(nestedGroupDN),
                                    String.valueOf(groupEntryDN));
        throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
                                     message, msgID);
      }
      LinkedHashSet<AttributeValue> values =
           new LinkedHashSet<AttributeValue>(1);
      values.add(new AttributeValue(memberAttributeType,
                                    nestedGroupDN.toString()));
      Attribute attr = new Attribute(memberAttributeType,
                                     memberAttributeType.getNameOrOID(),
                                     values);
      LinkedList<Modification> mods = new LinkedList<Modification>();
      mods.add(new Modification(ModificationType.ADD, attr));
      LinkedList<Control> requestControls = new LinkedList<Control>();
      requestControls.add(new Control(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE,
                                      false));
      InternalClientConnection conn =
           InternalClientConnection.getRootConnection();
      ModifyOperationBasis modifyOperation =
           new ModifyOperationBasis(conn,
                   InternalClientConnection.nextOperationID(),
                   InternalClientConnection.nextMessageID(), requestControls,
                               groupEntryDN, mods);
      modifyOperation.run();
      if (modifyOperation.getResultCode() != ResultCode.SUCCESS)
      {
        int    msgID   = MSGID_STATICGROUP_ADD_MEMBER_UPDATE_FAILED;
        String message = getMessage(msgID, String.valueOf(nestedGroupDN),
                              String.valueOf(groupEntryDN),
                              modifyOperation.getErrorMessage().toString());
        throw new DirectoryException(modifyOperation.getResultCode(), message,
                                     msgID);
      }
      LinkedList<DN> newNestedGroups = new LinkedList<DN>(nestedGroups);
      newNestedGroups.add(nestedGroupDN);
      nestedGroups = newNestedGroups;
      //Add it to the member DN list.
      LinkedHashSet<DN> newMemberDNs = new LinkedHashSet<DN>(memberDNs);
      newMemberDNs.add(nestedGroupDN);
      memberDNs = newMemberDNs;
    }
  }
@@ -349,8 +391,62 @@
  public void removeNestedGroup(DN nestedGroupDN)
         throws UnsupportedOperationException, DirectoryException
  {
    // FIXME -- We should add support for nested groups.
    throw new UnsupportedOperationException();
    ensureNotNull(nestedGroupDN);
    synchronized (this)
    {
      if (! nestedGroups.contains(nestedGroupDN))
      {
        int    msgID   = MSGID_STATICGROUP_REMOVE_NESTED_GROUP_NO_SUCH_GROUP;
        String message = getMessage(msgID, String.valueOf(nestedGroupDN),
                                    String.valueOf(groupEntryDN));
        throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE, message,
                                     msgID);
      }
      LinkedHashSet<AttributeValue> values =
           new LinkedHashSet<AttributeValue>(1);
      values.add(new AttributeValue(memberAttributeType,
                                                     nestedGroupDN.toString()));
      Attribute attr = new Attribute(memberAttributeType,
                                     memberAttributeType.getNameOrOID(),
                                     values);
      LinkedList<Modification> mods = new LinkedList<Modification>();
      mods.add(new Modification(ModificationType.DELETE, attr));
      LinkedList<Control> requestControls = new LinkedList<Control>();
      requestControls.add(new Control(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE,
                                      false));
      InternalClientConnection conn =
           InternalClientConnection.getRootConnection();
      ModifyOperationBasis modifyOperation =
           new ModifyOperationBasis(conn,
                   InternalClientConnection.nextOperationID(),
                   InternalClientConnection.nextMessageID(), requestControls,
                   groupEntryDN, mods);
      modifyOperation.run();
      if (modifyOperation.getResultCode() != ResultCode.SUCCESS)
      {
        int    msgID   = MSGID_STATICGROUP_REMOVE_MEMBER_UPDATE_FAILED;
        String message = getMessage(msgID,String.valueOf(nestedGroupDN),
                              String.valueOf(groupEntryDN),
                              modifyOperation.getErrorMessage().toString());
        throw new DirectoryException(modifyOperation.getResultCode(), message,
                                     msgID);
      }
      LinkedList<DN> newNestedGroups = new LinkedList<DN>(nestedGroups);
      newNestedGroups.remove(nestedGroupDN);
      nestedGroups = newNestedGroups;
      //Remove it from the member DN list.
      LinkedHashSet<DN> newMemberDNs = new LinkedHashSet<DN>(memberDNs);
      newMemberDNs.remove(nestedGroupDN);
      memberDNs = newMemberDNs;
    }
  }
@@ -360,14 +456,31 @@
   */
  @Override()
  public boolean isMember(DN userDN, Set<DN> examinedGroups)
         throws DirectoryException
          throws DirectoryException
  {
    if (! examinedGroups.add(getGroupDN()))
    reloadIfNeeded();
    if(memberDNs.contains(userDN))
    {
      return true;
    }
    else if (! examinedGroups.add(getGroupDN()))
    {
      return false;
    }
    return memberDNs.contains(userDN);
    else
    {
      for(DN nestedGroupDN : nestedGroups)
      {
        Group<? extends GroupImplementationCfg> g =
             (Group<? extends GroupImplementationCfg>)
              DirectoryServer.getGroupManager().getGroupInstance(nestedGroupDN);
        if((g != null) && (g.isMember(userDN, examinedGroups)))
        {
          return true;
        }
      }
    }
    return false;
  }
@@ -379,23 +492,68 @@
  public boolean isMember(Entry userEntry, Set<DN> examinedGroups)
         throws DirectoryException
  {
    if (! examinedGroups.add(getGroupDN()))
    {
      return false;
    }
    return memberDNs.contains(userEntry.getDN());
    return isMember(userEntry.getDN(), examinedGroups);
  }
  /**
   * Check if the group manager has registered a new group instance or removed a
   * a group instance that might impact this group's membership list.
   */
  private void
  reloadIfNeeded() throws DirectoryException
  {
    //Check if group instances have changed by passing the group manager
    //the current token.
    if(DirectoryServer.getGroupManager().
            hasInstancesChanged(nestedGroupRefreshToken))
    {
      synchronized (this)
      {
        Group thisGroup =
               DirectoryServer.getGroupManager().getGroupInstance(groupEntryDN);
        //Check if the group itself has been removed
        if(thisGroup == null) {
          int    msgID   = MSGID_STATICGROUP_GROUP_INSTANCE_INVALID;
          String message = getMessage(msgID, String.valueOf(groupEntryDN));
          throw new
               DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE, message, msgID);
        } else if(thisGroup != this) {
          LinkedHashSet<DN> newMemberDNs = new LinkedHashSet<DN>();
          MemberList memberList=thisGroup.getMembers();
          while (memberList.hasMoreMembers()) {
            try {
              newMemberDNs.add(memberList.nextMemberDN());
            } catch (MembershipException ex) {}
          }
          memberDNs=newMemberDNs;
        }
        LinkedList<DN> newNestedGroups = new LinkedList<DN>();
        for(DN dn : memberDNs)
        {
          Group gr=DirectoryServer.getGroupManager().getGroupInstance(dn);
          if(gr != null)
          {
            newNestedGroups.add(gr.getGroupDN());
          }
        }
        nestedGroupRefreshToken =
                DirectoryServer.getGroupManager().refreshToken();
        nestedGroups=newNestedGroups;
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public MemberList getMembers()
         throws DirectoryException
  {
    reloadIfNeeded();
    return new SimpleStaticGroupMemberList(groupEntryDN, memberDNs);
  }
@@ -409,6 +567,7 @@
                               SearchFilter filter)
         throws DirectoryException
  {
    reloadIfNeeded();
    if ((baseDN == null) && (filter == null))
    {
      return new SimpleStaticGroupMemberList(groupEntryDN, memberDNs);
@@ -554,6 +713,12 @@
      LinkedHashSet<DN> newMemberDNs = new LinkedHashSet<DN>(memberDNs);
      newMemberDNs.remove(userDN);
      memberDNs = newMemberDNs;
      //If it is in the nested group list remove it.
      if(nestedGroups.contains(userDN)) {
        LinkedList<DN> newNestedGroups = new LinkedList<DN>(nestedGroups);
        newNestedGroups.remove(userDN);
        nestedGroups = newNestedGroups;
      }
    }
  }
opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -5583,6 +5583,36 @@
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 537;
  /**
   * The message ID for the message that will be used if an attempt is made to
   * add a nested group to a static group that already includes that nested
   * group. This takes two arguments, which is the DN of the nested group
   * being added and the DN of the group.
   */
  public static final int MSGID_STATICGROUP_ADD_NESTED_GROUP_ALREADY_EXISTS =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 538;
  /**
   * The message ID for the message that will be used if an attempt is made to
   * remove a nested group from a static group that does not include that group.
   * This takes two arguments, which is the DN of the nested group being removed
   * and the DN of the group.
   */
  public static final int MSGID_STATICGROUP_REMOVE_NESTED_GROUP_NO_SUCH_GROUP =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 539;
  /**
   * The message ID for the message that will be used if an attempt is made to
   * refresh a static group member list using a group instance that has been
   * removed from the group manager via a ldap operation. This takes one
   * arguments, which is the DN of invalid or stale nested group instance.
   */
  public static final int MSGID_STATICGROUP_GROUP_INSTANCE_INVALID =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 540;
  /**
   * Associates a set of generic messages with the message IDs defined in this
@@ -8024,6 +8054,17 @@
    registerMessage(MSGID_REGEXMAP_SEARCH_FAILED,
                    "An internal failure occurred while attempting to " +
                    "resolve processed ID string %s to a user entry:  %s");
    registerMessage(MSGID_STATICGROUP_ADD_NESTED_GROUP_ALREADY_EXISTS,
                    "Cannot add group %s as a new nested group of static " +
                    "group %s because that group is already in the nested " +
                    "group list for the group");
    registerMessage(MSGID_STATICGROUP_REMOVE_NESTED_GROUP_NO_SUCH_GROUP,
                    "Cannot remove group %s as a nested group of static " +
                    "group %s because that group is not included in the " +
                    "nested group list for the group");
    registerMessage(MSGID_STATICGROUP_GROUP_INSTANCE_INVALID,
                    "Group instance with DN %s has been deleted and is no " +
                    "longer valid");
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java
@@ -35,12 +35,11 @@
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.testng.annotations.AfterClass;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.server.GroupImplementationCfg;
import org.opends.server.api.Group;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyDNOperationBasis;
import org.opends.server.extensions.DynamicGroup;
import org.opends.server.extensions.StaticGroup;
import org.opends.server.extensions.VirtualStaticGroup;
@@ -62,7 +61,7 @@
import org.opends.server.types.SearchScope;
import static org.testng.Assert.*;
import static org.testng.Assert.assertTrue;
/**
@@ -84,7 +83,11 @@
    TestCaseUtils.startServer();
  }
  @AfterClass()
  public void cleanUp() {
    GroupManager groupManager = DirectoryServer.getGroupManager();
    groupManager.deregisterAllGroups();
  }
  /**
   * Tests the {@code GroupManager.getGroupImplementations} method to ensure
@@ -113,6 +116,382 @@
               "Unexpected group class(es) registered:  " + groupClasses);
  }
  /**
   * Test static group nesting with some of the groups pointing to each
   * other in a circular fashion. Once this situation is detected the
   * membership check should return false.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testStaticGroupCircularNested() throws Exception {
    TestCaseUtils.initializeTestBackend(true);
    GroupManager groupManager = DirectoryServer.getGroupManager();
    groupManager.deregisterAllGroups();
    addNestedGroupTestEntries();
    DN group1DN = DN.decode("cn=group 1,ou=Groups,o=test");
    DN group2DN = DN.decode("cn=group 2,ou=Groups,o=test");
    DN group3DN = DN.decode("cn=group 3,ou=Groups,o=test");
    DN user1DN = DN.decode("uid=user.1,ou=People,o=test");
    DN user2DN = DN.decode("uid=user.2,ou=People,o=test");
    DN user3DN = DN.decode("uid=user.3,ou=People,o=test");
    DN user4DN = DN.decode("uid=user.4,ou=People,o=test");
    DN user5DN = DN.decode("uid=user.5,ou=People,o=test");
    Entry user1Entry = DirectoryServer.getEntry(user1DN);
    Entry user2Entry = DirectoryServer.getEntry(user2DN);
    Entry user3Entry = DirectoryServer.getEntry(user3DN);
    Entry user4Entry = DirectoryServer.getEntry(user4DN);
    Group group1Instance = groupManager.getGroupInstance(group1DN);
    Group group2Instance = groupManager.getGroupInstance(group2DN);
    Group group3Instance = groupManager.getGroupInstance(group3DN);
    assertNotNull(group1Instance);
    assertNotNull(group2Instance);
    assertNotNull(group3Instance);
    group1Instance.addNestedGroup(group2DN);
    group2Instance.addNestedGroup(group3DN);
    //Add circular nested group definition by adding group 1 to group 3
    //nested list. Group 1 contains group 2, which contains group 3, which
    //contains group 1.
    group3Instance.addNestedGroup(group1DN);
    group1Instance.addMember(user1Entry);
    group2Instance.addMember(user2Entry);
    group3Instance.addMember(user3Entry);
    group2Instance.addMember(user4Entry);
    //Search for DN not in any of the groups/
    assertFalse(group1Instance.isMember(user5DN));
  }
  /**
   * Test static group nesting wit one of the nested groups being a
   * dynamic group.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testStaticGroupDynamicNested() throws Exception {
    TestCaseUtils.initializeTestBackend(true);
    GroupManager groupManager = DirectoryServer.getGroupManager();
    groupManager.deregisterAllGroups();
    addNestedGroupTestEntries();
    DN group1DN = DN.decode("cn=group 1,ou=Groups,o=test");
    DN group2DN = DN.decode("cn=group 2,ou=Groups,o=test");
    DN group3DN = DN.decode("cn=group 3,ou=Groups,o=test");
    DN group4DN = DN.decode("cn=group 4,ou=Groups,o=test");
    DN user1DN = DN.decode("uid=user.1,ou=People,o=test");
    DN user2DN = DN.decode("uid=user.2,ou=People,o=test");
    DN user3DN = DN.decode("uid=user.3,ou=People,o=test");
    DN user4DN = DN.decode("uid=user.4,ou=People,o=test");
    DN user5DN = DN.decode("uid=user.5,ou=People,o=test");
    Entry user1Entry = DirectoryServer.getEntry(user1DN);
    Entry user2Entry = DirectoryServer.getEntry(user2DN);
    Entry user3Entry = DirectoryServer.getEntry(user3DN);
    Entry user4Entry = DirectoryServer.getEntry(user4DN);
    //User 5 is not added to any group, it matches the URL of the dynamic
    //group "group 4".
    Group group1Instance = groupManager.getGroupInstance(group1DN);
    Group group2Instance = groupManager.getGroupInstance(group2DN);
    Group group3Instance = groupManager.getGroupInstance(group3DN);
    //Group 4 is a dynamic group.
    Group group4Instance = groupManager.getGroupInstance(group4DN);
    assertNotNull(group1Instance);
    assertNotNull(group2Instance);
    assertNotNull(group3Instance);
    assertNotNull(group4Instance);
    group1Instance.addNestedGroup(group2DN);
    group2Instance.addNestedGroup(group3DN);
    //Dynamic group 4 is added to nested list of group 3.
    group3Instance.addNestedGroup(group4DN);
    group1Instance.addMember(user1Entry);
    group2Instance.addMember(user2Entry);
    group3Instance.addMember(user3Entry);
    group2Instance.addMember(user4Entry);
    //Check membership of user 5 through group 1. User 5 is a member of the
    //dynamic group "group 4" which is nested.
    assertTrue(group1Instance.isMember(user5DN));
  }
  /**
   * Invokes membership and nested group APIs using a group instance that has
   * been changed by the group manager via ldap modify.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testStaticGroupInstanceChange() throws Exception {
    TestCaseUtils.initializeTestBackend(true);
    GroupManager groupManager = DirectoryServer.getGroupManager();
    groupManager.deregisterAllGroups();
    addNestedGroupTestEntries();
    DN group1DN = DN.decode("cn=group 1,ou=Groups,o=test");
    DN group2DN = DN.decode("cn=group 2,ou=Groups,o=test");
    DN group3DN = DN.decode("cn=group 3,ou=Groups,o=test");
    DN group4DN = DN.decode("cn=group 4,ou=Groups,o=test");
    DN user1DN = DN.decode("uid=user.1,ou=People,o=test");
    DN user2DN = DN.decode("uid=user.2,ou=People,o=test");
    DN user3DN = DN.decode("uid=user.3,ou=People,o=test");
    DN user4DN = DN.decode("uid=user.4,ou=People,o=test");
    DN user5DN = DN.decode("uid=user.5,ou=People,o=test");
    Entry user1Entry = DirectoryServer.getEntry(user1DN);
    Entry user2Entry = DirectoryServer.getEntry(user2DN);
    Entry user3Entry = DirectoryServer.getEntry(user3DN);
    Entry user4Entry = DirectoryServer.getEntry(user4DN);
    Entry user5Entry = DirectoryServer.getEntry(user5DN);
    Group<? extends GroupImplementationCfg> group1Instance =
            (Group<? extends GroupImplementationCfg>)
                    groupManager.getGroupInstance(group1DN);
    assertNotNull(group1Instance);
    //Add even numbered groups.
    group1Instance.addNestedGroup(group2DN);
    group1Instance.addNestedGroup(group4DN);
    //Add even numbered members.
    group1Instance.addMember(user2Entry);
    group1Instance.addMember(user4Entry);
    //Switch things around, change groups and members to odd numbered nested
    //groups and odd numbered members via ldap modify.
    LinkedList<Modification> mods = new LinkedList<Modification>();
    Attribute g1 = new Attribute("member", "cn=group 1,ou=Groups,o=test");
    Attribute g2 = new Attribute("member", "cn=group 2,ou=Groups,o=test");
    Attribute g3 = new Attribute("member", "cn=group 3,ou=Groups,o=test");
    Attribute g4 = new Attribute("member", "cn=group 4,ou=Groups,o=test");
    Attribute u1 = new Attribute("member", "uid=user.1,ou=People,o=test");
    Attribute u2 = new Attribute("member", "uid=user.2,ou=People,o=test");
    Attribute u3 = new Attribute("member", "uid=user.3,ou=People,o=test");
    Attribute u4 = new Attribute("member", "uid=user.4,ou=People,o=test");
    Attribute u5 = new Attribute("member", "uid=user.5,ou=People,o=test");
    //Delete even groups and users.
    mods.add(new Modification(ModificationType.DELETE, g2));
    mods.add(new Modification(ModificationType.DELETE, g4));
    mods.add(new Modification(ModificationType.DELETE, u2));
    mods.add(new Modification(ModificationType.DELETE, u4));
    //Add odd groups and users.
    mods.add(new Modification(ModificationType.ADD, g1));
    mods.add(new Modification(ModificationType.ADD, g3));
    mods.add(new Modification(ModificationType.ADD, u1));
    mods.add(new Modification(ModificationType.ADD, u3));
    mods.add(new Modification(ModificationType.ADD, u5));
    InternalClientConnection conn =
            InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
            conn.processModify(group1Instance.getGroupDN(), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
    //Check that the user membership changes were picked up.
    assertFalse(group1Instance.isMember(user2Entry));
    assertFalse(group1Instance.isMember(user4Entry));
    assertTrue(group1Instance.isMember(user1Entry));
    assertTrue(group1Instance.isMember(user3Entry));
    assertTrue(group1Instance.isMember(user5Entry));
    assertFalse(group1Instance.isMember(group2DN));
    assertFalse(group1Instance.isMember(group4DN));
    assertTrue(group1Instance.isMember(group1DN));
    assertTrue(group1Instance.isMember(group3DN));
    //Check get members picked up everything.
    MemberList memberList = group1Instance.getMembers();
    while (memberList.hasMoreMembers())
    {
      DN memberDN = memberList.nextMemberDN();
      assertTrue(memberDN.equals(group1DN) || memberDN.equals(group3DN) ||
                                 memberDN.equals(user1DN) ||
                                 memberDN.equals(user3DN) ||
                                 memberDN.equals(user5DN));
    }
    //Check that the nested group changes were picked up.
    List<DN> nestedGroups=group1Instance.getNestedGroupDNs();
    assertFalse(nestedGroups.isEmpty());
    assertTrue(nestedGroups.contains(group1DN));
    assertFalse(nestedGroups.contains(group2DN));
    assertTrue(nestedGroups.contains(group3DN));
    assertFalse(nestedGroups.contains(group4DN));
  }
  /**
   * Invokes membership and nested group APIs using a group instance that has
   * been removed from the group manager via ldap delete.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testStaticGroupInstanceInvalid() throws Exception {
    TestCaseUtils.initializeTestBackend(true);
    GroupManager groupManager = DirectoryServer.getGroupManager();
    groupManager.deregisterAllGroups();
    addNestedGroupTestEntries();
    DN group1DN = DN.decode("cn=group 1,ou=Groups,o=test");
    DN group2DN = DN.decode("cn=group 2,ou=Groups,o=test");
    DN group3DN = DN.decode("cn=group 3,ou=Groups,o=test");
    DN group4DN = DN.decode("cn=group 4,ou=Groups,o=test");
    DN user1DN = DN.decode("uid=user.1,ou=People,o=test");
    Entry user1Entry = DirectoryServer.getEntry(user1DN);
    Group<? extends GroupImplementationCfg> group1Instance =
            (Group<? extends GroupImplementationCfg>)
                    groupManager.getGroupInstance(group1DN);
    Group<? extends GroupImplementationCfg> group2Instance =
            (Group<? extends GroupImplementationCfg>)
                    groupManager.getGroupInstance(group2DN);
    assertNotNull(group1Instance);
    //Add some nested groups and members.
    group1Instance.addNestedGroup(group2DN);
    group1Instance.addMember(user1Entry);
    InternalClientConnection conn =
            InternalClientConnection.getRootConnection();
    //Delete the group.
    DeleteOperation deleteOperation = conn.processDelete(group1DN);
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    assertNull(groupManager.getGroupInstance(group1DN));
    //Membership check should throw an exception.
    try
    {
      group1Instance.isMember(user1DN);
      throw new AssertionError("Expected isMember to fail but " +
              "it didn't");
    } catch (DirectoryException ex) {}
    //Nested groups should be empty.
    List<DN> nestedGroups=group1Instance.getNestedGroupDNs();
    assertTrue(nestedGroups.isEmpty());
    try
    {
      MemberList memberList=group1Instance.getMembers();
      throw new AssertionError("Expected getMembers to fail but " +
              "it didn't");
    } catch (DirectoryException ex) {}
  }
  /**
   * Invokes nested group API methods on various nested group
   * scenerios.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testStaticGroupNestedAPI() throws Exception {
    TestCaseUtils.initializeTestBackend(true);
    GroupManager groupManager = DirectoryServer.getGroupManager();
    groupManager.deregisterAllGroups();
    addNestedGroupTestEntries();
    DN group1DN = DN.decode("cn=group 1,ou=Groups,o=test");
    DN group2DN = DN.decode("cn=group 2,ou=Groups,o=test");
    DN group3DN = DN.decode("cn=group 3,ou=Groups,o=test");
    DN group4DN = DN.decode("cn=group 4,ou=Groups,o=test");
    DN bogusGroup = DN.decode("cn=bogus group,ou=Groups,o=test");
    DN user1DN = DN.decode("uid=user.1,ou=People,o=test");
    DN user2DN = DN.decode("uid=user.2,ou=People,o=test");
    DN user3DN = DN.decode("uid=user.3,ou=People,o=test");
    DN user4DN = DN.decode("uid=user.4,ou=People,o=test");
    DN user5DN = DN.decode("uid=user.5,ou=People,o=test");
    Entry user1Entry = DirectoryServer.getEntry(user1DN);
    Entry user2Entry = DirectoryServer.getEntry(user2DN);
    Entry user3Entry = DirectoryServer.getEntry(user3DN);
    Entry user4Entry = DirectoryServer.getEntry(user4DN);
    Entry user5Entry = DirectoryServer.getEntry(user5DN);
    //These casts are needed so there isn't a unchecked assignment
    //compile warning in the getNestedGroupDNs calls below.  Some IDEs
    //will give a unchecked cast warning.
    Group<? extends GroupImplementationCfg> group1Instance =
            (Group<? extends GroupImplementationCfg>)
                    groupManager.getGroupInstance(group1DN);
    Group<? extends GroupImplementationCfg> group2Instance =
            (Group<? extends GroupImplementationCfg>)
                    groupManager.getGroupInstance(group2DN);
    Group group3Instance = groupManager.getGroupInstance(group3DN);
    assertNotNull(group1Instance);
    assertNotNull(group2Instance);
    assertNotNull(group3Instance);
    //Add nested groups.
    group1Instance.addNestedGroup(group2DN);
    group2Instance.addNestedGroup(group3DN);
    //Add some members.
    group1Instance.addMember(user1Entry);
    group2Instance.addMember(user2Entry);
    group3Instance.addMember(user3Entry);
    group2Instance.addMember(user4Entry);
    group3Instance.addMember(user5Entry);
    //Check if group 3 shows up in the group 2 membership list.
    MemberList memberList = group2Instance.getMembers();
    boolean found=false;
    while (memberList.hasMoreMembers())
    {
      if( memberList.nextMemberDN().equals(group3DN)) {
        found=true;
        break;
      }
    }
    assertTrue(found);
    //Check membership via group 1 using nesting of group 2 and group 3.
    //User 5 is in group 3.
    assertTrue(group1Instance.isMember(user5DN));
    group2Instance.removeNestedGroup(group3DN);
    //Check group 3 is removed from group 2 membership list.
    memberList = group2Instance.getMembers();
    found=false;
    while (memberList.hasMoreMembers())
    {
      if(memberList.nextMemberDN().equals(user3DN)){
        found=true;
        break;
      }
    }
    assertFalse(found);
    //Check membership via group 1 should fail now, since nested group 3
    //was removed from group 2.
    assertFalse(group1Instance.isMember(user5DN));
    group2Instance.addNestedGroup(group3DN);
    //Check remove member call also removes DN from nested group list.
    group2Instance.removeMember(group3DN);
    assertFalse(group2Instance.getNestedGroupDNs().contains(group3DN));
    group1Instance.removeNestedGroup(group2DN);
    List<DN> nestedGroups=group1Instance.getNestedGroupDNs();
    assertTrue(nestedGroups.isEmpty());
    //Add nested groups to group 1
    group1Instance.addNestedGroup(group2DN);
    group1Instance.addNestedGroup(group3DN);
    group1Instance.addNestedGroup(group4DN);
    //Check get nested groups DNs list returns correct DN list.
    List<DN> nestedGroups1=group1Instance.getNestedGroupDNs();
    assertFalse(nestedGroups1.isEmpty());
    assertTrue(nestedGroups1.contains(group2DN));
    assertTrue(nestedGroups1.contains(group3DN));
    assertTrue(nestedGroups1.contains(group4DN));
    //Check removing a group not in the nested group list fails.
    try
    {
      group1Instance.removeNestedGroup(bogusGroup);
      throw new AssertionError("Expected removeNestedGroup to fail but " +
              "it didn't");
    } catch (DirectoryException ex) {}
    //Check adding a nested group already in the nested group list fails.
    try
    {
      group1Instance.addNestedGroup(group2DN);
      throw new AssertionError("Expected addNestedGroup to fail but " +
              "it didn't");
    } catch (DirectoryException ex) {}
    //Modify list via ldap modify.
    LinkedList<Modification> mods = new LinkedList<Modification>();
    Attribute a2 = new Attribute("member", "cn=group 2,ou=Groups,o=test");
    Attribute a3 = new Attribute("member", "cn=group 1,ou=Groups,o=test");
    mods.add(new Modification(ModificationType.DELETE, a2));
    mods.add(new Modification(ModificationType.ADD, a3));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
                        conn.processModify(group1Instance.getGroupDN(), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
    //Check removing a group already removed via ldap modify fails.
    try
    {
      group1Instance.removeNestedGroup(group2DN);
      throw new AssertionError("Expected removeNestedGroup to fail but " +
              "it didn't");
    } catch (DirectoryException ex) {}
    //Check adding a group added via ldap modify fails.
    try
    {
      group1Instance.addNestedGroup(group1DN);
      throw new AssertionError("Expected addNestedGroup to fail but " +
              "it didn't");
    } catch (DirectoryException ex) {}
  }
  /**
@@ -192,23 +571,25 @@
    assertTrue(groupInstance.isMember(user2DN));
    assertFalse(groupInstance.isMember(user3DN));
    assertFalse(groupInstance.supportsNestedGroups());
    assertTrue(groupInstance.supportsNestedGroups());
    assertTrue(groupInstance.getNestedGroupDNs().isEmpty());
    try
    {
      groupInstance.addNestedGroup(DN.decode("uid=test,ou=People,o=test"));
      throw new AssertionError("Expected addNestedGroup to fail but it " +
                               "didn't");
    } catch (UnsupportedOperationException uoe) {}
    } catch (DirectoryException ex) {
           throw new AssertionError("Expected addNestedGroup to succeed but" +
                                    " it didn't");
    }
    try
    {
      groupInstance.removeNestedGroup(
           DN.decode("uid=test,ou=People,o=test"));
      throw new AssertionError("Expected removeNestedGroup to fail but " +
                               "it didn't");
    } catch (UnsupportedOperationException uoe) {}
    } catch (DirectoryException ex) {
            throw new AssertionError("Expected removeNestedGroup to succeed " +
                    "but it didn't");
    }
    assertTrue(groupInstance.mayAlterMemberList());
@@ -1691,5 +2072,100 @@
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    assertNull(groupManager.getGroupInstance(groupDN));
  }
  /**
   * Adds nested group entries.
   *
   * @throws Exception If a problem adding the entries occurs.
   */
  private void addNestedGroupTestEntries() throws Exception {
    TestCaseUtils.addEntries(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People",
      "",
      "dn: ou=Groups,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: Groups",
      "",
      "dn: cn=group 1,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: group 1",
      "",
      "dn: cn=group 2,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: group 2",
      "",
      "dn: cn=group 3,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: group 3",
      "",
      "dn: cn=group 4,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfURLs",
      "cn: group 4",
      "memberURL: ldap:///ou=people,o=test??sub?(sn>=5)",
       "",
      "dn: uid=user.1,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: user.1",
      "givenName: User",
      "sn: 1",
      "cn: User 1",
      "userPassword: password",
      "",
      "dn: uid=user.2,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: user.2",
      "givenName: User",
      "sn: 2",
      "cn: User 2",
      "userPassword: password",
      "",
      "dn: uid=user.3,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: user.3",
      "givenName: User",
      "sn: 3",
      "cn: User 3",
      "userPassword: password",
      "",
      "dn: uid=user.4,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: user.4",
      "givenName: User",
      "sn: 4",
      "cn: User 4",
      "userPassword: password",
      "",
      "dn: uid=user.5,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: user.5",
      "givenName: User",
      "sn: 5",
      "cn: User 5",
      "userPassword: password");
  }
}