|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices;
using System.Security.Principal;
namespace System.DirectoryServices.AccountManagement
{
internal class ADDNLinkedAttrSet : BookmarkableResultSet
{
// This class can be used to either enumerate the members of a group, or the groups
// to which a principal belongs. If being used to enumerate the members of a group:
// * groupDN --- the DN of the group we're enumerating
// * members --- array of enumerators containing the DNs of the members of the group we're enumerating (the "member" attribute)
// * primaryGroupDN --- should be null
// * recursive --- whether or not to recursively enumerate group membership
//
// If being used to enumerate the groups to which a principal belongs:
// * groupDN --- the DN of the principal (i.e., the user)
// * members --- the DNs of the groups to which that principal belongs (e.g, the "memberOf" attribute)
// * primaryGroupDN --- the DN of the principal's primary group (constructed from the "primaryGroupID" attribute)
// * recursive --- should be false
//
// Note that the variables in this class are generally named in accord with the "enumerating the members
// of a group" case.
//
// It is assumed that recursive enumeration will only be performed for the "enumerating the members of a group"
// case, not the "groups to which a principal belongs" case, thus, this.recursive == true implies the former
// (but this.recursive == false could imply either case).
internal ADDNLinkedAttrSet(
string groupDN,
IEnumerable[] members,
string primaryGroupDN,
DirectorySearcher primaryGroupMembersSearcher,
bool recursive,
ADStoreCtx storeCtx)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info,
"ADDNLinkedAttrSet",
"ADDNLinkedAttrSet: groupDN={0}, primaryGroupDN={1}, recursive={2}, PG queryFilter={3}, PG queryBase={4}",
groupDN,
primaryGroupDN ?? "NULL",
recursive,
(primaryGroupMembersSearcher != null ? primaryGroupMembersSearcher.Filter : "NULL"),
(primaryGroupMembersSearcher != null ? primaryGroupMembersSearcher.SearchRoot.Path : "NULL"));
_groupsVisited.Add(groupDN); // so we don't revisit it
_recursive = recursive;
_storeCtx = storeCtx;
_originalStoreCtx = storeCtx;
if (null != members)
{
foreach (IEnumerable enumerator in members)
{
_membersQueue.Enqueue(enumerator);
_originalMembers.Enqueue(enumerator);
}
}
_members = null;
_currentMembersSearcher = null;
_primaryGroupDN = primaryGroupDN;
if (primaryGroupDN == null)
_returnedPrimaryGroup = true; // so we don't bother trying to return the primary group
_primaryGroupMembersSearcher = primaryGroupMembersSearcher;
_expansionMode = ExpansionMode.Enum;
_originalExpansionMode = _expansionMode;
}
internal ADDNLinkedAttrSet(
string groupDN,
DirectorySearcher[] membersSearcher,
string primaryGroupDN,
DirectorySearcher primaryGroupMembersSearcher,
bool recursive,
ADStoreCtx storeCtx)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info,
"ADDNLinkedAttrSet",
"ADDNLinkedAttrSet: groupDN={0}, primaryGroupDN={1}, recursive={2}, M queryFilter={3}, M queryBase={4}, PG queryFilter={5}, PG queryBase={6}",
groupDN,
primaryGroupDN ?? "NULL",
recursive,
(membersSearcher != null ? membersSearcher[0].Filter : "NULL"),
(membersSearcher != null ? membersSearcher[0].SearchRoot.Path : "NULL"),
(primaryGroupMembersSearcher != null ? primaryGroupMembersSearcher.Filter : "NULL"),
(primaryGroupMembersSearcher != null ? primaryGroupMembersSearcher.SearchRoot.Path : "NULL"));
_groupsVisited.Add(groupDN); // so we don't revisit it
_recursive = recursive;
_storeCtx = storeCtx;
_originalStoreCtx = storeCtx;
_members = null;
_originalMembers = null;
_membersEnum = null;
_primaryGroupDN = primaryGroupDN;
if (primaryGroupDN == null)
_returnedPrimaryGroup = true; // so we don't bother trying to return the primary group
if (null != membersSearcher)
{
foreach (DirectorySearcher ds in membersSearcher)
{
_memberSearchersQueue.Enqueue(ds);
_memberSearchersQueueOriginal.Enqueue(ds);
}
}
_currentMembersSearcher = null;
_primaryGroupMembersSearcher = primaryGroupMembersSearcher;
_expansionMode = ExpansionMode.ASQ;
_originalExpansionMode = _expansionMode;
}
// Return the principal we're positioned at as a Principal object.
// Need to use our StoreCtx's GetAsPrincipal to convert the native object to a Principal
internal override object CurrentAsPrincipal
{
get
{
if (this.current != null)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "CurrentAsPrincipal: using current");
if (this.current is DirectoryEntry)
return ADUtils.DirectoryEntryAsPrincipal((DirectoryEntry)this.current, _storeCtx);
else
{
return ADUtils.SearchResultAsPrincipal((SearchResult)this.current, _storeCtx, null);
}
}
else
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "CurrentAsPrincipal: using currentForeignPrincipal");
Debug.Assert(_currentForeignPrincipal != null);
return _currentForeignPrincipal;
}
}
}
// Advance the enumerator to the next principal in the result set, pulling in additional pages
// of results (or ranges of attribute values) as needed.
// Returns true if successful, false if no more results to return.
internal override bool MoveNext()
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Entering MoveNext");
_atBeginning = false;
bool needToRetry;
bool f = false;
do
{
needToRetry = false;
// reset our found state. If we are restarting the loop we don't have a current principal yet.
f = false;
if (!_returnedPrimaryGroup)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNext: trying PrimaryGroup DN");
f = MoveNextPrimaryGroupDN();
}
if (!f)
{
if (_expansionMode == ExpansionMode.ASQ)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNext: trying member searcher");
f = MoveNextMemberSearcher();
}
else
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNext: trying member enum");
f = MoveNextMemberEnum();
}
}
if (!f)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNext: trying foreign");
f = MoveNextForeign(ref needToRetry);
}
if (!f)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNext: trying primary group search");
f = MoveNextQueryPrimaryGroupMember();
}
}
while (needToRetry);
return f;
}
private bool MoveNextPrimaryGroupDN()
{
// Do the special primary group ID processing if we haven't yet returned the primary group.
Debug.Assert(_primaryGroupDN != null);
this.current = SDSUtils.BuildDirectoryEntry(
BuildPathFromDN(_primaryGroupDN),
_storeCtx.Credentials,
_storeCtx.AuthTypes);
_storeCtx.InitializeNewDirectoryOptions((DirectoryEntry)this.current);
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: returning primary group {0}", ((DirectoryEntry)this.current).Path);
_currentForeignDE = null;
_currentForeignPrincipal = null;
_returnedPrimaryGroup = true;
return true;
}
private bool GetNextSearchResult()
{
bool memberFound = false;
do
{
if (_currentMembersSearcher == null)
{
Debug.Assert(_memberSearchersQueue != null);
if (_memberSearchersQueue.Count == 0)
{
// We are out of searchers in the queue.
return false;
}
else
{
// Remove the next searcher from the queue and place it in the current search variable.
_currentMembersSearcher = _memberSearchersQueue.Dequeue();
_memberSearchResults = _currentMembersSearcher.FindAll();
Debug.Assert(_memberSearchResults != null);
_memberSearchResultsEnumerator = _memberSearchResults.GetEnumerator();
}
}
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextQueryMember: have a searcher");
memberFound = _memberSearchResultsEnumerator.MoveNext();
// The search is complete.
// Dispose the searcher and search results.
if (!memberFound)
{
_currentMembersSearcher.Dispose();
_currentMembersSearcher = null;
_memberSearchResults.Dispose();
_memberSearchResults = null;
}
} while (!memberFound);
return memberFound;
}
private bool MoveNextMemberSearcher()
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Entering MoveNextMemberSearcher");
bool needToRetry = false;
bool f = false;
do
{
f = GetNextSearchResult();
needToRetry = false;
if (f)
{
SearchResult currentSR = (SearchResult)_memberSearchResultsEnumerator.Current;
// Got a member from this group (or, got a group of which we're a member).
// Create a DirectoryEntry for it.
string memberDN = (string)currentSR.Properties["distinguishedName"][0];
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: got a value from the enumerator: {0}", memberDN);
// Make sure the member is a principal
if ((!ADUtils.IsOfObjectClass(currentSR, "group")) &&
(!ADUtils.IsOfObjectClass(currentSR, "user")) && // includes computer as well
(!ADUtils.IsOfObjectClass(currentSR, "foreignSecurityPrincipal")))
{
// We found a member, but it's not a principal type. Skip it.
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: not a principal, skipping");
needToRetry = true;
}
// If we're processing recursively, and the member is a group, we DON'T return it,
// but rather treat it as something to recursively visit later
// (unless we've already visited the group previously)
else if (_recursive && ADUtils.IsOfObjectClass(currentSR, "group"))
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: adding to groupsToVisit");
if (!_groupsVisited.Contains(memberDN) && !_groupsToVisit.Contains(memberDN))
_groupsToVisit.Add(memberDN);
// and go on to the next member....
needToRetry = true;
}
else if (_recursive && ADUtils.IsOfObjectClass(currentSR, "foreignSecurityPrincipal"))
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: foreign principal, adding to foreignMembers");
// If we haven't seen this FPO yet then add it to the seen user database.
if (!_usersVisited.ContainsKey(currentSR.Properties["distinguishedName"][0].ToString()))
{
// The FPO might represent a group, in which case we should recursively enumerate its
// membership. So save it off for later processing.
_foreignMembersCurrentGroup.Add(currentSR.GetDirectoryEntry());
_usersVisited.Add(currentSR.Properties["distinguishedName"][0].ToString(), true);
}
// and go on to the next member....
needToRetry = true;
}
else
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: using as current");
// Check to see if we have already seen this user during the enumeration
// If so then move on to the next user. If not then return it as current.
if (!_usersVisited.ContainsKey(currentSR.Properties["distinguishedName"][0].ToString()))
{
this.current = currentSR;
_currentForeignDE = null;
_currentForeignPrincipal = null;
_usersVisited.Add(currentSR.Properties["distinguishedName"][0].ToString(), true);
}
else
{
needToRetry = true;
}
}
}
else
{
// We reached the end of this group's membership. If we're not processing recursively,
// we're done. Otherwise, go on to the next group to visit.
// First create a DE that points to the group we want to expand, Using that as a search root run
// an ASQ search against member and start enumerating those results.
if (_recursive)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info,
"ADDNLinkedAttrSet",
"MoveNextMemberSearcher: recursive processing, groupsToVisit={0}",
_groupsToVisit.Count);
if (_groupsToVisit.Count > 0)
{
// Pull off the next group to visit
string groupDN = _groupsToVisit[0];
_groupsToVisit.RemoveAt(0);
_groupsVisited.Add(groupDN);
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: recursively processing {0}", groupDN);
// get the membership of this new group
DirectoryEntry groupDE = SDSUtils.BuildDirectoryEntry(BuildPathFromDN(groupDN), _storeCtx.Credentials, _storeCtx.AuthTypes);
_storeCtx.InitializeNewDirectoryOptions(groupDE);
// Queue up a searcher for the new group expansion.
DirectorySearcher ds = SDSUtils.ConstructSearcher(groupDE);
ds.Filter = "(objectClass=*)";
ds.SearchScope = SearchScope.Base;
ds.AttributeScopeQuery = "member";
ds.CacheResults = false;
_memberSearchersQueue.Enqueue(ds);
// and go on to the first member of this new group.
needToRetry = true;
}
}
}
}
while (needToRetry);
return f;
}
private bool GetNextEnum()
{
bool memberFound = false;
do
{
if (null == _members)
{
if (_membersQueue.Count == 0)
{
return false;
}
_members = _membersQueue.Dequeue();
_membersEnum = _members.GetEnumerator();
}
memberFound = _membersEnum.MoveNext();
if (!memberFound)
{
(_members as IDisposable)?.Dispose();
(_membersEnum as IDisposable)?.Dispose();
_members = null;
_membersEnum = null;
}
} while (!memberFound);
return memberFound;
}
private bool MoveNextMemberEnum()
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Entering MoveNextMemberEnum");
bool needToRetry = false;
bool disposeMemberDE = false;
bool f;
do
{
f = GetNextEnum();
needToRetry = false;
disposeMemberDE = false;
if (f)
{
DirectoryEntry memberDE = null;
try
{
// Got a member from this group (or, got a group of which we're a member).
// Create a DirectoryEntry for it.
string memberDN = (string)_membersEnum.Current;
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberEnum: got a value from the enumerator: {0}", memberDN);
memberDE = SDSUtils.BuildDirectoryEntry(
BuildPathFromDN(memberDN),
_storeCtx.Credentials,
_storeCtx.AuthTypes);
_storeCtx.InitializeNewDirectoryOptions(memberDE);
_storeCtx.LoadDirectoryEntryAttributes(memberDE);
// Make sure the member is a principal
if ((!ADUtils.IsOfObjectClass(memberDE, "group")) &&
(!ADUtils.IsOfObjectClass(memberDE, "user")) && // includes computer as well
(!ADUtils.IsOfObjectClass(memberDE, "foreignSecurityPrincipal")))
{
// We found a member, but it's not a principal type. Skip it.
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberEnum: not a principal, skipping");
needToRetry = true;
disposeMemberDE = true; //Since member is not principal we don't return it. So mark it for dispose.
}
// If we're processing recursively, and the member is a group, we DON'T return it,
// but rather treat it as something to recursively visit later
// (unless we've already visited the group previously)
else if (_recursive && ADUtils.IsOfObjectClass(memberDE, "group"))
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberEnum: adding to groupsToVisit");
if (!_groupsVisited.Contains(memberDN) && !_groupsToVisit.Contains(memberDN))
_groupsToVisit.Add(memberDN);
// and go on to the next member....
needToRetry = true;
disposeMemberDE = true; //Since recursive is set to true, we do not return groups. So mark it for dispose.
}
else if (_recursive && ADUtils.IsOfObjectClass(memberDE, "foreignSecurityPrincipal"))
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberEnum: foreign principal, adding to foreignMembers");
// If we haven't seen this FPO yet then add it to the seen user database.
if (!_usersVisited.ContainsKey(memberDE.Properties["distinguishedName"][0].ToString()))
{
// The FPO might represent a group, in which case we should recursively enumerate its
// membership. So save it off for later processing.
_foreignMembersCurrentGroup.Add(memberDE);
_usersVisited.Add(memberDE.Properties["distinguishedName"][0].ToString(), true);
disposeMemberDE = false; //We store the FPO DirectoryEntry objects for further processing. So do NOT dispose it.
}
// and go on to the next member....
needToRetry = true;
}
else
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberEnum: using as current");
// Check to see if we have already seen this user during the enumeration
// If so then move on to the next user. If not then return it as current.
if (!_usersVisited.ContainsKey(memberDE.Properties["distinguishedName"][0].ToString()))
{
this.current = memberDE;
_currentForeignDE = null;
_currentForeignPrincipal = null;
_usersVisited.Add(memberDE.Properties["distinguishedName"][0].ToString(), true);
disposeMemberDE = false; //memberDE will be set in the Principal object we return. So do NOT dispose it.
}
else
{
needToRetry = true;
}
}
}
finally
{
if (disposeMemberDE && memberDE != null)
{
//This means the constructed member is not used in the new principal
memberDE.Dispose();
}
}
}
else
{
// We reached the end of this group's membership. If we're not processing recursively,
// we're done. Otherwise, go on to the next group to visit.
if (_recursive)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info,
"ADDNLinkedAttrSet",
"MoveNextLocal: recursive processing, groupsToVisit={0}",
_groupsToVisit.Count);
if (_groupsToVisit.Count > 0)
{
// Pull off the next group to visit
string groupDN = _groupsToVisit[0];
_groupsToVisit.RemoveAt(0);
_groupsVisited.Add(groupDN);
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberEnum: recursively processing {0}", groupDN);
// get the membership of this new group
DirectoryEntry groupDE = SDSUtils.BuildDirectoryEntry(
BuildPathFromDN(groupDN),
_storeCtx.Credentials,
_storeCtx.AuthTypes);
_storeCtx.InitializeNewDirectoryOptions(groupDE);
// set up for the next round of enumeration
//Here a new DirectoryEntry object is created and passed
//to RangeRetriever object. Hence, configure
//RangeRetriever to dispose the DirEntry on its dispose.
_membersQueue.Enqueue(new RangeRetriever(groupDE, "member", true));
// and go on to the first member of this new group....
needToRetry = true;
}
}
}
}
while (needToRetry);
return f;
}
private void TranslateForeignMembers()
{
GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADDNLinkedAttrSet", "TranslateForeignMembers: Translating foreign members");
List<byte[]> sidList = new List<byte[]>(_foreignMembersCurrentGroup.Count);
// Foreach foreign principal retrieve the sid.
// If the SID is for a fake object we have to track it separately. If we were attempt to translate it
// it would fail and not be returned and we would lose it.
// Once we have a list of sids then translate them against the target store in one call.
foreach (DirectoryEntry de in _foreignMembersCurrentGroup)
{
// Get the SID of the foreign principal
if (de.Properties["objectSid"].Count == 0)
{
throw new PrincipalOperationException(SR.ADStoreCtxCantRetrieveObjectSidForCrossStore);
}
byte[] sid = (byte[])de.Properties["objectSid"].Value;
// What type of SID is it?
SidType sidType = Utils.ClassifySID(sid);
if (sidType == SidType.FakeObject)
{
//Add the foreign member DirectoryEntry to fakePrincipalMembers list for further translation
//This de will be disposed after completing the translation by another code block.
_fakePrincipalMembers.Add(de);
// It's a FPO for something like NT AUTHORITY\NETWORK SERVICE.
// There's no real store object corresponding to this FPO, so
// fake a Principal.
GlobalDebug.WriteLineIf(GlobalDebug.Info,
"ADDNLinkedAttrSet",
"TranslateForeignMembers: fake principal, SID={0}",
Utils.ByteArrayToString(sid));
}
else
{
GlobalDebug.WriteLineIf(GlobalDebug.Info,
"ADDNLinkedAttrSet",
"TranslateForeignMembers: standard principal, SID={0}",
Utils.ByteArrayToString(sid));
sidList.Add(sid);
//We do NOT need the Foreign member DirectoryEntry object once it has been translated and added to sidList.
//So disposing it off now
de.Dispose();
}
}
// This call will perform a bulk sid translate to the name + issuer domain.
_foreignMembersToReturn = new SidList(sidList, _storeCtx.DnsHostName, _storeCtx.Credentials);
// We have translated the sids so clear the group now.
_foreignMembersCurrentGroup.Clear();
}
private bool MoveNextForeign(ref bool outerNeedToRetry)
{
outerNeedToRetry = false;
bool needToRetry;
Principal foreignPrincipal;
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Entering MoveNextForeign");
do
{
needToRetry = false;
if (_foreignMembersCurrentGroup.Count > 0)
{
TranslateForeignMembers();
}
if (_fakePrincipalMembers.Count > 0)
{
foreignPrincipal = _storeCtx.ConstructFakePrincipalFromSID((byte[])_fakePrincipalMembers[0].Properties["objectSid"].Value);
_fakePrincipalMembers[0].Dispose();
_fakePrincipalMembers.RemoveAt(0);
}
else if ((_foreignMembersToReturn != null) && (_foreignMembersToReturn.Length > 0))
{
StoreCtx foreignStoreCtx;
SidListEntry foreignSid = _foreignMembersToReturn[0];
// sidIssuerName is null only if SID was not resolved
// return a unknown principal back
if (null == foreignSid.sidIssuerName)
{
// create and return the unknown principal if it is not yet present in usersVisited
if (_usersVisited.TryAdd(foreignSid.name, true))
{
byte[] sid = Utils.ConvertNativeSidToByteArray(foreignSid.pSid);
UnknownPrincipal unknownPrincipal = UnknownPrincipal.CreateUnknownPrincipal(_storeCtx.OwningContext, sid, foreignSid.name);
this.current = null;
_currentForeignDE = null;
_currentForeignPrincipal = unknownPrincipal;
// remove the current member
_foreignMembersToReturn.RemoveAt(0);
return true;
}
// remove the current member
_foreignMembersToReturn.RemoveAt(0);
needToRetry = true;
continue;
}
SidType sidType = Utils.ClassifySID(foreignSid.pSid);
if (sidType == SidType.RealObjectFakeDomain)
{
// This is a BUILTIN object. It's a real object on the store we're connected to, but LookupSid
// will tell us it's a member of the BUILTIN domain. Resolve it as a principal on our store.
GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADDNLinkedAttrSet", "MoveNextForeign: builtin principal");
foreignStoreCtx = _storeCtx;
}
else
{
ContextOptions remoteOptions = DefaultContextOptions.ADDefaultContextOption;
PrincipalContext remoteCtx = SDSCache.Domain.GetContext(foreignSid.sidIssuerName, _storeCtx.Credentials, remoteOptions);
foreignStoreCtx = remoteCtx.QueryCtx;
}
foreignPrincipal = foreignStoreCtx.FindPrincipalByIdentRef(
typeof(Principal),
UrnScheme.SidScheme,
(new SecurityIdentifier(Utils.ConvertNativeSidToByteArray(_foreignMembersToReturn[0].pSid), 0)).ToString(),
DateTime.UtcNow);
if (null == foreignPrincipal)
{
GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADDNLinkedAttrSet", "MoveNextForeign: no matching principal");
throw new PrincipalOperationException(SR.ADStoreCtxFailedFindCrossStoreTarget);
}
_foreignMembersToReturn.RemoveAt(0);
}
else
{
// We don't have any more foreign principals to return so start with the foreign groups
if (_foreignGroups.Count > 0)
{
outerNeedToRetry = true;
// Determine the domainFunctionalityMode of the foreign domain. If they are W2k or not a global group then we can't use ASQ.
if (_foreignGroups[0].Context.ServerInformation.OsVersion == DomainControllerMode.Win2k ||
_foreignGroups[0].GroupScope != GroupScope.Global)
{
_expansionMode = ExpansionMode.Enum;
return ExpandForeignGroupEnumerator();
}
else
{
_expansionMode = ExpansionMode.ASQ;
return ExpandForeignGroupSearcher();
}
}
else
{
// We are done with foreign principals and groups..
return false;
}
}
if (foreignPrincipal is GroupPrincipal)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextForeign: foreign member is a group");
// A group, need to recursively expand it (unless it's a fake group,
// in which case it is by definition empty and so contains nothing to expand, or unless
// we've already or will visit it).
// Postpone to later.
if (!foreignPrincipal.fakePrincipal)
{
string groupDN = (string)((DirectoryEntry)foreignPrincipal.UnderlyingObject).Properties["distinguishedName"].Value;
GlobalDebug.WriteLineIf(GlobalDebug.Info,
"ADDNLinkedAttrSet",
"MoveNextForeign: not a fake group, adding {0} to foreignGroups",
groupDN);
if (!_groupsVisited.Contains(groupDN) && !_groupsToVisit.Contains(groupDN))
{
_foreignGroups.Add((GroupPrincipal)foreignPrincipal);
}
else
{
foreignPrincipal.Dispose();
}
}
needToRetry = true;
continue;
}
else
{
// Not a group, nothing to recursively expand, so just return it.
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextForeign: using as currentForeignDE/currentForeignPrincipal");
DirectoryEntry foreignDE = (DirectoryEntry)foreignPrincipal.GetUnderlyingObject();
_storeCtx.LoadDirectoryEntryAttributes(foreignDE);
if (!_usersVisited.ContainsKey(foreignDE.Properties["distinguishedName"][0].ToString()))
{
_usersVisited.Add(foreignDE.Properties["distinguishedName"][0].ToString(), true);
this.current = null;
_currentForeignDE = null;
_currentForeignPrincipal = foreignPrincipal;
return true;
}
else
{
foreignPrincipal.Dispose();
}
needToRetry = true;
continue;
}
}
while (needToRetry);
return false;
}
private bool ExpandForeignGroupEnumerator()
{
Debug.Assert(_recursive);
GlobalDebug.WriteLineIf(GlobalDebug.Info,
"ADDNLinkedAttrSet",
"ExpandForeignGroupEnumerator: there are {0} foreignGroups",
_foreignGroups.Count);
GroupPrincipal foreignGroup = _foreignGroups[0];
_foreignGroups.RemoveAt(0);
// Since members of AD groups must be AD objects
Debug.Assert(foreignGroup.Context.QueryCtx is ADStoreCtx);
Debug.Assert(foreignGroup.UnderlyingObject is DirectoryEntry);
Debug.Assert(((DirectoryEntry)foreignGroup.UnderlyingObject).Path.StartsWith("LDAP:", StringComparison.Ordinal));
_storeCtx = (ADStoreCtx)foreignGroup.Context.QueryCtx;
//Here the foreignGroup object is removed from the foreignGroups collection.
//and not used anymore. Hence, configure RangeRetriever to dispose the DirEntry on its dispose.
_membersQueue.Enqueue(new RangeRetriever((DirectoryEntry)foreignGroup.UnderlyingObject, "member", true));
string groupDN = (string)((DirectoryEntry)foreignGroup.UnderlyingObject).Properties["distinguishedName"].Value;
_groupsVisited.Add(groupDN);
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "ExpandForeignGroupEnumerator: recursively processing {0}", groupDN);
return true;
}
private bool ExpandForeignGroupSearcher()
{
Debug.Assert(_recursive);
GlobalDebug.WriteLineIf(GlobalDebug.Info,
"ADDNLinkedAttrSet",
"ExpandForeignGroupSearcher: there are {0} foreignGroups",
_foreignGroups.Count);
GroupPrincipal foreignGroup = _foreignGroups[0];
_foreignGroups.RemoveAt(0);
// Since members of AD groups must be AD objects
Debug.Assert(foreignGroup.Context.QueryCtx is ADStoreCtx);
Debug.Assert(foreignGroup.UnderlyingObject is DirectoryEntry);
Debug.Assert(((DirectoryEntry)foreignGroup.UnderlyingObject).Path.StartsWith("LDAP:", StringComparison.Ordinal));
_storeCtx = (ADStoreCtx)foreignGroup.Context.QueryCtx;
// Queue up a searcher for the new group expansion.
DirectorySearcher ds = SDSUtils.ConstructSearcher((DirectoryEntry)foreignGroup.UnderlyingObject);
ds.Filter = "(objectClass=*)";
ds.SearchScope = SearchScope.Base;
ds.AttributeScopeQuery = "member";
ds.CacheResults = false;
_memberSearchersQueue.Enqueue(ds);
string groupDN = (string)((DirectoryEntry)foreignGroup.UnderlyingObject).Properties["distinguishedName"].Value;
_groupsVisited.Add(groupDN);
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "ExpandForeignGroupSearcher: recursively processing {0}", groupDN);
return true;
}
private bool MoveNextQueryPrimaryGroupMember()
{
bool f = false;
if (_primaryGroupMembersSearcher != null)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextQueryMember: have a searcher");
if (_queryMembersResults == null)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextQueryMember: issuing query");
_queryMembersResults = _primaryGroupMembersSearcher.FindAll();
Debug.Assert(_queryMembersResults != null);
_queryMembersResultEnumerator = _queryMembersResults.GetEnumerator();
}
f = _queryMembersResultEnumerator.MoveNext();
if (f)
{
this.current = (SearchResult)_queryMembersResultEnumerator.Current;
Debug.Assert(this.current != null);
_currentForeignDE = null;
_currentForeignPrincipal = null;
GlobalDebug.WriteLineIf(GlobalDebug.Info,
"ADDNLinkedAttrSet",
"MoveNextQueryMember: got a result, using as current {0}",
((SearchResult)this.current).Path);
}
}
return f;
}
// Resets the enumerator to before the first result in the set. This potentially can be an expensive
// operation, e.g., if doing a paged search, may need to re-retrieve the first page of results.
// As a special case, if the ResultSet is already at the very beginning, this is guaranteed to be
// a no-op.
internal override void Reset()
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Reset");
if (!_atBeginning)
{
_usersVisited.Clear();
_groupsToVisit.Clear();
string originalGroupDN = _groupsVisited[0];
_groupsVisited.Clear();
_groupsVisited.Add(originalGroupDN);
// clear the current enumerator
_members = null;
_membersEnum = null;
// replace all items in the queue with the originals and reset them.
if (null != _originalMembers)
{
_membersQueue.Clear();
foreach (IEnumerable ie in _originalMembers)
{
_membersQueue.Enqueue(ie);
IEnumerator enumerator = ie.GetEnumerator();
enumerator.Reset();
}
}
_expansionMode = _originalExpansionMode;
_storeCtx = _originalStoreCtx;
this.current = null;
if (_primaryGroupDN != null)
_returnedPrimaryGroup = false;
_foreignMembersCurrentGroup.Clear();
_fakePrincipalMembers.Clear();
_foreignMembersToReturn?.Clear();
_currentForeignPrincipal = null;
_currentForeignDE = null;
_foreignGroups.Clear();
_queryMembersResultEnumerator = null;
if (_queryMembersResults != null)
{
_queryMembersResults.Dispose();
_queryMembersResults = null;
}
if (null != _currentMembersSearcher)
{
_currentMembersSearcher.Dispose();
_currentMembersSearcher = null;
}
_memberSearchResultsEnumerator = null;
if (_memberSearchResults != null)
{
_memberSearchResults.Dispose();
_memberSearchResults = null;
}
if (null != _memberSearchersQueue)
{
foreach (DirectorySearcher ds in _memberSearchersQueue)
{
ds.Dispose();
}
_memberSearchersQueue.Clear();
if (null != _memberSearchersQueueOriginal)
{
foreach (DirectorySearcher ds in _memberSearchersQueueOriginal)
{
_memberSearchersQueue.Enqueue(ds);
}
}
}
_atBeginning = true;
}
}
internal override ResultSetBookmark BookmarkAndReset()
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Bookmarking");
ADDNLinkedAttrSetBookmark bookmark = new ADDNLinkedAttrSetBookmark();
bookmark.usersVisited = _usersVisited;
_usersVisited = new Dictionary<string, bool>();
bookmark.groupsToVisit = _groupsToVisit;
_groupsToVisit = new List<string>();
string originalGroupDN = _groupsVisited[0];
bookmark.groupsVisited = _groupsVisited;
_groupsVisited = new List<string>();
_groupsVisited.Add(originalGroupDN);
bookmark.expansionMode = _expansionMode;
// bookmark the current enumerators
bookmark.members = _members;
bookmark.membersEnum = _membersEnum;
// Clear the current enumerators for reset
_members = null;
_membersEnum = null;
// Copy all enumerators in the queue over to the bookmark queue.
if (null != _membersQueue)
{
bookmark.membersQueue = new Queue<IEnumerable>(_membersQueue.Count);
foreach (IEnumerable ie in _membersQueue)
{
bookmark.membersQueue.Enqueue(ie);
}
}
// Refill the original queue with the original enumerators and reset them
if (null != _membersQueue)
{
_membersQueue.Clear();
if (_originalMembers != null)
{
foreach (IEnumerable ie in _originalMembers)
{
_membersQueue.Enqueue(ie);
IEnumerator enumerator = ie.GetEnumerator();
enumerator.Reset();
}
}
}
bookmark.storeCtx = _storeCtx;
_expansionMode = _originalExpansionMode;
if (null != _currentMembersSearcher)
{
_currentMembersSearcher.Dispose();
_currentMembersSearcher = null;
}
_storeCtx = _originalStoreCtx;
bookmark.current = this.current;
bookmark.returnedPrimaryGroup = _returnedPrimaryGroup;
this.current = null;
if (_primaryGroupDN != null)
_returnedPrimaryGroup = false;
bookmark.foreignMembersCurrentGroup = _foreignMembersCurrentGroup;
bookmark.fakePrincipalMembers = _fakePrincipalMembers;
bookmark.foreignMembersToReturn = _foreignMembersToReturn;
bookmark.currentForeignPrincipal = _currentForeignPrincipal;
bookmark.currentForeignDE = _currentForeignDE;
_foreignMembersCurrentGroup = new List<DirectoryEntry>();
_fakePrincipalMembers = new List<DirectoryEntry>();
_currentForeignDE = null;
bookmark.foreignGroups = _foreignGroups;
_foreignGroups = new List<GroupPrincipal>();
bookmark.queryMembersResults = _queryMembersResults;
bookmark.queryMembersResultEnumerator = _queryMembersResultEnumerator;
_queryMembersResults = null;
_queryMembersResultEnumerator = null;
bookmark.memberSearchResults = _memberSearchResults;
bookmark.memberSearchResultsEnumerator = _memberSearchResultsEnumerator;
_memberSearchResults = null;
_memberSearchResultsEnumerator = null;
if (null != _memberSearchersQueue)
{
bookmark.memberSearcherQueue = new Queue<DirectorySearcher>(_memberSearchersQueue.Count);
foreach (DirectorySearcher ds in _memberSearchersQueue)
{
bookmark.memberSearcherQueue.Enqueue(ds);
}
}
if (null != _memberSearchersQueueOriginal)
{
_memberSearchersQueue.Clear();
foreach (DirectorySearcher ds in _memberSearchersQueueOriginal)
{
_memberSearchersQueue.Enqueue(ds);
}
}
bookmark.atBeginning = _atBeginning;
_atBeginning = true;
return bookmark;
}
internal override void RestoreBookmark(ResultSetBookmark bookmark)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Restoring from bookmark");
Debug.Assert(bookmark is ADDNLinkedAttrSetBookmark);
ADDNLinkedAttrSetBookmark adBookmark = (ADDNLinkedAttrSetBookmark)bookmark;
_usersVisited = adBookmark.usersVisited;
_groupsToVisit = adBookmark.groupsToVisit;
_groupsVisited = adBookmark.groupsVisited;
_storeCtx = adBookmark.storeCtx;
this.current = adBookmark.current;
_returnedPrimaryGroup = adBookmark.returnedPrimaryGroup;
_foreignMembersCurrentGroup = adBookmark.foreignMembersCurrentGroup;
_fakePrincipalMembers = adBookmark.fakePrincipalMembers;
_foreignMembersToReturn = adBookmark.foreignMembersToReturn;
_currentForeignPrincipal = adBookmark.currentForeignPrincipal;
_currentForeignDE = adBookmark.currentForeignDE;
_foreignGroups = adBookmark.foreignGroups;
_queryMembersResults?.Dispose();
_queryMembersResults = adBookmark.queryMembersResults;
_queryMembersResultEnumerator = adBookmark.queryMembersResultEnumerator;
_memberSearchResults = adBookmark.memberSearchResults;
_memberSearchResultsEnumerator = adBookmark.memberSearchResultsEnumerator;
_atBeginning = adBookmark.atBeginning;
_expansionMode = adBookmark.expansionMode;
// Replace enumerators
_members = adBookmark.members;
_membersEnum = adBookmark.membersEnum;
// Replace the enumerator queue elements
if (null != _membersQueue)
{
_membersQueue.Clear();
if (null != adBookmark.membersQueue)
{
foreach (IEnumerable ie in adBookmark.membersQueue)
{
_membersQueue.Enqueue(ie);
}
}
}
if (null != _memberSearchersQueue)
{
foreach (DirectorySearcher ds in _memberSearchersQueue)
{
ds.Dispose();
}
_memberSearchersQueue.Clear();
if (null != adBookmark.memberSearcherQueue)
{
foreach (DirectorySearcher ds in adBookmark.memberSearcherQueue)
{
_memberSearchersQueue.Enqueue(ds);
}
}
}
}
// IDisposable implementation
public override void Dispose()
{
try
{
if (!_disposed)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing");
if (_primaryGroupMembersSearcher != null)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing primaryGroupMembersSearcher");
_primaryGroupMembersSearcher.Dispose();
}
if (_queryMembersResults != null)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing queryMembersResults");
_queryMembersResults.Dispose();
}
if (_currentMembersSearcher != null)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing membersSearcher");
_currentMembersSearcher.Dispose();
}
if (_memberSearchResults != null)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing memberSearchResults");
_memberSearchResults.Dispose();
}
if (_memberSearchersQueue != null)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing memberSearchersQueue");
foreach (DirectorySearcher ds in _memberSearchersQueue)
{
ds.Dispose();
}
_memberSearchersQueue.Clear();
}
IDisposable disposableMembers = _members as IDisposable;
if (disposableMembers != null)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing members Enumerable");
disposableMembers.Dispose();
}
IDisposable disposableMembersEnum = _membersEnum as IDisposable;
if (disposableMembersEnum != null)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing membersEnum Enumerator");
disposableMembersEnum.Dispose();
}
if (_membersQueue != null)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing membersQueue");
foreach (IEnumerable enumerable in _membersQueue)
{
(enumerable as IDisposable)?.Dispose();
}
}
if (_foreignGroups != null)
{
foreach (GroupPrincipal gp in _foreignGroups)
{
gp.Dispose();
}
}
_disposed = true;
}
}
finally
{
base.Dispose();
}
}
//
//
//
private UnsafeNativeMethods.IADsPathname _pathCracker;
private readonly object _pathLock = new object();
private Dictionary<string, bool> _usersVisited = new Dictionary<string, bool>();
// The 0th entry in this list is always the DN of the original group/user whose membership we're querying
private List<string> _groupsVisited = new List<string>();
private List<string> _groupsToVisit = new List<string>();
protected object current; // current member of the group (or current group of the user)
private bool _returnedPrimaryGroup;
private readonly string _primaryGroupDN; // the DN of the user's PrimaryGroup (not included in this.members/originalMembers)
private readonly bool _recursive;
private readonly Queue<IEnumerable> _membersQueue = new Queue<IEnumerable>();
private IEnumerable _members; // the membership we're currently enumerating over
private readonly Queue<IEnumerable> _originalMembers = new Queue<IEnumerable>(); // the membership we started off with (before recursing)
private IEnumerator _membersEnum;
private ADStoreCtx _storeCtx;
private readonly ADStoreCtx _originalStoreCtx;
private bool _atBeginning = true;
private bool _disposed;
// foreign
// This contains a list of employees built while enumerating the current group. These are FSP objects in the current domain and need to
// be translated to find out the domain that holds the actual object.
private List<DirectoryEntry> _foreignMembersCurrentGroup = new List<DirectoryEntry>();
// List of objects from the group tha are actual fake group objects.
private List<DirectoryEntry> _fakePrincipalMembers = new List<DirectoryEntry>();
// list of SIDs + store that have been translated. These could be any principal object
private SidList _foreignMembersToReturn;
private Principal _currentForeignPrincipal;
private DirectoryEntry _currentForeignDE;
private List<GroupPrincipal> _foreignGroups = new List<GroupPrincipal>();
// members based on a query (used for users who are group members by virtue of their primaryGroupId pointing to the group)
private readonly DirectorySearcher _primaryGroupMembersSearcher;
private SearchResultCollection _queryMembersResults;
private IEnumerator _queryMembersResultEnumerator;
private DirectorySearcher _currentMembersSearcher;
private readonly Queue<DirectorySearcher> _memberSearchersQueue = new Queue<DirectorySearcher>();
private readonly Queue<DirectorySearcher> _memberSearchersQueueOriginal = new Queue<DirectorySearcher>();
private SearchResultCollection _memberSearchResults;
private IEnumerator _memberSearchResultsEnumerator;
private ExpansionMode _expansionMode;
private readonly ExpansionMode _originalExpansionMode;
private string BuildPathFromDN(string dn)
{
string userSuppliedServername = _storeCtx.UserSuppliedServerName;
if (null == _pathCracker)
{
lock (_pathLock)
{
if (null == _pathCracker)
{
UnsafeNativeMethods.Pathname pathNameObj = new UnsafeNativeMethods.Pathname();
_pathCracker = (UnsafeNativeMethods.IADsPathname)pathNameObj;
_pathCracker.EscapedMode = 2 /* ADS_ESCAPEDMODE_ON */;
}
}
}
_pathCracker.Set(dn, 4 /* ADS_SETTYPE_DN */);
string escapedDn = _pathCracker.Retrieve(7 /* ADS_FORMAT_X500_DN */);
if (userSuppliedServername.Length > 0)
return "LDAP://" + _storeCtx.UserSuppliedServerName + "/" + escapedDn;
else
return "LDAP://" + escapedDn;
}
}
internal enum ExpansionMode
{
Enum = 0,
ASQ = 1,
}
internal sealed class ADDNLinkedAttrSetBookmark : ResultSetBookmark
{
public Dictionary<string, bool> usersVisited;
public List<string> groupsToVisit;
public List<string> groupsVisited;
public IEnumerable members;
public IEnumerator membersEnum;
public Queue<IEnumerable> membersQueue;
public ADStoreCtx storeCtx;
public object current;
public bool returnedPrimaryGroup;
public List<DirectoryEntry> foreignMembersCurrentGroup;
public List<DirectoryEntry> fakePrincipalMembers;
public SidList foreignMembersToReturn;
public Principal currentForeignPrincipal;
public DirectoryEntry currentForeignDE;
public List<GroupPrincipal> foreignGroups;
public SearchResultCollection queryMembersResults;
public IEnumerator queryMembersResultEnumerator;
public SearchResultCollection memberSearchResults;
public IEnumerator memberSearchResultsEnumerator;
public bool atBeginning;
public ExpansionMode expansionMode;
public Queue<DirectorySearcher> memberSearcherQueue;
}
}
// #endif
|