Recently I was writing a login process that required me to query Active Directory, admittedly something I have never done before, I took to the internet to search for a solution on how this was going to work and came across the following query string to use within my existing class.
search.Filter = "(&(objectCategory=person)(objectClass=user)(anr=" + Username + ")(!userAccountControl:1.2.840.113556.1.4.803:=2))";
I set to test this with multiple users, and it worked perfectly, the correct data was returned in 100% of the test cases that I had tried, so the code got moved into production and signed off.
Fast forward 6 months or so and a user logged a ticket saying they were unable to login to the application, there is a check you see to make sure that each user has a valid email address in Active Directory, if they don’t they can’t use the application, the user who had logged the ticket did have an email address so I debugged the code with the failing user’s username and right enough the wrong details were returned from Active Directory.
Here is the code I was using, The search filter is going to look for the following;
As i was using search.FindOne It will only return the first result it finds.
public static string Fname(string Username)
{
DirectoryEntry entry = new DirectoryEntry();
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(&(objectCategory=person)(objectClass=user)(anr=" + Username + ")(!userAccountControl:1.2.840.113556.1.4.803:=2))";
search.PropertiesToLoad.Add("givenName"); // first name
SearchResult result = search.FindOne();
string Firstname = string.Empty;
if (result.Properties["givenName"].Count > 0)
{
Firstname = result.Properties["givenName"][0] as String;
}
return Firstname;
}
anr or Ambiguous Name Resolution will take the string provided in the anr and search a pre-defined list of attributes (see below);
Windows Server |
Schema Version |
displayName |
givenName (First Name) |
legacyExchangeDN |
msDS-AdditionalSamAccountName |
msDS-PhoneticCompanyName |
msDS-PhoneticDepartment |
msDS-PhoneticDisplayName |
msDS-PhoneticFirstName |
msDS-PhoneticLastName |
Name (RDN) |
physicalDeliveryOfficeName |
proxyAddresses |
sAMAccountName |
sn (Last Name) |
mailNickname |
msExchResourceSearchProperties |
Source: Active Directory: Ambiguous Name Resolution
So let’s suppose the username the user had entered was bloggs the anr would look like this anr=”bloggs” the problem is it would only return the first result where “bloggs” appears at the start of any of the naming attributes listed in the pre-defined list above. This was a problem, I had made a mistake in compiling the query string.
What I actually wanted was;
This required a small change to the query string so instead of using anr we would need to swap this for sAMAccountName this will now return objects that match the above criteria but the Active Directory username must explicilty match the provided string. So if the Active Directory username was bloggsj the username in the application would have to send bloggsj else no match would be found and login would fail.
public static string Fname(string Username)
{
DirectoryEntry entry = new DirectoryEntry();
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=" + Username + ")(!userAccountControl:1.2.840.113556.1.4.803:=2))";
search.PropertiesToLoad.Add("givenName"); // first name
SearchResult result = search.FindOne();
string Firstname = string.Empty;
if (result.Properties["givenName"].Count > 0)
{
Firstname = result.Properties["givenName"][0] as String;
}
return Firstname;
}
I debugged the amended code using the same username of the person who had logged the ticket, right enough the correct information was now being returned, but what about people who were not affected before the change? I tested with them too and all of the accounts returned the expected data from Active Directory.
This tripped me up, I fell, but I got back up, debugged the problem and came up with a resoloution, I am not by any means a Senior developer so I wanted to share my findings with you guys so that if you come across this problem in your code it dosen’t trip you up too, hopefully someone somewhere will find this useful.