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.