Obtain the members of Domain Users of AD group according to the memberOf attribute

Posted by tapos on Sun, 20 Feb 2022 02:03:09 +0100

Background description

An LDAP Sync windows system application made by my department can upload user Directory Server data, including LDAP User and LDAP Group, to our products.

The application is developed with C# and uses C# system DirectoryServices. Protocols. DLL library (which provides a series of LDAP API s).

On my computer, the path of the DLL is: C: \ program files (x86) \ reference assemblies \ Microsoft \ framework \ NETFramework\v4. 7\System. DirectoryServices. Protocols. dll

You can use the dotPeek tool of Jetbrains to decompile the dll file and view the source code.

 

Problem description

When we sync members of an LDAP group, we use the memberOf attribute to return qualified LDAP users as members of the group. The LDAP filter is as follows:

(&(objectClass=*)(memberOf=cn=security_group_1,dc=test2019,dc=com)(|(mail=*)(proxyAddresses=*)))

However, AD has a special group: Domain Users. Generally, all LDAP users in this AD server belong to this group. However, there is no dn of Domain Users in the memberOf attribute of LDAP user.

As a result, the user wants to synchronize the Domain Users group, but cannot find any members.

 

problem analysis

There is a primary group in AD. the default is Domain Users.

By default, all LDAP users belong to the primary group through an attribute of the LDAP User: primaryGroupID, which is the Rid of the primary group.

Obtain all attributes of an LDAP User through powershell on AD server:

 Get-ADUser -Filter 'Name -eq "shayne2"' -properties *

Results obtained:

Then get the properties of Domain Users:

Get-ADGroup -Filter 'Name -eq "Domain Users"' -properties *

You can see its objectSid, and RID is the last number separated by "-".  

 

 

Problem solving

We can add the judgment of primaryGroupID to the LDAP filter for obtaining group member.

(&(objectClass=*)(|(memberOf=cn=domain users,cn=users,dc=test2019,dc=com)(primaryGroupID=513))(|(mail=*)(proxyAddresses=*)))

By default, the RID of Domain Users is 513, but in order to prevent users from modifying the Primary Group, we need to calculate the SID of the group and then calculate the RID.

How to get SID

Suppose that the retrieved information of each group is in an object of type SearchResultEntry, and the object name is entry.

This was achieved at the beginning:

if (entry.Attributes["objectSid"] != null)
{
    group.sid = new SecurityIdentifier((byte[])entry.Attributes["objectSid"][0], 0).ToString();
}

However, when the group is a group in Builtin, it will report an error and throw such an exception:

The reason is that string cannot be cast to byte [].

Read the source code and find the entry Attributes ["objectsid"] may return several types of values, string or byte [], or object.

public object this[int index]
{
    get
    {
        if (!this.isSearchResult)
          return this.List[index];
        if (!(this.List[index] is byte[] bytes))
          return this.List[index];
        try
        {
          return (object) DirectoryAttribute.utf8EncoderWithErrorDetection.GetString(bytes);
        }
        catch (ArgumentException ex)
        {
          return this.List[index];
        }
    }
    set
    {
        switch (value)
        {
          case null:
            throw new ArgumentNullException(nameof (value));
          case string _:
          case byte[] _:
          case Uri _:
            this.List[index] = value;
            break;
          default:
            throw new ArgumentException(Res.GetString("ValidValueType"), nameof (value));
        }
    }
}

However, it provides a method, GetValues(Type valuesType), which specifies the returned type:

    public object[] GetValues(Type valuesType)
    {
      if (valuesType == typeof (byte[]))
      {
        int count = this.List.Count;
        byte[][] numArray = new byte[count][];
        for (int index = 0; index < count; ++index)
        {
          if (this.List[index] is string)
            numArray[index] = DirectoryAttribute.encoder.GetBytes((string) this.List[index]);
          else
            numArray[index] = this.List[index] is byte[] ? (byte[]) this.List[index] : throw new NotSupportedException(Res.GetString("DirectoryAttributeConversion"));
        }
        return (object[]) numArray;
      }
      if (!(valuesType == typeof (string)))
        throw new ArgumentException(Res.GetString("ValidDirectoryAttributeType"), nameof (valuesType));
      int count1 = this.List.Count;
      string[] strArray = new string[count1];
      for (int index = 0; index < count1; ++index)
      {
        if (this.List[index] is string)
        {
          strArray[index] = (string) this.List[index];
        }
        else
        {
          if (!(this.List[index] is byte[]))
            throw new NotSupportedException(Res.GetString("DirectoryAttributeConversion"));
          strArray[index] = DirectoryAttribute.encoder.GetString((byte[]) this.List[index]);
        }
      }
      return (object[]) strArray;
    }

Therefore, you can specify the type when obtaining the SID and catch exceptions to prevent other problems later:

if (entry.Attributes[DSAConst.ATTR_NAME_GROUP_SID] != null)
    {
    try
    {
        group.sid = new SecurityIdentifier((byte[])entry.Attributes[DSAConst.ATTR_NAME_GROUP_SID].GetValues(typeof(byte[]))[0], 0).ToString();
    }
    catch (Exception ex)
    {
        logger.Error(String.Format("Get objectSid failed for group {0}, msg={1}", group.name, ex.Message));
    }
}

How to get RID based on SID

String rid = sid.Substring(sid.LastIndexOf("-") + 1);

 

Topics: C# ldap