12

I've got a string that I'm fetching from LDAP for Active Directory group membership and I need to parse it to check if the user is a member of the AD group. Is there a class that can parse this for me?

Example:

CN=Foo Group Name,DC=mydomain,DC=com
abatishchev
  • 98,240
  • 88
  • 296
  • 433
Gabe Brown
  • 1,418
  • 3
  • 17
  • 22

7 Answers7

7

If you don't want to add additional dependencies and just want to parse the string..

This type of string can easily be parsed just using string.Split. To get the CN values, would be something like..

string[] split = "CN=Foo Group Name,DC=mydomain,DC=com".Split(',');
List<string> cnValues = new List<string>();
foreach(string pair in split){
    string[] keyValue=pair.Split('=');
    if(keyValue[0]=="CN")
       cnValues.Add(keyValue[1]);
}
markt
  • 5,126
  • 31
  • 25
  • 27
    For those reading back, this solution isn't enough for a production environment -- the [RFC](http://www.rfc-archive.org/getrfc.php?rfc=4514) specifies that values can be quoted and there are also character escaping rules. Also, splitting the string by commas with this format is *highly* perilous. – Jon Seigel Mar 23 '11 at 18:29
6

These are called distinguished names.

CodeProject has a parser project that appears to do what you need: http://www.codeproject.com/KB/IP/dnparser.aspx

spoulson
  • 21,335
  • 15
  • 77
  • 102
  • 1
    It's also been wrapped up into a github project and nuget package for easy access. https://github.com/picrap/DNParser/blob/master/README.md – John K Mar 02 '18 at 20:09
5

Besides, if you query the AD for a group members, you'll be able to compare all of the members' distinguishedName's directly without parsing code through the DirectoryEntry class of the System.DirectoryServices namespace.

Otherwise, I just don't know of such a class somewhere. =)

Hope this helps anyway somehow !

EDIT #1

Here's a link from which I have learned a lot working with the AD and the System.DirectoryServices namespace: Howto: (Almost) Everything In Active Directory via C#

I shall provide you with a sample code in a few days, if you still require it, where I will use the System.DirectoryServices.DirectorySearcher object class to retrieve the members of a group.

I hope this link will help you as it did for me! =)

EDIT #2

Here's the code sample I told you about. This should make it more efficient to query against the AD without having to work bakc and forth the AD.

public IList<string> GetMembers(string groupName) {
    if (string.IsNullOrEmpty(groupName))
        throw new ArgumentNullException("groupName");

    IList<string> members = new List<string>();

    DirectoryEntry root = new DirectoryEntry(@"LDAP://my.domain.com");
    DirectorySearcher searcher = new DirectorySearcher();
    searcher.SearchRoot = root;
    searcher.SearchScope = SearchScope.Subtree;
    searcher.PropertiesToLoad.Add("member");

    searcher.Filter = string.Format("(&(objectClass=group)(sAMAccountName={0}))", groupName);

    SearchResult result = searcher.FindOne();
    DirectoryEntry groupFound = result.GetDirectoryEntry();
    for (int index = 0; index < ((object[])groupFound.Properties["member"].Value).Length; ++index)
        members.Add((string)((object[])groupFound.Properties["member"].Value)[index]);

    return members;

}

Disclaimer : This code is provided as-is. I tested it on my local machine and it works perfectly fine. But since I had to retype it here because I couldn't just copy-paste it, I have perhaps made some mistakes while typing, which I wish didn't occur.

Alexander
  • 2,320
  • 2
  • 25
  • 33
Will Marcouiller
  • 23,773
  • 22
  • 96
  • 162
  • This worked pretty well for me, problem is that it's pretty expensive round-trip cost going back and forth with AD. Trying a way to fetch them all at once. – Gabe Brown Sep 30 '10 at 20:24
  • If you're using .NET 3.5 or can use the `System.Linq` namespace, you might perhaps be interested to take an eye out the Bart De Smet's LINQ to AD project on Codeplex. This allows you to use LINQ to query against the AD. Indeed, there's a bit of work to do to complete the library, but the most critical aspects are covered within his open source code. – Will Marcouiller Sep 30 '10 at 20:48
  • If you want immediate members of a group, call `DirectorySearcher` against the DN of the group and get its `members` attribute for a list of DNs. If you want nested members of groups within a group, get the `tokenGroups` attribute for a list of object SIDs. I wrote about this a while back: http://explodingcoder.com/blog/content/how-query-active-directory-security-group-membership – spoulson Sep 30 '10 at 22:11
  • Right ! The `DirectorySearcher` class is the one to use to query against the AD. Thanks for the mention @spoulson! Completely forgot about it for a few! =) – Will Marcouiller Oct 01 '10 at 00:00
3

To parse the DistinquishedName you have to pay attention to the escape characters. Here's a method that will parse the string correctly and return a list of key value pairs.

    public static List<KeyValuePair<string, string>> ParseDistinguishedName(string input)
    {
        int i = 0;
        int a = 0;
        int v = 0;
        var attribute = new char[50];
        var value = new char[200];
        var inAttribute = true;
        string attributeString, valueString;
        var names = new List<KeyValuePair<string, string>>();

        while (i < input.Length)
        {
            char ch = input[i++];
            switch(ch)
            {
                case '\\':
                    value[v++] = ch;
                    value[v++] = input[i++];
                    break;
                case '=':
                    inAttribute = false;
                    break;
                case ',':
                    inAttribute = true;
                    attributeString = new string(attribute).Substring(0, a);
                    valueString = new string(value).Substring(0, v);
                    names.Add(new KeyValuePair<string, string>(attributeString, valueString));
                    a = v = 0;
                    break;
                default:
                    if (inAttribute)
                    {
                        attribute[a++] = ch;
                    }
                    else
                    {
                        value[v++] = ch;
                    }
                    break;
            }
        }

        attributeString = new string(attribute).Substring(0, a);
        valueString = new string(value).Substring(0, v);
        names.Add(new KeyValuePair<string, string>(attributeString, valueString));
        return names;
    }

    static void Main(string[] args)
    {
        const string TestString = "CN=BY2STRAKRJOB2,OU=MSNStorage,OU=RESOURCE,OU=PRODUCTION,DC=phx,DC=gbl,STREET=street address,L=locality Name,C=Country Name,UID=user id,STUFF=\\,\\.\\+\"<>;\\=\\0A";

        var names = ParseDistinguishedName(TestString);
        foreach (var pair in names)
        {
            Console.WriteLine("{0} = {1}", pair.Key, pair.Value);
        }
    }
Rockfish
  • 109
  • 1
  • 2
  • 2
    Quite like this approach - though it doesn't trim whitespace from attribute names, and I had some issues with quoted values too e.g. O="VeriSign, Inc." - so I tweaked things slightly https://gist.github.com/davetransom/e9c58b96afa46d4c75a0 – Dave Transom Apr 10 '15 at 23:55
3
Using System.DirectoryServices;

namespace GetGroups
{
    public string GetGroupName(string LDAPGroupEntry)
    {
        // LDAPGroupEntry is in the form "LDAP://CN=Foo Group Name,DC=mydomain,DC=com"
        DirectoryEntry grp = new DirectoryEntry(LDAPGroupEntry);
        return grp.Properties["Name"].Value.ToString();
    }
}
animuson
  • 53,861
  • 28
  • 137
  • 147
1

I came here to see if we can parse "LDAP://ldap.company.com:389/ou=people,o=company" to protocol, port, baseDN and server FQDN. I tried System.Uri class it worked as excepted.

Vivek Raj
  • 459
  • 5
  • 16
0

To answer the parsing question, use PInvoke with DsGetRdnW. For code, see my answer to another question: https://stackoverflow.com/a/11091804/628981.

But it sounds like you're doing it wrong. First, get the SID for your target group:

string targetGroupName = //target group name;
DirectorySearcher dsTargetGroup = new DirectorySearcher();
dsTargetGroup.Filter = string.Format("(sAMAccountName={0})", targetGroupName);
SearchResult srTargetGroup = dsTargetGroup.FindOne();
DirectoryEntry deTargetGroup = srTargetGroup.GetDirectoryEntry();
byte[] byteSid = (byte[])deTargetGroup.Properties["objectSid"].Value;
SecurityIdentifier targetGroupSid = new SecurityIdentifier(byteSid, 0);

Then, it depends on what you have. If the user is running your app (or authenticated to your website/service), then enumerate the SIDs in the token. For example, in desktop apps use WindowsIdentity.GetCurrent().Groups. Otherwise, you'll need to get a DirectoryEntry for the user and then get the tokenAttributes attribute like spoulson suggested:

DirectoryEntry deTargetUser = //target user;
DirectorySearcher dsTargetUser = new DirectorySearcher(deTargetUser);
dsTargetUser.SearchScope = SearchScope.Base; //tokenGroups is a constructed attribute, so have to ask for it while performing a search
dsTargetUser.Filter = "(objectClass=*)"; //this is closest thing I can find to an always true filter
dsTargetUser.PropertiesToLoad.Add("tokenGroups");
SearchResult srTargetUser = dsTargetUser.FindOne();
foreach(byte[] byteGroupSid in srTargetUser.Properties["tokenGroups"])
{
    SecurityIdentifier groupSid = new SecurityIdentifier(byteGroupSid, 0);
    if(groupSid == targetGroupSid)
    {
        //success
    }
}

Just in case you need to get a DirectoryEntry from a SID, you can get the search string by:

public static string GetSIDSearchFilter(SecurityIdentifier sid)
{
    byte[] byteSid = new byte[sid.BinaryLength];
    sid.GetBinaryForm(byteSid, 0);
    return string.Format("(objectSid={0})", BuildFilterOctetString(byteSid));
}

public static string BuildFilterOctetString(byte[] bytes)
{
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < bytes.Length; i++)
    {
        sb.AppendFormat("\\{0}", bytes[i].ToString("X2"));
    }
    return sb.ToString();
}
Community
  • 1
  • 1
Sean Hall
  • 7,629
  • 2
  • 29
  • 44