Tuesday, June 30, 2009

C# class to validate User credentials on Active Directory (LDAP)

I have seen a lot of folks trying to build a SSO int(er|ra)net web application, and end up using Windows Authentication because its tough for them to write a AD connector. Trust me guys its not too hard, not with google around :).
Anyway here's yet another SSO implementation (happens to be mine :)).
Its fairly easy to use the class. Just 2 public static routines. One to validate a user's credential, and the other to retrieve all users from the AD. Part one is self explanatory. Part two was important for me because I do roles management (and no I dont use Membership Providers - its either too little or too much).

Here's the full implementation. Let me know if you have trouble using it. btw, a sample usage:
1. if (!String.IsNullOrEmpty(string errorMessage = ADConnector.AuthenticateUserPassword("LDAP://secure.company.com", "tech1", "tech1234")))
{
//blah blah blah
)
2. SearchResultCollection resColl = ADConnector.DumpAllUsers("LDAP://secure.comapy.com", "admin", "admin1234");


foreach (SearchResult res in resColl)

{

//If account is not disabled

if (!Convert.ToBoolean(Convert.ToInt32(res.Properties["userAccountControl"][0], System.Globalization.CultureInfo.InvariantCulture) & ADConnector.UF_DISABLED))

{

//blah blah blah

}

}

}


---------------------------------------------------------------------------------ADConnector.cs------------------------------------------------------------------------------------------------


namespace Utilities.Login.SSO

{

/// <summary>

/// <remarks>Developed by: Praveen Rangarajan

/// <CR1 date="10/12/2008">Included the functionality to retrieve all accounts from the AD</CR1>

/// <CR2 date="2/4/2009">Functionality to return "Locked-out", "Password expired", and "Account disabled" error message</CR2>

/// </remarks>

/// </summary>

public partial class ADConnector

{

public const int UF_DISABLED = 0x0002;

public const int UF_LOCKED = 0x0010;

public const int UF_EXPIRED = 0x800000;



/// <summary>

/// Authenticate the login credentials of the user on the AD.

/// </summary>

/// <param name="ldap">Url to the LDAP server alongwith the default DC settings. Example "LDAP://contoso.com, DC=contoso, DC=com"</param>

/// <param name="userName">Domain account of the user. Just the account name is sufficient</param>

/// <param name="password">User password</param>

/// <returns>

/// null If valid.

/// Non-empty string with the error message, if invalid.

/// </returns>

public static string AuthenticateUserPassword(string ldap, string userName, string password, string adminName, string adminPassword)

{

try

{

DirectoryEntry deSystem = new DirectoryEntry(ldap, userName, password);

DirectorySearcher s = new DirectorySearcher(deSystem, "SAMAccountName=" + userName,

new string[] { "userAccountControl" }

, SearchScope.Subtree);

SearchResult res;

if ((res = s.FindOne()) == null)

return "Username and/or Password incorrect";

if (Convert.ToBoolean(Convert.ToInt32(res.Properties["userAccountControl"][0]) & UF_DISABLED))

return "Account has been disabled. Please contact administrator for more details";

return null;

}

catch

{

//User validation returned exception. Now check for Password expired or Account locked out.

// Use the admin account (any account has LDAP query rights) to check for the above condition.


DirectoryEntry deSystem = new DirectoryEntry(ldap, adminName, adminPassword);

DirectorySearcher s = new DirectorySearcher(deSystem, "SAMAccountName=" + userName,

new string[] { "userAccountControl", "msDS-User-Account-Control-Computed" }

, SearchScope.Subtree);

SearchResult res;

if ((res = s.FindOne()) == null)

return "User not identified in AD";

if (Convert.ToBoolean(Convert.ToInt32(res.Properties["userAccountControl"][0]) & UF_DISABLED))

return "Account has been disabled. Please contact administrator for more details";

if (res.Properties.Contains("msDS-User-Account-Control-Computed") &&

Convert.ToBoolean(Convert.ToInt32(res.Properties["msDS-User-Account-Control-Computed"][0]) & UF_LOCKED))

return "Account locked-out. Please contact administrator for more details";

if (Convert.ToBoolean(Convert.ToInt32(res.Properties["userAccountControl"][0]) & UF_EXPIRED))

return "Password has expired. Please change your password before attempting to login again.";


return "Username and/or Password incorrect";

}

}


/// <summary>

/// Internal function to convert Large integer into its highpart and lowpart equivalents.

/// </summary>

/// <param name="largeInteger">Large integer object</param>

/// <returns></returns>

private static long LongFromLargeIntegerObject(object largeInteger)

{

System.Type type = largeInteger.GetType();

int highPart = (int)type.InvokeMember("HighPart", BindingFlags.GetProperty, null, largeInteger, null);

int lowPart = (int)type.InvokeMember("LowPart", BindingFlags.GetProperty, null, largeInteger, null);

return (long)highPart << style="color: #0000ff">uint)lowPart;

}


/// <summary>

/// Returns a collection of all users on the AD, for the given CN.

/// </summary>

/// <param name="ldap">Fully qualified LDAP url, alongwith the CN and OU.</param>

/// <param name="connectUser">Domain account having privileges to query to the LDAP.</param>

/// <param name="connectPassword">Password for the domain account.</param>

/// <returns>SearchResultCollection</returns>

public static SearchResultCollection DumpAllUsers(string ldap, string connectUser, string connectPassword)

{

DirectoryEntry deSystem = new DirectoryEntry(ldap, connectUser, connectPassword);

DirectorySearcher s = new DirectorySearcher(deSystem, "SAMAccountName=*",

new string[] { "cn", "mail", "samAccountName", "userAccountControl" }

, SearchScope.Subtree);


return s.FindAll();

}

}

}

No comments: