Primary groups are an artifact of POSIX compliance for Windows NT. Unless we are using POSIX-compliant applications or Services for Macintosh, we typically do not change this value from its default of Domain Users.
Unlike normal group membership where the member attribute holds the group membership, primary group membership is held on the member's side rather than the group's side in the form of the primaryGroupID attribute. This attribute holds the significant and unique part of the primary group's security identifier (SID), known as the relative identifier (RID). It is only through calculation that we can determine the user's membership in a particular primary group. It is important to note that a user can be part of only one primary group at a time.
The difficulty with using the primary group starts with the inconsistency of how different ADSI providers treat it. Using the WinNT provider, we can enumerate all groups using the IADsUser.Groups property. This enumeration will include all groups including the primary group, but we have no indication as to which group is the primary group. Just to be contrary, the LDAP provider will enumerate all groups except for the primary group using the same ADSI interface. In order to include the primary group using the LDAP provider, we have to enumerate all the security groups held in the tokenGroups attribute, but again we have no indication as to which one is the primary group.
In these scenarios, we can see a couple of methods by which to determine which group is the primary group.
It turns out that the last technique is the easiest to do in .NET, as we can easily handle the resulting byte array and determine the primary group.
The steps to perform this are simple.
byte[] userSID = user.Properties["objectSid"][0] as byte[];
int primaryGroupID = (int)user.Properties["primaryGroupId"][0];
//Create the Primary Group SID byte[] sidBytes = CreatePrimaryGroupSID(userSID, primaryGroupID); private byte[] CreatePrimaryGroupSID( byte[] userSid, int primaryGroupID) { //convert the int into a byte array byte[] rid = BitConverter.GetBytes(primaryGroupID); //place the bytes into the user's SID byte array //overwriting them as necessary for (int i=0; i < rid.Length; i++) { userSid.SetValue( rid[i], new long[]{userSid.Length - (rid.Length - i)} ); } return userSid; }
adsPath = String.Format( "LDAP://", BuildOctetString(sidBytes) ); DirectoryEntry primaryGroup = new DirectoryEntry( adsPath, null, null, AuthenticationTypes.Secure ); //we now have our primary group using (primaryGroup) { Console.WriteLine( "Primary Group: {0}", primaryGroup.Name ); } //From Listing 3.5 in Chapter 3 private string BuildOctetString(byte[] bytes) { StringBuilder sb = new StringBuilder(); for(int i=0; i < bytes.Length; i++) { sb.Append(bytes[i].ToString("X2")); } return sb.ToString(); }
We will typically use this technique when we need to determine the primary group directly. Otherwise, if we only want to obtain an enumeration of our security groups, including the primary group, we should rely upon using the tokenGroups attribute, as shown in Chapter 10.
Some developers might be wondering how to set the primary group for a user to something other than the default value of Domain Users. As we mentioned earlier, unless we are specifically dealing with POSIX applications or Macintosh clients, there is no reason to attempt this. However, throwing caution to the wind, we will show you how to accomplish this even if we don't think you will generally ever need to do this:
//group is DirectoryEntry for primary group group.RefreshCache(new string[]{"primaryGroupToken"}); //this is our user DirectoryEntry that will have //its primary group set to 'group'. user.Properties["primaryGroupID"].Value = group.Properties["primaryGroupToken"].Value; user.CommitChanges();
The code seems pretty simple, and for the most part, it is. Just be aware that the user must already be a member of the group that is to be set as the primary group. That might seem strange or possibly redundant, but if we consider that it would be a major security violation if just anyone could change their primary group to Domain Admins just because they had the ability to write to the primaryGroupID attribute, we can see why this limitation exists. Once the primary group is set, we can remove the direct group membership from the group itself and the user will still be a member.
Part I: Fundamentals
Introduction to LDAP and Active Directory
Introduction to .NET Directory Services Programming
Binding and CRUD Operations with DirectoryEntry
Searching with the DirectorySearcher
Advanced LDAP Searches
Reading and Writing LDAP Attributes
Active Directory and ADAM Schema
Security in Directory Services Programming
Introduction to the ActiveDirectory Namespace
Part II: Practical Applications
User Management
Group Management
Authentication
Part III: Appendixes
Appendix A. Three Approaches to COM Interop with ADSI
Appendix B. LDAP Tools for Programmers
Appendix C. Troubleshooting and Help
Index