Skip to main content

Query LDAP (Active Directory) and retrieve a list of the specified user group memberships (optimized for performance).

using System;
using System.DirectoryServices;

namespace LdapTools
{
    public static class LdapHelper
    {
        private const string LdapRootPath = "LDAP://DC=abc,DC=corp,DC=net";
        private const string LdapMemberOfProperty = "memberOf";
        private const string LdapPersonSearchFilter = "(&((&(objectCategory=person)(objectClass=user)))(sAMAccountName={0}))";

        /// <summary>
        /// Query LDAP and attempt to retrieve a list of the specified
        /// <paramref name="username"/>'s group memberships
        /// using the "memberOf" property. (note: optimized for performance).
        /// </summary>
        /// <param name="username">The LDAP user name.</param>
        /// <exception cref="ArgumentNullException">
        /// Thrown when the specified parameter for <paramref name="username"/>
        /// is null, empty, or contains only whitespace characters.
        /// </exception>
        /// <exception cref="NullReferenceException">
        /// Thrown when the LDAP <see cref="T:System.DirectoryServices.SearchResult"/>
        /// returns null.
        /// </exception>
        /// <returns>
        /// An <see cref="System.Array"/> of strings containing the
        /// <paramref name="username"/>'s group memberships.
        /// </returns>
        public static string[] GetUserGroups(string username)
        {
            if (string.IsNullOrWhiteSpace(username))
            {
                throw new ArgumentNullException("username",
                    "User name cannot be null, empty " +
                    "or contain only whitespace characters.");
            }

            // check username for domain prefix (e.g.: "DOMAIN\USERNAME" => "USERNAME"),
            // and remove if found:
            username = username.IndexOf('\\') == -1 ? username : username.Split('\\')[1];

            string[] groups = {};
            using (var de = new DirectoryEntry(LdapRootPath))
            {
                // ------------------------------------------------------------------------------------
                // Authentication Type Flags:
                // https://msdn.microsoft.com/en-us/library/system.directoryservices.authenticationtypes(v=vs.110).aspx
                // ------------------------------------------------------------------------------------
                // AuthenticationTypes.ReadonlyServer = writable server not required (serverless binding)
                // AuthenticationTypes.Secure         = request secure authentication (also the default)
                // AuthenticationTypes.FastBind       = query only base adsi interface for performance boost
                // ------------------------------------------------------------------------------------
                de.AuthenticationType =
                    AuthenticationTypes.ReadonlyServer |
                    AuthenticationTypes.Secure |
                    AuthenticationTypes.FastBind;

                using (var ds = new DirectorySearcher(de))
                {
                    ds.Filter = string.Format(LdapPersonSearchFilter, username);
                    ds.PropertiesToLoad.Add(LdapMemberOfProperty);
                    ds.SearchScope = SearchScope.Subtree;
                    SearchResult sr = ds.FindOne();
                    if (sr == null || sr.Properties == null)
                    {
                        throw new NullReferenceException(string.Format(
                            "Failed to find \"{0}\" in LDAP, or user does not " +
                            "have any associated group memberships.",
                            username)
                        );
                    }

                    int memberOfCount = sr.Properties[LdapMemberOfProperty].Count;
                    Array.Resize(ref groups, memberOfCount);
                    for (int i = 0; i <= memberOfCount - 1; i++)
                    {
                        string distinguishedName = sr.Properties[LdapMemberOfProperty][i].ToString();
                        int equalsIndex = distinguishedName.IndexOf("=", 1, StringComparison.Ordinal);
                        if (equalsIndex == -1)
                        {
                            break;
                        }

                        int commaIndex = distinguishedName.IndexOf(",", 1, StringComparison.Ordinal);
                        groups[i] = distinguishedName.Substring(equalsIndex + 1)
                            .Substring(0, commaIndex - equalsIndex - 1);
                    }
                }
            }

            return groups;
        }
    }
}