Search code examples
c#active-directoryldap.net-5

Get LDAP TokenGroups using System.DirectoryServices.Protocols in .NET5


I'm porting some code from System.DirectoryServices to System.DirectoryServices.Protocols due to the need to run on linux.

The majority of the code is fine but this little piece that retrieves tokenGroups isn't coming across.

The original code reads:

using (var ldapObject = new DirectoryEntry(objectPath, username, password, AuthenticationTypes.Secure))
        {
            ldapObject.Options.Referral = ReferralChasingOption.All;
            ldapObject.RefreshCache(new string[] { "tokenGroups" });
            foreach (byte[] sid in ldapObject.Properties["tokenGroups"])
            {
                // Won't run on linux
                var groupSID = new System.Security.Principal.SecurityIdentifier(sid, 0).ToString();
                try
                {
                    var group = this.GetSecurityGroupBySID(groupSID);
                }
                catch (Exception ex)
                {
                    log.Warn($"Failed to get group with SID {groupSID}", ex);
                }
            }
        }

Where objectPath is of the form $"LDAP://{domainName}/{distinguishedName}"

My attempt looks like this:

public void GetExpandedGroups(string objectMail, LdapConnection ldapConnection)
        {
            var usersPath = "OU=active,OU=users,DC=path,DC=etc"; //Not real values
            var filter = $"(&(objectClass = user)(!userAccountControl:1.2.840.113556.1.4.803:= 2)(mail={objectMail}))";
            var request = new SearchRequest(usersPath, filter, SearchScope.Subtree, new string[] { "tokenGroups" });
            request.Controls.Add(new SecurityDescriptorFlagControl(SecurityMasks.Dacl | SecurityMasks.Group | SecurityMasks.Owner));

            var ldapObject = (SearchResponse)ldapConnection.SendRequest(request); // Error thrown here

            foreach (byte[] sid in ldapObject.Entries[0].Attributes["tokenGroups"])
            {
                // Do stuff...
            }
        }

Whenever this runs I get an error, thrown. The stack trace and error response are below:

   at System.DirectoryServices.Protocols.LdapConnection.<ConstructResponseAsync>d__57.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()
   at System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request, TimeSpan requestTimeout)
   at System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request)
   at LDAPTesting.Connectors.Protocols.GetExpandedGroups(String objectMail, LdapConnection ldapConnection) in C:\code\LDAPTesting\LDAPTesting\Connectors\Protocols.cs:line 62
   at LDAPTesting.Connectors.Protocols.GetGroups() in C:\code\LDAPTesting\LDAPTesting\Connectors\Protocols.cs:line 103
   at LDAPTesting.Program.Main(String[] args) in C:\code\LDAPTesting\LDAPTesting\Program.cs:line 17
00002120: SvcErr: DSID-031404CC, problem 5012 (DIR_ERROR), data 0

My thoughts are that I'm querying the wrong path but it looks to match the path that the original code requests on so I'm not sure what else I'm missing.


Solution

  • For anyone that comes along this, the solution is incredibly simple.

    The SearchRequest object needs to be of the scope Base. Really obvious when reading what the error message. So the line:

    var request = new SearchRequest(usersPath, filter, SearchScope.Subtree, new string[] { "tokenGroups" });
    

    Should instead read:

    var request = new SearchRequest(usersPath, filter, SearchScope.Base, new string[] { "tokenGroups" });
    

    I had a feeling that there would be an obvious and simple answer due to overlooking something and indeed there was!