From b52ab703908a5d76a22169f35781b52e90302591 Mon Sep 17 00:00:00 2001
From: dugan <dugan@localhost>
Date: Mon, 13 Aug 2007 11:57:26 +0000
Subject: [PATCH] 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

---
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java |  500 ++++++++++++++++++++++++++++++++++
 opends/src/server/org/opends/server/extensions/StaticGroup.java                            |  251 ++++++++++++++---
 opends/src/server/org/opends/server/core/GroupManager.java                                 |   45 ++
 opends/src/server/org/opends/server/messages/ExtensionsMessages.java                       |   41 ++
 4 files changed, 778 insertions(+), 59 deletions(-)

diff --git a/opends/src/server/org/opends/server/core/GroupManager.java b/opends/src/server/org/opends/server/core/GroupManager.java
index cf84c84..236edb0 100644
--- a/opends/src/server/org/opends/server/core/GroupManager.java
+++ b/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;
+  }
 }
 
diff --git a/opends/src/server/org/opends/server/extensions/StaticGroup.java b/opends/src/server/org/opends/server/extensions/StaticGroup.java
index 338b0d4..9a119ec 100644
--- a/opends/src/server/org/opends/server/extensions/StaticGroup.java
+++ b/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;
+      }
     }
   }
 
diff --git a/opends/src/server/org/opends/server/messages/ExtensionsMessages.java b/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
index e59aa07..b6d0c38 100644
--- a/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
+++ b/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");
   }
 }
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java
index 1a8c231..8473b4d 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java
+++ b/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");
+  }
 }
 

--
Gitblit v1.10.0