I have some working LDAP code in which we rebind to the found user in order to validate the user, using his distinguished name. Effectively this is what is happening:
string userDn = #"cn=Feat Studentl+umanroleid=302432,ou=Faculty of Engineering & Physical Sciences Administration,ou=Faculty of Engineering & Physical Sciences,ou=People,o=University of TestSite,c=GB";
string fullPath = #"LDAP://surinam.testsite.ac.uk:636/" + userDn;
DirectoryEntry authUser = new DirectoryEntry(fullPath, userDn, "mypassword", AuthenticationTypes.None);
authUser.RefreshCache();
However this causes error unknown error 80005000 at DirectoryEntry.Bind()
I suspected the problem might be that the DN has a '+' and a '=' in the CN attribute. Therefore after finding that the way to escape this should be with a \ and the hex value of the character I tried this:
string userDn = #"cn=Feat Studentl\2Bumanroleid\3D302432,ou=Faculty of Engineering & Physical Sciences Administration,ou=Faculty of Engineering & Physical Sciences,ou=People,o=University of TestSite,c=GB";
However I get the error:
Login failure: unknown user name or bad password
I assume this is because that now it is happy with the request but it is failing to match the users DN, for some reason.
Is there anyway around this?
In my experience developing LDAP services, whenever you get a login failure due to invalid credentials, that does tend to be the issue with the bind attempt. You're getting that error because DirectoryEntry does not parse the escaped characters in the DN... however, you shouldn't have to do that in the first place.
In your code - setting the AuthenticationTypes to "None" forces the entry to make a Simple bind based on the DN you're providing. Since your including the server name as part of the path, I would try using the ServerBind auth type instead, like this :
string LdapPath = ("LDAP://" + ldapUrl + "/" + Domain);
//Build the user and issue the Refresh bind
var dirEntry = new DirectoryEntry
{
Path = LdapPath,
Username = _usernameToVerify,
Password = _passwordToVerify,
AuthenticationType = AuthenticationTypes.ServerBind
};
//This will load any available properties for the user
dirEntry.RefreshCache();
Also, it looks like you're making this call to the secure LDAP port (636), so make sure you also include AuthenticationTypes.SecureSocketsLayer along with the ServerBind mechansim :
AuthenticationType = AuthenticationTypes.ServerBind | AuthenticationTypes.SecureSocketsLayer
Hope this helps!
I had to resort to digging through an old DLL project that was customised for one customer.
I managed to get it to work. It appears you have to refer to these low level Directory Services routines if you have a DN with escape characters. (Note in real life the DN is obtained by an initial felxible user search by setting up a DirectorySearcher and doing FindOne first)
string userDn = #"cn=Feat Studentl+umanroleid=302432,ou=Faculty of Engineering & Physical Sciences Administration,ou=Faculty of Engineering & Physical Sciences,ou=People,o=University of TestSite,c=GB";
string basicUrl = #"surinam.testsite.ac.uk:636";
var ldapConnection = new LdapConnection(basicUrl);
ldapConnection.AuthType = AuthType.Basic;
LdapSessionOptions options = ldapConnection.SessionOptions;
options.ProtocolVersion = 3;
options.SecureSocketLayer = true;
NetworkCredential credential = new NetworkCredential(userDn, password);
ldapConnection.Credential = credential;
try
{
ldapConnection.Bind();
Console.WriteLine("bind succeeded ");
}
catch (LdapException e)
{
if (e.ErrorCode == 49)
{
Console.WriteLine("bind failed ");
}
else
{
Console.WriteLine("unexpected result " + e.ErrorCode);
}
}
catch (DirectoryOperationException e)
{
Console.WriteLine("unexpected error " + e.Message);
}
Related
I am using the below code to find the user in the active directory
DirectoryEntry dirEntry = null;
using (dirEntry = new DirectoryEntry(ldapPath, ldapUser, ldapPassword))
{
try
{
Object adsiObject = dirEntry.NativeObject;
result = true;
}
catch (Exception exception)
{
errorInfo.ErrorCode = -1;
errorInfo.ErrorMessage = exception.Message;
}
}
I have a doubt about "ldapUser".
Which field does it correspond in the Active Directory ?
Is it "user logon name" or "user logon name pre-2000" ?
And i need to know the no of characters that could be given for "ldapUser" field.
What you call "User Logon Name" is in reality the User Principal Name (UPN) and the pre-Windows 2000 is the sAMAccountName. You can find both in the Attribute Editor (you need the advanced features for that).
The property that is edited with dirEntry.Username is the sAMAccountName.
I advice you to edit your UPN too if you change the username. I prefer directly using the properties of the DirectoryEntry object like that :
dirEntry.Properties["sAMAccountName"].Value = newName_;
dirEntry.Properties["userPrincipalName"].Value = newName_ + "#yourdomain.com";
About the characters needed number, it depends of your GPO (for the minimal value and if special/capital characters are required or not).
For the maximum value, it's 20 characters. I faced to this issue few month ago.
Hope that helped.
I'm attempting to query AD in an ASP.Net (4.0) application that is running on Windows Server 2008 R2 (IIS7 installed). (It also fails when running as a 2.0 application as well)
This is nothing new for me, as I've done this many times before. I wrote a small ASP.Net program that runs fine on my own machine (Windows XP with IIS6), but fails when run on the 2008 box.
(The result is that you see a list of groups the user is a member of in a textbox)
(on button_click)
var userName = txtUserName.Text;
if (userName.Trim().Length == 0)
{
txtResults.Text = "-- MISSING USER NAME --";
return;
}
var entry = new DirectoryEntry("LDAP://blah.blah/DC=blah,DC=blah",
"cn=acct, dc=blah, dc=blah",
"pass");
var search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + userName + ")";
search.PropertiesToLoad.Add("memberOf");
var groupsList = new StringBuilder();
var result = search.FindOne();
if (result != null)
{
int groupCount = result.Properties["memberOf"].Count;
for (int counter = 0; counter < groupCount; counter++)
{
groupsList.Append((string)result.Properties["memberOf"][counter]);
groupsList.Append("\r\n");
}
}
txtResults.Text = groupsList.ToString();
When I run this code I get the following error on search.FindOne():
System.DirectoryServices.DirectoryServicesCOMException (0x8007203B): A local error has occurred.
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne)
at System.DirectoryServices.DirectorySearcher.FindOne()
at WebApplication1._Default.btnSearch_Click(Object sender, EventArgs e)
We've done a lot of research with this and twiddled every IIS7 setting we can think of, but no go so far. Any clues?
Change the username parameter from "cn=xxx, dc=yyy, dc=zzz" to "Domain\Username"
You can also change the IIS Application Pool to run a domain account with the query priveleges you are searching for.
I have a few other comments as well:
Make sure the first entry for the DirectoryEntry constructor includes the container for the users as well. This should help the DirectorySearcher to work more reliably.
I believe the second parameter in the DirectoryEntry constructor should be the user name, not the AD query path.
You should set the AuthenticationType property as well. With Server 2008, by default, this needs to be set to AuthenticationTypes.Secure | AuthenticationTypes.ServerBind | AuthenticationTypes.Sealing. I'd guess that 2008R2 has a simliar requirement.
I see that the question is rather old, but after struggling with this I thought to mention that it is indeed possible to use the LDAP-style of the username (in opposite to the DNS style). This works well for me:
string connString = "LDAP://MyDomain/CN=blah,DC=blah,DC=blah";
string username = "CN=MyAdmin,CN=Users,CN=blah,DC=blah,DC=blah";
string password = "myLittleSecret";
DirectoryEntry root = new DirectoryEntry(
connString,
username,
password,
AuthenticationTypes.None);
Where MyAdmin is a member in the Administrators role.
One little thing that took me a while to find is the AuthenticationTypes.None parameter that is needed if you do not want to communicate over SSL. Surely, you want to do this in production, but for development purposes it may be OK to skip the encryption.
Environment: Windows 7
I was also getting this exception when tried to query the active directory:
SearchResult result = srch.FindOne();
To resolve this, just put the above code inside Security.RunWithElevatedPrivileges().
Final Solution:
SPSecurity.RunWithElevatedPrivileges(delegate()
{
result = srch.FindOne();
});
I'm using DirectoryServices to authenticate a user against an ADLDS (the lighteweight Active Directory). After I pass authentication. How can I determine the DN or SID of the currently logged in user?
using (DirectoryEntry entry = new DirectoryEntry(<a>LDAP://XYZ:389</a>,
userName.ToString(),
password.ToString(),
AuthenticationTypes.Secure))
{
try
{
// Bind to the native object to force authentication to happen
Object native = entry.NativeObject;
MessageBox.Show("User authenticated!");
}
catch (Exception ex)
{
throw new Exception("User not authenticated: " + ex.Message);
}
...
Thanks
Update:
I get an exception at
src = search.FindAll()
There is no such object on the server.
I realized the user logging in has a class type "foreignSecurityPrincipal" in the Active Directory lightweight so I figured perhaps I can just modify your filter to be:
search.Filter = "(&(objectclass=foreignSecurityPrincipal)" + "(sAMAccountName=" + userName + "))";
But that gave me the same exception. Any idea what I am missing?
To my knowledge you will have to do an LDAP Search for the user and get the distinguishedName property from AD. See below:
// you can use any root DN here that you want provided your credentials
// have search rights
DirectoryEntry searchEntry = new DirectoryEntry("LDAP://XYZ:389");
DirectorySearcher search = new DirectorySearcher(searchEntry);
search.Filter = "(&(objectclass=user)(objectCategory=person)" +
"(sAMAccountName=" + userName + "))";
if (search != null)
{
search.PropertiesToLoad.Add("sAMAccountName");
search.PropertiesToLoad.Add("cn");
search.PropertiesToLoad.Add("distinguishedName");
log.Info("Searching for attributes");
// find firest result
SearchResult searchResult = null;
using (SearchResultCollection src = search .FindAll())
{
if (src.Count > 0)
searchResult = src[0];
}
if (searchResult != null)
{
// Get DN here
string DN = searchResult.Properties["distinguishedName"][0].ToString();
}
When I add a new user manually in the active directory, the 'distinguished Name' cannot be define manually but the convention seems to be the first name + ' ' + the last name. In this case, why not trying to get the 'distinguished name' following this pattern. I also found that if I just specified a first name to create a non-human user, the 'distinguihed name' is the equal to the first name without space after.
I follow this pattern in my application and it works and it's much simple than trying to create custom query to search user.
Microsoft has a general purpose KB article (Q316748) describing how to authenticate against Active Directory using the DirectoryEntry object. In their example they produce a username value by concatenating the domain name and username into the standard NetBIOS format("domain\username") and passing that as a parameter to the directory entry constructor:
string domainAndUsername = domain + #"\" + username;
DirectoryEntry entry = new DirectoryEntry(_path, domainAndUsername, pwd);
It recently came to our attention that the domain part of the username was being completely ignored and in multiple environments I've confirmed this behavior. The username and password are in fact being used, as authentication fails when they're invalid, but any arbitrary value can be supplied for the domain name and authentication passes. At a glance I'd theorize this format works for WinNT based directory access but the domain part is ignored for LDAP.
A check on google shows many LDAP examples passing a "domain\username" value to the DirectoryEntry object so I've either messed something up in my configuration or there's a lot of people confused by the KB article. Can anyone confirm this is the expected behavior or recommend a way to accept "domain\username" values and authenticate against Active Directory with them?
Thanks,
The short answer: When the path parameter of the DirectoryEntry constructor contains an invalid domain name the DirectoryEntry object will (after an unsuccessful search for the invalid domain in the forrest) attempt a fall back by dropping the domain part of the username parameter and attempt connection using the plain username (sAMAccountName).
The long answer: If the domain name specified in the username parameter is invalid but the user exists in the domain specified in the path parameter the user will be authenticated (through the use of the fallback). However, if the user exists in another domain in the forrest than the one specified in the path parameter authentication will only succeed when the domain part of the username parameter is included and correct.
There are four different ways of specifying the username parameter when dealing with DirectoryEntry-objects:
Distinguished Name (CN=Username,CN=Users,DC=domain,DC=local)
NT Account Name (DOMAIN\Username)
Plain Account Name/sAMAccountname (username)
User Principal Name (generally username#domain.local)
Let me illustrate with an example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices;
namespace DirectoryTest
{
class Program
{
private static Int32 counter = 1;
static void Main(string[] args)
{
TestConnection();
}
private static void TestConnection()
{
String domainOne = "LDAP://DC=domain,DC=one";
String domainOneName = "DOMAINONE";
String domainOneUser = "onetest";
String domainOnePass = "testingONE!";
String domainTwo = "LDAP://DC=domain,DC=two";
String domainTwoName = "DOMAINTWO";
String domainTwoUser = "twotest";
String domainTwoPass = "testingTWO!";
String invalidDomain = "INVALIDDOMAIN";
// 1) This works because it's the correct NT Account Name in the same domain:
Connect(domainOne, domainOneName + "\\" + domainOneUser, domainOnePass);
// 2) This works because username can be supplied without the domain part
// (plain username = sAMAccountName):
Connect(domainOne, domainOneUser, domainOnePass);
// 3) This works because there's a fall back in DirectoryEntry to drop the domain part
// and attempt connection using the plain username (sAMAccountName) in (in this case)
// the forrest root domain:
Connect(domainOne, invalidDomain + "\\" + domainOneUser, domainOnePass);
// 4) This works because the forrest is searched for a domain matching domainTwoName:
Connect(domainOne, domainTwoName + "\\" + domainTwoUser, domainTwoPass);
// 5) This fails because domainTwoUser is not in the forrest root (domainOne)
// and because no domain was specified other domains are not searched:
Connect(domainOne, domainTwoUser, domainTwoPass);
// 6) This fails as well because the fallback of dropping the domain name and using
// the plain username fails (there's no domainTwoUser in domainOne):
Connect(domainOne, invalidDomain + "\\" + domainTwoUser, domainTwoPass);
// 7) This fails because there's no domainTwoUser in domainOneName:
Connect(domainOne, domainOneName + "\\" + domainTwoUser, domainTwoPass);
// 8) This works because there's a domainTwoUser in domainTwoName:
Connect(domainTwo, domainTwoName + "\\" + domainTwoUser, domainTwoPass);
// 9) This works because of the fallback to using plain username when connecting
// to domainTwo with an invalid domain name but using domainTwoUser/Pass:
Connect(domainTwo, invalidDomain + "\\" + domainTwoUser, domainTwoPass);
}
private static void Connect(String path, String username, String password)
{
Console.WriteLine(
"{0}) Path: {1} User: {2} Pass: {3}",
counter, path, username, password);
DirectoryEntry de = new DirectoryEntry(path, username, password);
try
{
de.RefreshCache();
Console.WriteLine("{0} = {1}", username, "Autenticated");
}
catch (Exception ex)
{
Console.WriteLine("{0} ({1})", ex.Message, username);
}
Console.WriteLine();
counter++;
}
}
}
In the example above domain.one is the forrest root domain and domain.two is in the same forrest as domain.one (but a different tree naturally).
So to answer your question: Authentication will always fail if the user in not in the domain that we're connecting to and no or an invalid domain name is specified in the username parameter.
I have two applications which uses DirectoryEntry(_path, domainAndUsername, pwd); constructor and I don't have any authentication problem. Each application is installed on a different customer, both with very (very) large domain structures.
I am trying to get a user's email address in AD without success.
String account = userAccount.Replace(#"Domain\", "");
DirectoryEntry entry = new DirectoryEntry();
try {
DirectorySearcher search = new DirectorySearcher(entry);
search.PropertiesToLoad.Add("mail"); // e-mail addressead
SearchResult result = search.FindOne();
if (result != null) {
return result.Properties["mail"][0].ToString();
} else {
return "Unknown User";
}
} catch (Exception ex) {
return ex.Message;
}
Can anyone see the issue or point in the right direction?
Disclaimer: This code doesn't search for a single exact match, so for domain\j_doe it may return domain\j_doe_from_external_department's email address if such similarly named account also exists. If such behaviour is undesirable, then either use a samAccountName filter intead of an anr one used below or filter the results additionally.
I have used this code successfully (where "account" is the user logon name without the domain (domain\account):
// get a DirectorySearcher object
DirectorySearcher search = new DirectorySearcher(entry);
// specify the search filter
search.Filter = "(&(objectClass=user)(anr=" + account + "))";
// specify which property values to return in the search
search.PropertiesToLoad.Add("givenName"); // first name
search.PropertiesToLoad.Add("sn"); // last name
search.PropertiesToLoad.Add("mail"); // smtp mail address
// perform the search
SearchResult result = search.FindOne();
You guys are working too hard:
// Look up the current user's email address
string eMail = UserPrincipal.Current.EmailAddress;
You can try the below GetUserEmail method. If You are looking out to find the email address for logged-in user in MVC then call the GetUserEmail() function with User.Identity.Name
using System.DirectoryServices;
using System.Linq;
public string GetUserEmail(string UserId)
{
var searcher = new DirectorySearcher("LDAP://" + UserId.Split('\\').First().ToLower())
{
Filter = "(&(ObjectClass=person)(sAMAccountName=" + UserId.Split('\\').Last().ToLower() + "))"
};
var result = searcher.FindOne();
if (result == null)
return string.Empty;
return result.Properties["mail"][0].ToString();
}
GetUserEmail(User.Identity.Name) //Get Logged in user email address
You forgot a filter.
Try adding this before calling FindOne:
search.Filter = String.Format("(sAMAccountName={0})", account);
What about this
public string GetEmailFromSamAccountName(string samAccountName, string domain="YOURCOMPANY")
{
using (var principalContext = new PrincipalContext(ContextType.Domain, domain))
{
var userPrincipal = UserPrincipal.FindByIdentity(principalContext, samAccountName);
return userPrincipal.EmailAddress;
}
}
Also, where do you pull the username from (stored, user input, current identity)? A username can change (be renamed) easily - the SID/Windows Logon Identity on the other hand does not change - so you would be better off doing filters/searches by SID rather than samaccountname - if possible and/or needed design-wise...
You need to add references for System.DirectoryServices.AccountManagement and include this same references in your using statement. Now you will have access to the current users login details as listed below include the email address.
string loginname = Environment.UserName;
string firstname = UserPrincipal.Current.GivenName;
string lastname = UserPrincipal.Current.Surname;
string name = UserPrincipal.Current.Name;
string eMail = UserPrincipal.Current.EmailAddress;
update: fredrick nailed it....
Jakob is right. You need to filter your search. You can do all sorts of ands and ors there too if you need to, but I think sAMAccountName is enough. You might want to fire up the ADSI tool (it's in the resource kit I think), which lets you walk AD like the registry. it's great for looking at properties. Then find a user, work out what prop you want (mail in this case) and what it's primary key is - sAMAccountName is a good one, but you may also want to filter on the node type.
I'm on a mac, so I can't check it for you, but each node in AD has a type, and you can add that to your filter. I think it looks like this:
((sAMAccountName=bob) & (type=User))
Again, check that - I know it's not type=user, but something LIKE that.