I'm attempting to authenticate a user against ADAM using a user I created in ADAM. However, regardless of the password used (correct, or incorrect), my search comes back with a valid DirectoryEntry object. I would assume that if the password is invalid, then the search would come back with a null object. Are my assumptions wrong or is there a flaw in the code below?
DirectoryEntry de = new DirectoryEntry("LDAP://localhost:389/cn=Groups,cn=XXX,cn=YYY,dc=ZZZ");
DirectorySearcher deSearch = new DirectorySearcher();
deSearch.SearchRoot = de;
deSearch.Filter = "(&(objectClass=user) (cn=" + userId + "))";
SearchResultCollection results = deSearch.FindAll();
if (results.Count > 0)
{
DirectoryEntry d = new DirectoryEntry(results[0].Path, userId, password);
if (d != null)
DoSomething();
}
You need to access a property of the DirectoryEntry to determine if it's valid. I usually check if the Guid is null or not.
bool valid = false;
using (DirectoryEntry entry = new DirectoryEntry( results[0].Path, userId, password ))
{
try
{
if (entry.Guid != null)
{
valid = true;
}
}
catch (NullReferenceException) {}
}
Note: you'll also want to wrap your search root directory entry and searcher in using statements, or explicitly dispose of them when you are done so that you don't leave the resources in use.
P.S. I'm not sure exactly which exception gets thrown when you attempt to access an invalid directory entries properties. A bit of experimentation is probably in order to figure out which exception to catch. You won't want to catch all exceptions as there are other problems (directory server not available, for instance) that you may want to handle differently than a failed authentication.
Related
I maintain a C# program which needs to check whether thousands of Active Directory accounts are still in existence & if they are enabled or not. Recently, I've found that my program was hanging while querying the directory for an account. Fortunately, I've just discovered the DirectorySearcher.ClientTimeout property (which I hadn't been setting, meaning that the search goes on indefinitely).
The one problem that I see with using this property is that, if the search hangs while looking up an account that happens to actually exist, the DirectorySearcher.FindOne() method will return 0 results. As you can imagine, that's a problem since at runtime, I don't know whether the search failed or if the account really wasn't found.
Does anyone know if there's another property that gets set in the object that I can use to see if the search aborted? Is there any difference between a result set from an aborted search versus one that really contains zero results?
Here's my method:
public static string UserExists(string username, Log log)
{
string accountStatus;
if (username.Split('\\').Length != 2)
return "Invalid ID,Invalid ID";
else
{
try
{
string[] parts = username.Split('\\');
domain = parts[0];
ScopeDN = "DC=" + domain + ",DC=contoso,DC=com";
DirectoryEntry de = GetDirectoryEntry("LDAP://" + ScopeDN);
DirectorySearcher ds = new DirectorySearcher();
ds.SearchRoot = de;
ds.ClientTimeout = TimeSpan.FromSeconds(30);
ds.Filter = "(&(objectClass=user) (sAMAccountName=" + username + "))";
SearchResult result = ds.FindOne();
if (result == null)
accountStatus = "Does Not Exist,Account Does Not Exist";
else
{
int UAC = (int)result.Properties["userAccountControl"][0];
bool enabled = !Convert.ToBoolean(UAC & 0x00002);
if (enabled)
accountStatus = "Exists,Account is Enabled";
else
accountStatus = "Exists,Account is Disabled";
}
return accountStatus;
}
catch (Exception e)
{
log.exception(LogLevel._ERROR, e, false);
return "Exception,Exception";
}
}
}
Try doing the following optimizations:
Dispose DirectoryEntry
Dispose DirectorySearcher
Create and cache a connection to RootDse object before querying users. In this case all AD queries will use one single cached connection to AD, which significantly increases performance.
For example
var entry = new DirectoryEntry("LDAP://contoso.com/RootDSE");
entry.RefreshCache();
// making user search and other AD work with domain contoso.com here
entry.Dispose();
AFAIK, there is no difference. One thing you could do is retry a couple times before assuming it is missing. Its a matter of confidence. One failure may not give high enough confidence. But 2 or 3 might. Does that help you?
I am having a problem updating user information in an Active Directory DB...
When I run the following code I get this error:
The specified directory service attribute or value does not exist
The problem is the path it is using to save the information is this:
CN=AD Test,OU=Container Name,DC=us,DC=flg,DC=int
Ad Test is the username in AD that I am trying to update.
and I believe it should be:
CN=Ad Test,OU=Container Name, OU=Server Name,DC=us,DC=flg,DC=int
I am new to Directory services so I would greatly appreciate any help in finding out why I cannot update... Thank you in advance
public bool UpdateActiveDirectory(string LdapServerName, string CustId, Employee SQLresult)
{
try
{
DirectoryEntry rootEntry = new DirectoryEntry("LDAP://" + LdapServerName, "usrename", "password", AuthenticationTypes.Secure);
DirectorySearcher searcher = new DirectorySearcher(rootEntry);
searcher.Filter = "(sAMAccountName=" + SQLresult.LogonNT + ")";
searcher.PropertiesToLoad.Add("title");
searcher.PropertiesToLoad.Add("street");
searcher.PropertiesToLoad.Add("1");
searcher.PropertiesToLoad.Add("st");
searcher.PropertiesToLoad.Add("postalCode");
searcher.PropertiesToLoad.Add("department");
searcher.PropertiesToLoad.Add("mail");
searcher.PropertiesToLoad.Add("manager");
searcher.PropertiesToLoad.Add("telephoneNumber");
SearchResult result = searcher.FindOne();
if (result != null)
{
// create new object from search result
DirectoryEntry entryToUpdate = result.GetDirectoryEntry();
entryToUpdate.Properties["title"].Value = SQLresult.Title;
entryToUpdate.Properties["street"].Value = SQLresult.Address;
entryToUpdate.Properties["1"].Value = SQLresult.City;
entryToUpdate.Properties["st"].Value = SQLresult.State;
entryToUpdate.Properties["postalCode"].Value = SQLresult.ZipCode;
entryToUpdate.Properties["department"].Value = SQLresult.Department;
entryToUpdate.Properties["mail"].Value = SQLresult.EMailID;
entryToUpdate.Properties["manager"].Value = SQLresult.ManagerName;
entryToUpdate.Properties["telephoneNumber"].Value = SQLresult.Phone;
entryToUpdate.CommitChanges();
Console.WriteLine("User Updated");
}
else
{
Console.WriteLine("User not found!");
}
}
catch (Exception e)
{
Console.WriteLine("Exception caught:\n\n" + e.ToString());
}
return true;
}
Maybe just a typo?
The third property you're trying to update:
entryToUpdate.Properties["1"].Value = SQLresult.City;
is that a one (1) in there? It should be a small L (l) instead.
Also: the manager's name must be the Distinguished Name of the manager - the whole
CN=Manager,CN=Ad Test,OU=Container Name, OU=Server Name,DC=us,DC=flg,DC=int
thing - not just the name itself.
If that doesn't help anything - just go back to old-school debugging technique:
update just a single property; if it fails --> that's your problem case - figure out why it's a problem.
If it works: uncomment a second property and run again
-> repeat over and over again, until you find your culprit
public Object IsAuthenticated()
{
String domainAndUsername = strDomain + "\\" + strUser;
***DirectoryEntry entry = new DirectoryEntry(_path, domainAndUsername, strPass);***
SearchResult result;
try
{
//Bind to the native AdsObject to force authentication.
DirectorySearcher search = new DirectorySearcher(entry) { Filter = ("(SAMAccountName=" + strUser + ")") };
search.PropertiesToLoad.Add("givenName"); // First Name
search.PropertiesToLoad.Add("sn"); // Last Name
search.PropertiesToLoad.Add("cn"); // Last Name
result = search.FindOne();
if (null == result)
{
return null;
}
//Update the new path to the user in the directory.
_path = result.Path;
_filterAttribute = (String)result.Properties["cn"][0];
}
catch (Exception ex)
{
return new Exception("Error authenticating user. " + ex.Message);
}
return user;
}
In the above code segment, is there a way to retrieve the user's Windows login password so that the LDAP authentication works without asking the user his password another time?
Can the value for "strPass",that is being passed when DirectoryEntry object is being created, be retrieved by any way?
The password does not exist anywhere. It would be a big security hole if it did.
Also, BTW, get rid of the try/catch block. It's doing nothing but hiding the reason for the exception.
Use the ActiveDirectoryMemebershipProvider - You can authenticate without writing code, thus eliminating the login scenario you currently have.
You could set up Windows Authentication in your ASP.NET app.
http://msdn.microsoft.com/en-us/library/ff647405.aspx
Once you set this up, only authenticated users have access to the protected parts of your site.
This gives you access some key bits of information.
For example:
System.Web.HttpContext.Current.User.Identity.Name - gives the name (domain\username) of an authenticated user.
System.Web.HttpContext.Current.User.IsInRole("role_name_here") - will tell you if the authenticated user is in a given role.
Authentication can be tricky to do for the first time - please do not ask a user for their windows password - this is a security risk - allow IIS and the .NET framework take care of this for you. The article above may be a bit long and seem a bit complicated but it has a lot of good information in it.
When we try to search for a user in ActiveDirectory, we get that exception - 0x8007203B.
Basically we deployed a web service, which uses DirectoryEntry & DirectorySearcher class to find a user in AD, and sometimes this exception happens. But when we do IISReset, it again works fine.
Code is very simple like this:
DirectoryEntry domainUser = new DirectoryEntry("LDAP://xxx.yyy/dc=xxx,dc=yyy", "domain\user", "pwd", AuthenticationTypes.Secure);
DirectoryEntry result = new DirectorySearcher(domainUser, filter);
Only some times this happens. I don't have much information to provide, any guess much appreciated
This is how my filter looks like
public static string BuildFilter(DirectoryEntry dirEntry, string userName, string userMail)
{
try
{
string filter = string.Empty;
if (!string.IsNullOrEmpty(userName) && string.IsNullOrEmpty(userMail))
filter = string.Format(#"(&(objectClass=user)(samaccounttype=805306368)(|(CN={0})(samaccountname={0})))", userName);
else if (string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(userMail))
filter = string.Format(#"(&(objectClass=user)(samaccounttype=805306368)(mail={0}))", userMail);
else
filter = string.Format(#"(&(objectClass=user)(samaccounttype=805306368)(|(CN={0})(samaccountname={0})(mail={1})))", userName, userMail);
return filter;
}
catch (Exception ex)
{
_logger.Error("BuildUserSearch - Failed to build LDAP search", ex);
}
return null;
}
You say that this it's just append after some time. As DirectoryEntry and DirectorySearcher are built on COM object into disposable class I would first just add some using sections to be sure that underlying objects are corectly freed.
using(DirectoryEntry root = new DirectoryEntry(ldapPath))
{
using(DirectorySearcher searcher=new DirectorySearcher(root))
{
...
}
...
}
Any guess are appreciated?
Then here's mine:
ASP.NET: DirectoryServicesCOMException [...];
Windows Error Codes: Repair 0x8007203B. How To Repair 0x8007203B.
What makes me confuse is that you say it works most of the time...
Did this help?
P.S. I'll update if I think of anything else.
I would like to have a clean C# class that authenticates from Active Directory.
It should be pretty simple, it just has to ask for credentials and check if it matches what AD is expecting.
I am responsible for a number of C# applications, and I would like all of them to use the same class.
Could someone please provide a clean code sample of such a class? It should have good error handling, be well commented, and specifically ask for credentials rather than try to read if a user is already logged in to AD for another application. (This is a security requirement because some applications are used in areas with shared computers: People with multiple roles and different permission levels may use the same computer and forget to log out between sessions)
http://support.microsoft.com/kb/316748
public bool IsAuthenticated(String domain, String username, String pwd)
{
String domainAndUsername = domain + "\\" + username;
DirectoryEntry entry = new DirectoryEntry(_path, domainAndUsername, pwd);
try
{ //Bind to the native AdsObject to force authentication.
Object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if(null == result)
{
return false;
}
//Update the new path to the user in the directory.
_path = result.Path;
_filterAttribute = (String)result.Properties["cn"][0];
}
catch (Exception ex)
{
throw new Exception("Error authenticating user. " + ex.Message);
}
return true;
}
Admittedly I have no experience programming against AD, but the link below seems it might address your problem.
http://www.codeproject.com/KB/system/everythingInAD.aspx#35
There's some reason you can't use Windows integrated authentication, and not bother users with entering their names and passwords? That's simultaneously the most usable and secure solution when possible.