Philippsen's Blog

Everyday findings in my world of .net and related stuff

Archive for June, 2014

Fetch users from AD using a security group as point of entry

Posted by Torben M. Philippsen on June 27, 2014

In my organisation I have been coding a small application that fetches users from our corporate AD. _Until now I have been using a colection of OU’s to get users from different locations in our AD. However now our IT department has organized the users in security groups. It is common practice for security administrators to create hierarchies of security groups, which allows for easier membership management.

The requirement I faced was that whatever privileges were granted to members of a security group, those privileges apply to any members of security groups found downstream in the hierarchy. This is also a commonly used concept. It was necessary for me to find the security group in AD (represented as a single directory entry) and then drill down into each level of the group hierarchy and find all user members of all associated security groups. I will try to boil it all down to an “easy to go through” article.

My point of entry was this MSDN blogpost – thanks for the inspiration. However I quickly noticed that the code samples lacked a lot of code in order for me to produce a working proof of concept application.

Googling the parts that were missing I actually managed to combine all into a working sample – feel free to download it from here.

Here is how it works:

  1. Looking at the “Tester” project You’ll notice that it contains a simple main method
             static  void  Main(string [] args)
             {
                 ADUserFromSecGroupsPOC.GetAllUsers  users = new  ADUserFromSecGroupsPOC.GetAllUsers();
                 List<ADUser> searchresult = new  List<ADUser>();
                 searchresult = users.GetADSecurityGroupUsers("USR_ERP_RMSsubscription");
                 foreach  (ADUser u in searchresult)
                 {
                     Console.WriteLine(u.AccountExpires);
                     Console.WriteLine(u.PrimarySMTPAddress);
                     Console.WriteLine(u.SAMAccountName);
                     Console.WriteLine(u.SID);
                     Console.WriteLine(u.UserAccountControl);
                     Console.WriteLine("----------------------------------------------------------------" );
                 }
                 Console.ReadKey();
             }

  2. The magic happens in the “ADUserFromSecGroupsPOC” class when calling the method “GetADSecurityGroupUsers” method parsing the security group used as single point of entry.
    /// <summary>
    /// This method takes security group alias and fetches list of user alias that are member of this SG   
    /// </summary>
    ///
    /// <param name="sgAlias">Security group alias</param>
    /// <returns>List of String</returns>
    public  List <ADUser> GetADSecurityGroupUsers(String  sgAlias)
    {
        string  path = "LDAP://OU=Security groups,DC=deverp,DC=local" ;
        string  filter;
        string  filterFormat = "(cn={0})" ;
        filter = String .Format(filterFormat, sgAlias);
        //Get all the AD directory entries of this security group.   
        PropertyCollection  properties = CISFLDAP.GetADPropertiesForSingleEntry(path, filter);
        List <ADUser> groupMembers = new  List <ADUser>();
        if  (properties != null)
        {
             //Used to limit AD search of members to only security groups and users.   
             //The filter is built here but only really needed in the recursive method called below.   
             //Was placed here for performance reasons.  Has no affect on AD call in this method.   
             Collection <int> sAMAccountTypes = new  Collection <int>();
             sAMAccountTypes.Add((int)AuthZGlobals .sAMAccountType.SecurityGroup);
             sAMAccountTypes.Add((int)AuthZGlobals .sAMAccountType.User);
             sAMAccountTypes.Add((int)AuthZGlobals .sAMAccountType.DomainLocalGroup);
             //Builds the filter based on the sAMAccountTypes in the collection.   
             string  sAMAccountFilter = CISFLDAP .MakesAMAccountTypeQueryString(sAMAccountTypes);
             //Look up all the members of this directory entry and look for person data   
             //to add to the collection.   
             groupMembers = GetUsersInGroup(properties, groupMembers, sAMAccountFilter);//GetUsersInGroup do not return anything.    
        }
        return  groupMembers;
    }
    
    

     

     

  3. From this security group we will drilldown adding ONLY user accounts from all nested levels. If the type of the member is a security group, the method will recurse and get the members for this new security group:
    /// <summary>

    /// Recurses through the's member records and collects all the users.   
    /// </summary>
    /// <param name="properties">The collection of the directory's properties.</param>
    /// <param name="groupMembers">List of users found. </param>
    /// <param name="filter">sAMAccountTypes to filter the AD search on.</param>
    public  List <ADUser > GetUsersInGroup(PropertyCollection  properties, List <ADUser > groupMembers, string  filter)
    {
        string  pathFormat = "LDAP://{0}" ;
        string  memberIdx = "member" ;
        string  sAMAccountNameIdx = "sAMAccountName" ;
        string  sAMAccountTypeIdx = "sAMAccountType" ;
        DateTime  accountExpires = DateTime .MinValue;
        bool  hasExpired = false ;
        //string personnelNumberIdx = "extensionAttribute4";
        if  (properties[memberIdx] != null )
        {
            foreach  (object  property in  properties[memberIdx])
            {
                string  distinguishedName = property.ToString();
                string  path = String .Format(pathFormat, distinguishedName);
                //Get the directory entry for this member record.  Filters for only the sAMAccountTypes  
                //security group and user.   
                PropertyCollection  childProperties = CISFLDAP .GetADPropertiesForSingleEntry(path, filter);
                if  (childProperties != null )
                {
                     //If the's sAMAccountType is User.   
                     if  ((int )childProperties[sAMAccountTypeIdx].Value == (int )AuthZGlobals .sAMAccountType .User)
                     {
                        //If member is a user then add, else member is a Service Account with no    
                        //personnel number.   
                        //if (childProperties[personnelNumberIdx].Value != null)
                        if  (childProperties[sAMAccountNameIdx].Value != null )
                        {
                            //groupMembers.Add(searchResults.Properties[sAMAccountNameIdx].Value.ToString());
                            ADUser  userObj = new  ADUser ();
                            userObj.PrimarySMTPAddress = GetPrimarySMTPAdress(childProperties["proxyaddresses" ]);
                            userObj.SAMAccountName = childProperties["SAMAccountName" ].Value.ToString();
                            userObj.SID = GetSidString((byte []) childProperties["objectSid" ][0]);
                            if  (childProperties["accountExpires" ].Count > 0)
                            {
                                LargeInteger  oaccountExpires = (LargeInteger )childProperties["accountExpires" ].Value;
                                accountExpires = ConvertAccountExpiresToDate(oaccountExpires);
                                //find out whether the account has expired
                                hasExpired = IsAccountExpired(accountExpires);
                                userObj.AccountExpires = accountExpires.ToShortDateString();
                            }
                            userObj.UserAccountControl = childProperties["userAccountControl"][0].ToString();
                            groupMembers.Add(userObj);
                        }
                        else
                        {
                             //You'll need to decide if you want to capture the Service Acount info.
                        }
                    }
                    else
                    {
                        //RECURSE - Look up all the members of the security group just found. 
                        GetUsersInGroup(childProperties, groupMembers, filter); // entry is not declared anywhere  
                    }
                }
            }
        }
        return  groupMembers;
    }
    
    

Posted in Active Directory, Microsoft .net | Tagged: , , , , | Comments Off on Fetch users from AD using a security group as point of entry

 
%d bloggers like this: