Primary Group Membership

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.

  • We can enumerate membership using both the LDAP and WinNT providers using the IADsUser.Groups property, and figure out which group is missing, and we will have found our primary group. This works fine, but it seems rather clunky, as we have to use both providers to get this information, not to mention we are relying on an aberration for an implementation.
  • We could also search for all of the groups in the domain and return their primaryGroupToken attribute. This constructed attribute is the RID of the group. We can then compare this RID to the user's primaryGroupID attribute and see if they match. When they match, we have found our primary group. The major downside is that since primaryGroupToken is a constructed attribute, we cannot search for it directly. Furthermore, the search is relatively slow on domains with a large number of groups, since we would have to inspect them all.
  • Knowing the user's SID and the group's RID we can construct the primary group's SID and directly bind to 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.

  1. Retrieve the user's SID in byte array format:

    byte[] userSID = user.Properties["objectSid"][0] as byte[];
    
  2. Retrieve the user's primaryGroupId RID:

    int primaryGroupID =
     (int)user.Properties["primaryGroupId"][0];
    
  3. Overwrite the user's RID with the primary group RID:

    //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;
    }
    
  4. Construct a SID binding string and bind to the primary group:

    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



The. NET Developer's Guide to Directory Services Programming
The .NET Developers Guide to Directory Services Programming
ISBN: 0321350170
EAN: 2147483647
Year: 2004
Pages: 165

Similar book on Amazon

Flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net