1 Reply Latest reply on Jul 12, 2005 7:17 AM by 843793

    JNDI, Active Directory and Group Memberships

    800477
      Aarrgghhh !

      You don't add groups to users, you add users (or groups, or computers ..) to groups !

      One of the attributes of a user object is "memberOf" and conversely one of the attributes of a group is "member"

      memberOf is not a "real" attribute per se, meaning that it is read only, it contains "links" to groups and that it is constructed from all the groups that the user is a member of in that domain.

      The reason why it has links to groups is to ensure referential integrity Eg. if you delete, rename or move a user, the group's membership is correctly maintained.

      it is read-only, and if you attempt to modify it's values you wil get a LDAP Error of the form:
      javax.naming.OperationNotSupportedException: [LDAP: error code 53 - 0000209A: SvcErr: DSID-031A0DD1, problem 5003 (WILL_NOT_PERFORM),data 0];

      One other phenonema is that the user's membership only reflects the groups that are known on the domain controller that is being queried. In a multi-domain or multi-forest environment, a domain controller will only have knowledge of groups in it's own domain.

      If the domain controller is a Global Catalog, it will have knowledge of all groups in the forest so it will reflect the list of groups in the forest that a user is a member of.

      This is a slight simplification, with Windows Server 2003, branch office scenarios enable caching of group memberships without the need for a global catalog. (Me thinks a description of the Global Catalog is a future forum topic).

      Therefore viewing a user's memberOf attribute may not reveal the full list of groups that a user is a member of. In addition, memberOf does not contain the user's Primary Group membership, nor does it reflect groups in other forests that the user may belong to.

      The following code demonstrates viewing a user's memberOf attribute
      /**
       * memberof.java
       * December 2004
       * Sample JNDI application to determine what groups a user belongs to
       * 
       */
      
      import java.util.Hashtable;
      import javax.naming.*;
      import javax.naming.ldap.*;
      import javax.naming.directory.*;
      
      public class memberof     {
           public static void main (String[] args)     {
           
                Hashtable env = new Hashtable();
                String adminName = "CN=Administrator,CN=Users,DC=ANTIPODES,DC=COM";
                String adminPassword = "XXXXXXX";
                String ldapURL = "ldap://mydc.antipodes.com:389";
                env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
                //set security credentials, note using simple cleartext authentication
                env.put(Context.SECURITY_AUTHENTICATION,"simple");
                env.put(Context.SECURITY_PRINCIPAL,adminName);
                env.put(Context.SECURITY_CREDENTIALS,adminPassword);
                          
                //connect to my domain controller
                env.put(Context.PROVIDER_URL,ldapURL);
                
                try {
      
                     //Create the initial directory context
                     LdapContext ctx = new InitialLdapContext(env,null);
                
                     //Create the search controls           
                     SearchControls searchCtls = new SearchControls();
                
                     //Specify the search scope
                     searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
      
                     //specify the LDAP search filter
                     String searchFilter = "(&(objectClass=user)(CN=Andrew Anderson))";
                
                     //Specify the Base for the search
                     String searchBase = "DC=antipodes,DC=com";
      
                     //initialize counter to total the group members
                     int totalResults = 0;
      
                     //Specify the attributes to return
                     String returnedAtts[]={"memberOf"};
                     searchCtls.setReturningAttributes(returnedAtts);
                
                     //Search for objects using the filter
                     NamingEnumeration answer = ctx.search(searchBase, searchFilter, searchCtls);
      
                     //Loop through the search results
                     while (answer.hasMoreElements()) {
                          SearchResult sr = (SearchResult)answer.next();
      
                          System.out.println(">>>" + sr.getName());
      
                          //Print out the groups
       
                          Attributes attrs = sr.getAttributes();
                          if (attrs != null) {
      
                               try {
                                    for (NamingEnumeration ae = attrs.getAll();ae.hasMore();) {
                                         Attribute attr = (Attribute)ae.next();
                                         System.out.println("Attribute: " + attr.getID());
                                         for (NamingEnumeration e = attr.getAll();e.hasMore();totalResults++) {
      
                                              System.out.println(" " +  totalResults + ". " +  e.next());
                                         }
      
                                    }
      
                               }      
                               catch (NamingException e)     {
                                    System.err.println("Problem listing membership: " + e);
                               }
                          
                          }
                     }
      
                     System.out.println("Total groups: " + totalResults);
                     ctx.close();
      
                } 
                
                catch (NamingException e) {
                     System.err.println("Problem searching directory: " + e);
                      }
           }
      }
      An alternative is to use another constructed attribute, tokenGroups. It will return the list of Security Identifiers (SID) that are in the user's security token.

      There are a few things to be aware of when using tokenGroups:
      1. The SID's are in binary format and would need to be formatted into the "S-1-5-aa-bb-cc-dd" format to be human readable
      2. You would then need to do searches using the SID to find the distingusihed names of the groups that tehy map to.
      3. The search base must be OBJECT_SCOPE
      Eg.
      //Specify the search scope
      searchCtls.setSearchScope(SearchControls.OBJECT_SCOPE);
      
      //specify the LDAP search filter
      String searchFilter = "(objectClass=user)";
                
      //Specify the Base for the search
      String searchBase = "CN=Andrew Anderson,OU=Research,DC=antipodes,DC=com";
      And finally another alternative is to examine the members of a group.

      Even if a group contains members from other domains, it's membership is complete. It's referential integrity is maintained, for example if a user in another domain is renamed or moved, an Active Directory process called the Infrastructure Master ensure that the membership list is correct.

      The following code demonstrates retrieving the list of members from a group.
      /**
       * member.java
       * 6 July 2001
       * Sample JNDI application to retrieve group members
       * 
        */
      
      import java.util.Hashtable;
      import javax.naming.*;
      import javax.naming.ldap.*;
      import javax.naming.directory.*;
      
      public class member     {
           public static void main (String[] args)     {
           
                Hashtable env = new Hashtable();
                String adminName = "CN=Administrator,CN=Users,DC=ANTIPODES,DC=COM";
                String adminPassword = "XXXXXXX";
                String ldapURL = "ldap://mydc.antipodes.com:389";
                env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
                //set security credentials, note using simple cleartext authentication
                env.put(Context.SECURITY_AUTHENTICATION,"simple");
                env.put(Context.SECURITY_PRINCIPAL,adminName);
                env.put(Context.SECURITY_CREDENTIALS,adminPassword);
                          
                //connect to my domain controller
                env.put(Context.PROVIDER_URL,ldapURL);
                
                try {
      
                     //Create the initial directory context
                     LdapContext ctx = new InitialLdapContext(env,null);
                
                     //Create the search controls           
                     SearchControls searchCtls = new SearchControls();
                
                     //Specify the search scope
                     searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
      
                     //specify the LDAP search filter
                     String searchFilter = "(&(objectClass=group)(CN=All Research))";
                
                     //Specify the Base for the search
                     String searchBase = "DC=antipodes,DC=com";
      
                     //initialize counter to total the group members
                     int totalResults = 0;
      
                     //Specify the attributes to return
                     String returnedAtts[]={"member"};
                     searchCtls.setReturningAttributes(returnedAtts);
                
                     //Search for objects using the filter
                     NamingEnumeration answer = ctx.search(searchBase, searchFilter, searchCtls);
      
                     //Loop through the search results
                     while (answer.hasMoreElements()) {
                          SearchResult sr = (SearchResult)answer.next();
      
                          System.out.println(">>>" + sr.getName());
      
                          //Print out the members
       
                          Attributes attrs = sr.getAttributes();
                          if (attrs != null) {
      
                               try {
                                    for (NamingEnumeration ae = attrs.getAll();ae.hasMore();) {
                                         Attribute attr = (Attribute)ae.next();
                                         System.out.println("Attribute: " + attr.getID());
                                         for (NamingEnumeration e = attr.getAll();e.hasMore();totalResults++) {
      
                                              System.out.println(" " +  totalResults + ". " +  e.next());
                                         }
      
                                    }
      
                               }      
                               catch (NamingException e)     {
                                    System.err.println("Problem listing members: " + e);
                               }
                          
                          }
                     }
      
                     System.out.println("Total members: " + totalResults);
                     ctx.close();
      
                } 
                
                catch (NamingException e) {
                     System.err.println("Problem searching directory: " + e);
                      }
           }
      }
      One problems with large group memberships is that a feature of the Active Directory which is designed to ensure performance and limit Denial of Service attacks is that it limits the number of results that may be returned from a mult-valued attribute to 1000 values. Therefore the above code such would only retrieve 1000 members from a group even though it may contain more.

      To retrieve all the values of a mult-valued attribute such as the member attribute you need to use the range retrieval control. A description and sample code is available in this forum at:

      http://forum.java.sun.com/thread.jspa?threadID=578347&tstart=15

      Unlike users, the members attribute is read & write, therefore if you want to add a user to a group, you do this by updating the members attribute.

      If you want to add a new member, it is a add operation, if you want to delete a member it is a delete operation etc.

      The following is some sample code to add a member to a group:
      /**
       * addmember.java
       * 5 July 2001
       * Sample JNDI application that adds a user to a group in the Active Directory.
       * 
       */
      
      import java.util.Hashtable;
      import javax.naming.directory.*;
      import javax.naming.*;
      
      public class addmember
      {
           public static void main (String[] args)
           {
           
                Hashtable env = new Hashtable();
                String adminName = "CN=Administrator,CN=Users,DC=antipodes,DC=com";
                String adminPassword = "XXXXXX";
                String userName = "CN=Andrew Anderson,OU=Research,DC=antipodes,DC=com";
                String groupName = "CN=Telemetry,OU=Research,DC=antipodes,DC=com";
                
                env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
           
                //set security credentials, note using simple cleartext authentication
                env.put(Context.SECURITY_AUTHENTICATION,"simple");
                env.put(Context.SECURITY_PRINCIPAL,adminName);
                env.put(Context.SECURITY_CREDENTIALS,adminPassword);
                          
                //connect to my domain controller
                env.put(Context.PROVIDER_URL, "ldap://mydc.antipodes.com:389");
                          
                try {
      
                     // Create the initial directory context
                     InitialDirContext ctx = new InitialDirContext(env);
      
                     //Create a LDAP add attribute for the member attribute
                     ModificationItem mods[] = new ModificationItem[1];
                     mods[0]= new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("member", userName)); 
      
                     //update the group
                     ctx.modifyAttributes(groupName,mods);
      
                     ctx.close();
                
                     System.out.println("Added " + userName + " to " + groupName);
                
                } 
      
                catch (NamingException e) {
                     System.err.println("Problem adding member: " + e);
                }
           
           }
      }
      One other thing to note is that both the syntax of the member and memberOf attribute is distinguished name. This tends to make it easy to find and bind to other objects in the directory.