Reputation: 878
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
But when trying to read the users from DirectoryEntry, all I get is
Is there any way that i can get the users from "GroupInB" as well ? :(
Upvotes: 3
Views: 3220
Reputation: 40988
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;
}
Upvotes: 4