File: System\DirectoryServices\AccountManagement\AD\TokenGroupsSet.cs
Web Access
Project: src\src\runtime\src\libraries\System.DirectoryServices.AccountManagement\src\System.DirectoryServices.AccountManagement.csproj (System.DirectoryServices.AccountManagement)
// 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;
using System.Text;

namespace System.DirectoryServices.AccountManagement
{
    internal sealed class TokenGroupSet : ResultSet
    {
        internal TokenGroupSet(
                             string userDN,
                             ADStoreCtx storeCtx,
                             bool readDomainGroups)
        {
            _principalDN = userDN;
            _storeCtx = storeCtx;
            _attributeToQuery = readDomainGroups ? "tokenGroups" : "tokenGroupsGlobalAndUniversal";

            GlobalDebug.WriteLineIf(GlobalDebug.Info,
                                    "TokenGroupSet",
                                    "TokenGroupSet: userDN={0}",
                                    userDN);
        }

        private readonly string _principalDN;
        private readonly ADStoreCtx _storeCtx;

        private bool _atBeginning = true;
        private DirectoryEntry _current; // current member of the group (or current group of the user)

        private IEnumerator _tokenGroupsEnum;

        private SecurityIdentifier _currentSID;
        private bool _disposed;

        private readonly string _attributeToQuery;

        // 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 (_currentSID != null)
                {
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "TokenGroupSet", "CurrentAsPrincipal: using current");

                    string SidBindingString = $"<SID={Utils.SecurityIdentifierToLdapHexBindingString(_currentSID)}>";

                    DirectoryEntry currentDE = SDSUtils.BuildDirectoryEntry(
                                                BuildPathFromDN(SidBindingString),
                                                _storeCtx.Credentials,
                                                _storeCtx.AuthTypes);

                    _storeCtx.InitializeNewDirectoryOptions(currentDE);
                    _storeCtx.LoadDirectoryEntryAttributes(currentDE);

                    return ADUtils.DirectoryEntryAsPrincipal(currentDE, _storeCtx);
                }

                return null;
            }
        }

        // 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()
        {
            if (_atBeginning)
            {
                Debug.Assert(_principalDN != null);

                _current = SDSUtils.BuildDirectoryEntry(
                                            BuildPathFromDN(_principalDN),
                                            _storeCtx.Credentials,
                                            _storeCtx.AuthTypes);

                _current.RefreshCache(new string[] { _attributeToQuery });

                _tokenGroupsEnum = _current.Properties[_attributeToQuery].GetEnumerator();

                _atBeginning = false;
            }

            GlobalDebug.WriteLineIf(GlobalDebug.Info, "TokenGroupSet", "MoveNextLocal: returning primary group {0}", _current.Path);

            if (_tokenGroupsEnum.MoveNext())
            {
                // Got a member from this group (or, got a group of which we're a member).
                // Create a DirectoryEntry for it.

                byte[] sid = (byte[])_tokenGroupsEnum.Current;
                _currentSID = new SecurityIdentifier(sid, 0);
                GlobalDebug.WriteLineIf(GlobalDebug.Info, "TokenGroupSet", "MoveNextLocal: got a value from the enumerator: {0}", _currentSID.ToString());

                return true;
            }

            return false;
        }

        // 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()
        {
            if (_atBeginning)
                return;

            _tokenGroupsEnum.Reset();
        }

        // IDisposable implementation
        public override void Dispose()
        {
            try
            {
                if (!_disposed)
                {
                    _current?.Dispose();

                    _disposed = true;
                }
            }
            finally
            {
                base.Dispose();
            }
        }

        private string BuildPathFromDN(string dn)
        {
            string userSuppliedServername = _storeCtx.UserSuppliedServerName;

            UnsafeNativeMethods.Pathname pathCracker = new UnsafeNativeMethods.Pathname();
            UnsafeNativeMethods.IADsPathname pathName = (UnsafeNativeMethods.IADsPathname)pathCracker;
            pathName.EscapedMode = 2 /* ADS_ESCAPEDMODE_ON */;
            pathName.Set(dn, 4 /* ADS_SETTYPE_DN */);
            string escapedDn = pathName.Retrieve(7 /* ADS_FORMAT_X500_DN */);

            if (userSuppliedServername.Length > 0)
                return "LDAP://" + _storeCtx.UserSuppliedServerName + "/" + escapedDn;
            else
                return "LDAP://" + escapedDn;
        }
    }
}