Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find Recursive Group Membership (Active Directory) using C#

I am looking to get a list of all of the groups that a user is a member of in Active Directory, both explicitly listed in the memberOf property list as well as implicitly through nested group membership. For example, if I examine UserA and UserA is a part of GroupA and GroupB, I also want to list GroupC if GroupB is a member of GroupC.

To give you a bit more insight into my application, I will be doing this on a limited basis. Basically, I want a security check occasionally that will list these additional memberships. I will want to differentiate the two but that shouldn't be hard.

My problem is that I have not found an efficient way to make this query work. The standard text on Active Directory (This CodeProject Article) shows a way to do this that is basically a recursive lookup. That seems terribly inefficient. Even in my small domain, a user might have 30+ group memberships. That means 30+ calls to Active Directory for one user.

I've looked into the following LDAP code to get all of the memberOf entries at once:

(memberOf:1.2.840.113556.1.4.1941:={0}) 

where {0} would be my LDAP path (ex: CN=UserA,OU=Users,DC=foo,DC=org). However, it does not return any records. The downside of this method, even if it worked, would be that I wouldn't know which group was explicit and which was implicit.

That is what I have so far. I would like to know if there is a better way than the CodeProject article and, if so, how that could be accomplished (actual code would be wonderful). I am using .NET 4.0 and C#. My Active Directory is at a Windows 2008 functional level (it isn't R2 yet).

like image 944
IAmTimCorey Avatar asked Jun 06 '11 13:06

IAmTimCorey


2 Answers

Thirst thanks for this an interesting question.

Next, just a correction, you say :

I've looked into the following LDAP code to get all of the memberOf entries at once:

(memberOf:1.2.840.113556.1.4.1941:={0}) 

You don't make it work. I remember I make it work when I learnt about its existence, but it was in an LDIFDE.EXE filter. So I apply it to ADSI in C# and it's still working. There were too much parenthesis in the sample I took from Microsoft, but it was working (source in AD Search Filter Syntax).

According to your remark concerning the fact that we don't know if a user explicitly belongs to the group I add one more request. I know this is not very good, but it's the best I'am abable to do.

static void Main(string[] args) {   /* Connection to Active Directory    */   DirectoryEntry deBase = new DirectoryEntry("LDAP://WM2008R2ENT:389/dc=dom,dc=fr");     /* To find all the groups that "user1" is a member of :    * Set the base to the groups container DN; for example root DN (dc=dom,dc=fr)     * Set the scope to subtree    * Use the following filter :    * (member:1.2.840.113556.1.4.1941:=cn=user1,cn=users,DC=x)    */   DirectorySearcher dsLookFor = new DirectorySearcher(deBase);   dsLookFor.Filter = "(member:1.2.840.113556.1.4.1941:=CN=user1 Users,OU=MonOu,DC=dom,DC=fr)";   dsLookFor.SearchScope = SearchScope.Subtree;   dsLookFor.PropertiesToLoad.Add("cn");    SearchResultCollection srcGroups = dsLookFor.FindAll();    /* Just to know if user is explicitly in group    */   foreach (SearchResult srcGroup in srcGroups)   {     Console.WriteLine("{0}", srcGroup.Path);      foreach (string property in srcGroup.Properties.PropertyNames)     {       Console.WriteLine("\t{0} : {1} ", property, srcGroup.Properties[property][0]);     }      DirectoryEntry aGroup = new DirectoryEntry(srcGroup.Path);     DirectorySearcher dsLookForAMermber = new DirectorySearcher(aGroup);     dsLookForAMermber.Filter = "(member=CN=user1 Users,OU=MonOu,DC=dom,DC=fr)";     dsLookForAMermber.SearchScope = SearchScope.Base;     dsLookForAMermber.PropertiesToLoad.Add("cn");      SearchResultCollection memberInGroup = dsLookForAMermber.FindAll();     Console.WriteLine("Find the user {0}", memberInGroup.Count);    }    Console.ReadLine(); } 

In my test tree this give :

LDAP://WM2008R2ENT:389/CN=MonGrpSec,OU=MonOu,DC=dom,DC=fr adspath : LDAP://WM2008R2ENT:389/CN=MonGrpSec,OU=MonOu,DC=dom,DC=fr cn : MonGrpSec Find the user 1  LDAP://WM2008R2ENT:389/CN=MonGrpDis,OU=ForUser1,DC=dom,DC=fr adspath : LDAP://WM2008R2ENT:389/CN=MonGrpDis,OU=ForUser1,DC=dom,DC=fr cn : MonGrpDis Find the user 1  LDAP://WM2008R2ENT:389/CN=MonGrpPlusSec,OU=ForUser1,DC=dom,DC=fr adspath : LDAP://WM2008R2ENT:389/CN=MonGrpPlusSec,OU=ForUser1,DC=dom,DC=fr cn : MonGrpPlusSec Find the user 0  LDAP://WM2008R2ENT:389/CN=MonGrpPlusSecUniv,OU=ForUser1,DC=dom,DC=fr adspath : LDAP://WM2008R2ENT:389/CN=MonGrpPlusSecUniv,OU=ForUser1,DC=dom,DC=fr cn : MonGrpPlusSecUniv Find the user 0 

(edited) '1.2.840.113556.1.4.1941' is not working in W2K3 SP1, it begins to work with SP2. I presume it's the same with W2K3 R2. It's supposed to work on W2K8. I test here with W2K8R2. I'll soon be able to test this on W2K8.

like image 68
JPBlanc Avatar answered Sep 30 '22 00:09

JPBlanc


If there is no way other than recursive calls (and I don't believe there is) then at least you can let the framework do the work for you: see the UserPrincipal.GetAuthorizationGroups method (in the System.DirectoryServices.AccountManagement namespace and introduced in .Net 3.5)

This method searches all groups recursively and returns the groups in which the user is a member. The returned set may also include additional groups that system would consider the user a member of for authorization purposes.

Compare with the results of GetGroups ("Returns a collection of group objects that specify the groups of which the current principal is a member") to see whether the membership is explicit or implicit.

like image 40
stuartd Avatar answered Sep 30 '22 02:09

stuartd