using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.DirectoryServices;
using System.Text;
namespace Org.Khakipants.Identity {
///
/// Summary description for LdapQueries.
///
internal class LdapQueries {
#region Ldap Queries
private static string MEMBEROF = "memberof";
// this is not as efficient as it could be
// because of poor connection management
/// The path to the ldap server (i.e. "GC://" + server + ":3269").
internal static ArrayList GetGroups(string path, string userName){
Debug.WriteLine("Getting groups.");
StringCollection allParentGroupDns = new StringCollection(); // key and value = a groupDn
/////////////////////
// get all groups the user is an immediate memberof
/////////////////////
ArrayList desiredProperties = new ArrayList();
desiredProperties.Add(MEMBEROF);
SortedList properties = GetProperties(path, userName, desiredProperties);
ArrayList immediateGroupDnList = (ArrayList) properties[MEMBEROF];
/////////////////////
// find all of the parents/ancestors of the immediateGroupDnList
/////////////////////
DirectoryEntry anonymousEntry = new DirectoryEntry( path , ANONYMOUS_USERNAME , ANONYMOUS_PASSWORD);
try {
return GetAllParentGroups(anonymousEntry, immediateGroupDnList);
} finally {
anonymousEntry.Close();
}
}
private static string GROUPS_SEARCH_FILTER =
"(|(objectCategory=CN=Group,CN=Schema,CN=Configuration,DC=ad,DC=khakipants,DC=org)(objectCategory=CN=Organizational-Unit,CN=Schema,CN=Configuration,DC=ad,DC=khakipants,DC=org))";
// "(|(objectClass=group)(objectClass=organizationalUnit))";
private static string BuildSearchFilter(ICollection childGroupDns ) {
StringBuilder sb = new StringBuilder();
// improve performance slightly by only looking at groups
sb.Append("(&"+ GROUPS_SEARCH_FILTER + "(|");
if( 0 < childGroupDns.Count ) {
foreach( string s in childGroupDns ) {
sb.Append(String.Format("(distinguishedName={0})", s));
}
} else {
// put a filter that will always be false... this allows me to
// do some performance testing for negative results.
sb.Append("(distinguishedName=SomethingThatWillNeverExist)");
}
sb.Append("))");
return sb.ToString();
}
private static ArrayList GetAllParentGroups(DirectoryEntry entry, ArrayList groupDns) {
ArrayList needsToBeExpanded = ExtractUnique(groupDns); // need to expand all children
Hashtable hasBeenSeen = new Hashtable();
foreach( string s in needsToBeExpanded) {
hasBeenSeen.Add(s,s);
}
ArrayList desiredPropertiesList = new ArrayList();
desiredPropertiesList.Add(MEMBEROF);
string [] desiredProperties = (string []) desiredPropertiesList.ToArray(typeof(string));
while( 0 < needsToBeExpanded.Count ) {
#if DEBUG
DateTime StartTime = DateTime.Now;
#endif
string SearchFilter = BuildSearchFilter(needsToBeExpanded);
Debug.WriteLine(SearchFilter);
// we don't need to expand these anymore
needsToBeExpanded.Clear();
DirectorySearcher searcher = new DirectorySearcher(
entry,
SearchFilter,
desiredProperties,
SearchScope.Subtree);
using( searcher ) {
SearchResultCollection results = searcher.FindAll();
using(results) {
foreach( SearchResult result in results ) {
ResultPropertyValueCollection memberOfList = result.Properties[MEMBEROF];
if( memberOfList != null ) {
foreach( string parentGroupDn in memberOfList ) {
if( ! hasBeenSeen.Contains(parentGroupDn) ) {
hasBeenSeen.Add(parentGroupDn, parentGroupDn);
needsToBeExpanded.Add(parentGroupDn);
} else {
Debug.WriteLine(String.Format("already seen : {0}", parentGroupDn));
}
}
}
}
}
}
#if DEBUG
Debug.WriteLine("Elapsed time = " + (DateTime.Now - StartTime).ToString());
#endif
}
return new ArrayList(hasBeenSeen.Keys);
}
internal static bool IsValidCredential(string path, string userName, string password) {
try {
string userDn = GetDistinguishedName(path, userName);
DirectoryEntry ssoEntry = new DirectoryEntry( path, ExtractDomain(userDn) + "\\" + userName , password);
using(ssoEntry) {
Trace.WriteLine(String.Format("Checking password for {0}.", userName));
try{
// force an authentication
object obj = ssoEntry.NativeObject;
} catch (Exception ex) {
Debug.WriteLine(ex);
return false;
}
}
} catch (InvalidUsernameException iue) {
object o = iue; // to squash compiler warnings
return false;
} catch (Exception ex) {
throw new Exception("Error authenticating user.", ex);
}
return true;
}
internal static string GetDistinguishedName(string path, string userName ) {
Trace.WriteLine(String.Format("Getting distinquished name for {0}.", userName));
ArrayList desiredProperties = new ArrayList();
desiredProperties.Add("distinguishedname");
SortedList Properties = GetProperties(path, userName, desiredProperties);
object DNArray = Properties["distinguishedname"];
if( null != DNArray) {
return (string) ((ArrayList) DNArray )[0];
} else {
throw new Exception("Could not load distinguishedname property.");
}
}
// an example CN=Jacobs\, Nathan B.,CN=Users,DC=khakipants,DC=org
internal static string ExtractDomain(string DN) {
string [] strings = DN.Split(',');
ArrayList DClist = new ArrayList();
foreach( string s in strings) {
if( s.StartsWith("DC=") ) {
DClist.Add(s.Substring(3));
}
}
StringBuilder sb = new StringBuilder();
foreach( string s in DClist) {
sb.Append(s);
sb.Append("."); // the trailing '.' is ok because of DNS standard
}
sb.Length = sb.Length - 1;
return sb.ToString();
}
/// If userName is not found in the given path.
internal static ArrayList GetAvailableProperties(string path, string userName ) {
Trace.WriteLine("Getting all available properties.");
ArrayList properties = new ArrayList();
// connect anonymously to determine proper distinguishedname
DirectoryEntry anonymousEntry = new DirectoryEntry( path , ANONYMOUS_USERNAME , ANONYMOUS_PASSWORD);
using (anonymousEntry) {
Debug.WriteLine(String.Format("Getting available properties for {0}.", userName));
// Bind to the native AdsObject to force authentication.
Object obj = anonymousEntry.NativeObject;
DirectorySearcher anonymousSearch = new DirectorySearcher(anonymousEntry);
using(anonymousSearch) {
anonymousSearch.ReferralChasing = ReferralChasingOption.All;
anonymousSearch.Filter = "(&(objectClass=user)(objectCategory=person)(SamAccountName=" + userName + "))";
anonymousSearch.PropertiesToLoad.Add("*");
SearchResult result = anonymousSearch.FindOne();
if(null == result) {
throw new InvalidUsernameException(userName, path);
}
foreach (string name in result.Properties.PropertyNames) {
properties.Add(name);
}
}
}
return properties;
}
internal static SortedList GetProperties(string path, string userName, ArrayList desiredProperties ) {
Trace.WriteLine("Getting properties.");
desiredProperties = ExtractUnique(desiredProperties);
SortedList properties = new SortedList();
// connect anonymously to determine proper distinguishedname
DirectoryEntry anonymousEntry = new DirectoryEntry( path , ANONYMOUS_USERNAME , ANONYMOUS_PASSWORD);
using(anonymousEntry) {
Trace.WriteLine(String.Format("Getting properties for {0}.", userName));
// Bind to the native AdsObject to force authentication.
Object obj = anonymousEntry.NativeObject;
DirectorySearcher anonymousSearch = new DirectorySearcher(anonymousEntry);
using(anonymousSearch) {
anonymousSearch.ReferralChasing = ReferralChasingOption.All;
anonymousSearch.Filter = "(&(objectClass=user)(objectCategory=person)(SamAccountName=" + userName + "))";
foreach ( string property in desiredProperties ) {
anonymousSearch.PropertiesToLoad.Add(property);
}
SearchResult result = anonymousSearch.FindOne();
if(null == result) {
throw new InvalidUsernameException(userName, path);
}
// note: using desiredProperties and not PropertyNames because of
// spurious addition of adspath to result set.
foreach (string name in desiredProperties ) { // result.Properties.PropertyNames) {
if( null != result.Properties[name] ) {
properties.Add(name, new ArrayList(result.Properties[name]));
} else {
properties.Add(name, new ArrayList());
}
}
}
}
return properties;
}
#endregion
#region Helper Functions
private static ArrayList ExtractUnique(ArrayList list) {
Hashtable table = new Hashtable();
foreach( object o in list ) {
if( ! table.Contains( o ) ) {
table[o] = o;
}
}
return new ArrayList(table.Keys);
}
#endregion
private LdapQueries() {
}
}
}