2

I have 2 domains A and B. I have a group in B "GroupInB" which has a user "UserInB"( In future, there can be many users in that group). In the domain A, I have created a group "GroupInA" and then added "GroupInB" to "GroupInA". (This causes a ForeignSecurityPrincipal for GroupB). And also I have added a user "UserInA" in "GroupA".

Now the problem is, I want to read all the users in "GroupA". I expect the result to be

  1. UserInA
  2. UserInB

But when trying to read the users from DirectoryEntry, all I get is

  1. UserInA
  2. GroupInB

Is there any way that i can get the users from "GroupInB" as well ? :(

Shrikey
  • 858
  • 3
  • 11
  • 34
  • Please check this link: https://stackoverflow.com/questions/3665487/get-members-of-an-active-directory-group-recursively-i-e-including-subgroups! – Am_I_Helpful Jan 24 '19 at 09:41
  • @Am_I_Helpful : The link that u pointed helps in searching for the groups in the same domain. In my case, GroupB is added from another domain. I can read the GroupB's name but not the users in it. – Shrikey Jan 24 '19 at 10:12
  • In that case, delegate appropriate permission for the user who can read group-membership of users in another domain too, and then follow the code as highlighted in my link above. – Am_I_Helpful Jan 24 '19 at 10:15
  • @Am_I_Helpful : I am not sure of how to assign the permission, but the problem in this case is "GroupB" would not be a "group" or "user". It will be a "foreignSecurityPrincipal". And cant extract members from it.. Tried the similar approach already with no luck :( – Shrikey Jan 24 '19 at 10:20

1 Answers1

4

I wrote an article a little while ago about getting all the members of a group: Find all the members of a group

I included a part about finding users from external trusted domains, which show up as Foreign Security Principals, however it seems I forgot about this specific use case: where a group from the foreign domain is the member. So I've updated the code in my article and included it below.

It is a little bit complicated since the Foreign Security Principal has the SID of the object on the external domain, but to bind to an object using the SID, you have to use the DNS name of the domain. So we first need to create a mapping of domain SIDs and DNS names for all the trusted domains. However, if you are running this in only one environment, you could always hard-code a list of domains to speed this up.

This is the code. If you pass true for the recursive parameter, it will expand all the member groups.

public static IEnumerable<string> GetGroupMemberList(DirectoryEntry group, bool recursive = false, Dictionary<string, string> domainSidMapping = null) {
    var members = new List<string>();

    group.RefreshCache(new[] { "member", "canonicalName" });

    if (domainSidMapping == null) {
        //Find all the trusted domains and create a dictionary that maps the domain's SID to its DNS name
        var groupCn = (string) group.Properties["canonicalName"].Value;
        var domainDns = groupCn.Substring(0, groupCn.IndexOf("/", StringComparison.Ordinal));

        var domain = Domain.GetDomain(new DirectoryContext(DirectoryContextType.Domain, domainDns));
        var trusts = domain.GetAllTrustRelationships();

        domainSidMapping = new Dictionary<string, string>();

        foreach (TrustRelationshipInformation trust in trusts) {
            using (var trustedDomain = new DirectoryEntry($"LDAP://{trust.TargetName}")) {
                try {
                    trustedDomain.RefreshCache(new [] {"objectSid"});
                    var domainSid = new SecurityIdentifier((byte[]) trustedDomain.Properties["objectSid"].Value, 0).ToString();
                    domainSidMapping.Add(domainSid, trust.TargetName);
                } catch (Exception e) {
                    //This can happen if you're running this with credentials
                    //that aren't trusted on the other domain or if the domain
                    //can't be contacted
                    throw new Exception($"Can't connect to domain {trust.TargetName}: {e.Message}", e);
                }
            }
        }
    }

    while (true) {
        var memberDns = group.Properties["member"];
        foreach (string member in memberDns) {
            using (var memberDe = new DirectoryEntry($"LDAP://{member.Replace("/", "\\/")}")) {
                memberDe.RefreshCache(new[] { "objectClass", "msDS-PrincipalName", "cn" });

                if (recursive && memberDe.Properties["objectClass"].Contains("group")) {
                    members.AddRange(GetGroupMemberList(memberDe, true, domainSidMapping));
                } else if (memberDe.Properties["objectClass"].Contains("foreignSecurityPrincipal")) {
                    //User is on a trusted domain
                    var foreignUserSid = memberDe.Properties["cn"].Value.ToString();
                    //The SID of the domain is the SID of the user minus the last block of numbers
                    var foreignDomainSid = foreignUserSid.Substring(0, foreignUserSid.LastIndexOf("-"));
                    if (domainSidMapping.TryGetValue(foreignDomainSid, out var foreignDomainDns)) {
                        using (var foreignMember = new DirectoryEntry($"LDAP://{foreignDomainDns}/<SID={foreignUserSid}>")) {
                            foreignMember.RefreshCache(new[] { "msDS-PrincipalName", "objectClass" });
                            if (recursive && foreignMember.Properties["objectClass"].Contains("group")) {
                                members.AddRange(GetGroupMemberList(foreignMember, true, domainSidMapping));
                            } else {
                                members.Add(foreignMember.Properties["msDS-PrincipalName"].Value.ToString());
                            }
                        }
                    } else {
                        //unknown domain
                        members.Add(foreignUserSid);
                    }
                } else {
                    var username = memberDe.Properties["msDS-PrincipalName"].Value.ToString();
                    if (!string.IsNullOrEmpty(username)) {
                        members.Add(username);
                    }
                }
            }
        }

        if (memberDns.Count == 0) break;

        try {
            group.RefreshCache(new[] {$"member;range={members.Count}-*"});
        } catch (COMException e) {
            if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
                break;
            }
            throw;
        }
    }
    return members;
}
Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84