ldapUser corresponds to which filed in Active Directory? - c#

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.

Related

Check if a UserPrincipal is enabled

I'm using C# code to query an Active Directory. The main issue I'm having is determining whether an account has been disabled or not. Looking through many online articles, it would appear that one cannot solely rely on the property UserPrincipal.Enabled to determine if a user account is enabled or not. Fair enough, as its a nullable Boolean but when an AD administrator disables an account, this does appear to get set to false. The problem I have is when I query a client's AD, I find that most user accounts UserPrincipal objects return false for this property. So when I use this code to check if an account is disabled:
private bool IsUserEnabled(UserPrincipal userPrincipal)
{
bool isEnabled = true;
if (userPrincipal.AccountExpirationDate != null)
{
// Check the expiration date is not passed.
if (userPrincipal.AccountExpirationDate <= DateTime.Now)
{
Log.DebugFormat("User {0} account has expired on {1}", userPrincipal.DisplayName, userPrincipal.AccountExpirationDate.Value);
isEnabled = false;
}
}
if (userPrincipal.IsAccountLockedOut())
{
isEnabled = false;
Log.DebugFormat("User {0} account is locked out", userPrincipal.DisplayName);
}
if (userPrincipal.Enabled != null)
{
isEnabled = userPrincipal.Enabled.Value;
Log.DebugFormat("User {0} account is Enabled is set to {1}", userPrincipal.DisplayName, userPrincipal.Enabled.Value);
}
return isEnabled;
}
Most accounts appear disabled because of the userPrincipal.Enabled check.
However, if I leave this out and just rely on the account expiration date and the account lockout properties, then I may miss someone who is disabled using the checkbox in Active Directory which simply disables the account - without setting the account expiration date.
All the accounts where enabled returns false are actually active accounts who can log in to the domain.
How do you check if an account is actually enabled or not?
I ran into a similar issue, and was equally perplexed!
I initially was using a System.DirectoryServices.DirectorySearcher to search for disabled users. The status of an AD user record (re: disabled, locked out, password expiry, etc) is stored within the UserAccountControl property. You could pass in a filter to the DirectorySearcher to locate, lets says, disabled accounts by specifying the UserAccountControl property as part of the filter.
I was never fond of this approach as it amounted to using a magic string and some magic numbers in order to build the query; for example, this is the filter used to locate disabled accounts:
var searcher = new DirectorySearcher(dirEntry)
{
Filter = "(UserAccountControl:1.2.840.113556.1.4.803:=2)",
PageSize = 50
};
When I switched over to using the UserPrincipal, I was thrilled to see this nice handy "Enabled" property right on the class.. At least until I realized it didn't return the same value that the DirectorySearcher filter would return.
Unfortunately, the only reliable way that I could find to determine if the account was actually enabled was to dig into the underlying DirectoryEntry object, and go inspect the UserAccountControl property directly, ie:
var result = (DirectoryEntry)userPrincipal.GetUnderlyingObject();
var uac = (int)result.Properties["useraccountcontrol"].Value;
var isEnabled = !Convert.ToBoolean(uac & 2);
Note - the UserAccountControl property is a "flags" enum; all possible values for the UserAccountControl property can be found here: https://msdn.microsoft.com/en-us/library/aa772300(v=vs.85).aspx
I ended up building the above snippet into a little extension method; fortunately doing this extra work to retrieve the UserAccountControl property didn't noticeable slow down my AD queries.
I got a solution by expanding the result and going to the base and expanding the base. You will see the enabled property there. Then I right click the expression and add to watch and copy the watch expression to my code.
using (var context = new PrincipalContext(ContextType.Domain, "xyz.com", "Administrator", "xyz123"))
{
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
foreach (var result in searcher.FindAll())
{
DirectoryEntry de = result.GetUnderlyingObject() as DrectoryEntry;
foreach (String key in de.Properties.PropertyNames)
{
Console.WriteLine(key + " : " + de.Properties[key].Value);
}
Console.WriteLine("Enabled: " +((System.DirectoryServices.AccountManagement.AuthenticablePrincipal)(result)).Enabled);
Console.WriteLine("First Name: " + de.Properties["givenName"].Value);
}
}
}
Console.ReadLine();
((System.DirectoryServices.AccountManagement.AuthenticablePrincipal)(result)).Enabled
is working fine to get the enabled true or false in the list of users.
I can tell you what works for me in one of my applications. Here is a snippet from my application:
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "domain.com"))
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(pc, "Doe, John"))
{
Console.Out.Write(user.Enabled);
}
}
This, for me, accurately returns whether the account is enabled or not.

OpenLdap C# bind with escaped characters in Distinguished Name

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);
}

Permitted logon hours on group with Active Directory

I search for a long time how I can define permitted logon hours on group like it is possible to do with user from the account tab in Active Directory.
I already have a class in c# that can make queries to returns a list of all permitted hours of a user with the help 'logonhours' properties.
public byte[] GetLogonHours(string userName, string password, string path)
{
DirectoryEntry entry = this.GetUserAccount(userName, path);
return (byte[])entry.Properties["logonHours"].Value;
}
public DirectoryEntry GetUserAccount(string username, string path)
{
using (DirectoryEntry objRootEntry = new DirectoryEntry(path))
{
using (DirectorySearcher objAdSearcher = new DirectorySearcher(objRootEntry))
{
objAdSearcher.Filter = "(&(objectClass=user)(samAccountName=" + username + "))";
SearchResult objResult = objAdSearcher.FindOne();
if (objResult != null)
{
return objResult.GetDirectoryEntry();
}
}
}
return null;
}
I used this post to help me understanding how I can query the logon hours:
http://anlai.wordpress.com/2010/09/07/active-directory-permitted-logon-times-with-c-net-3-5-using-system-directoryservices-accountmanagement/
It is important to understand that I don't want a feature to know when the last time a user has been logged. What I have is a feature that prevent a user logging at some moments.
What I want is a feature that can apply logon hours for a group of users and I can query the Active Directory with c# to get these logon hours information.
Thank you very much.
In my understanding logon hours information is a user information. As discribed in HOW TO: Limit User Logon Time in a Domain in Windows Server 2003 pointed by #Knite you can change it :
User by user, whatever if you loop on a list of user
Applying a GPO to an organizationalUnit users belongs to
In your case you can loop for all the members of a group and change their logon hours.
According to http://support.microsoft.com/kb/816666 , you should generate the list of users in a group and write their logon hours to a CSV file.

Create Active Directory user in .NET (C#)

I need to create a new user in Active Directory. I have found several examples like the following:
using System;
using System.DirectoryServices;
namespace test {
class Program {
static void Main(string[] args) {
try {
string path = "LDAP://OU=x,DC=y,DC=com";
string username = "johndoe";
using (DirectoryEntry ou = new DirectoryEntry(path)) {
DirectoryEntry user = ou.Children.Add("CN=" + username, "user");
user.Properties["sAMAccountName"].Add(username);
ou.CommitChanges();
}
}
catch (Exception exc) {
Console.WriteLine(exc.Message);
}
}
}
}
When I run this code I get no errors, but no new user is created.
The account I'm running the test with has sufficient privileges to create a user in the target Organizational Unit.
Am I missing something (possibly some required attribute of the user object)?
Any ideas why the code does not give exceptions?
EDIT
The following worked for me:
int NORMAL_ACCOUNT = 0x200;
int PWD_NOTREQD = 0x20;
DirectoryEntry user = ou.Children.Add("CN=" + username, "user");
user.Properties["sAMAccountName"].Value = username;
user.Properties["userAccountControl"].Value = NORMAL_ACCOUNT | PWD_NOTREQD;
user.CommitChanges();
So there were actually a couple of problems:
CommitChanges must be called on user (thanks Rob)
The password policy was preventing the user to be created (thanks Marc)
I think you are calling CommitChanges on the wrong DirectoryEntry. In the MSDN documentation (http://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentries.add.aspx) it states the following (emphasis added by me)
You must call the CommitChanges method on the new entry to make the creation permanent. When you call this method, you can then set mandatory property values on the new entry. The providers each have different requirements for properties that need to be set before a call to the CommitChanges method is made. If those requirements are not met, the provider might throw an exception. Check with your provider to determine which properties must be set before committing changes.
So if you change your code to user.CommitChanges() it should work, if you need to set more properties than just the account name then you should get an exception.
Since you're currently calling CommitChanges() on the OU which hasn't been altered there will be no exceptions.
Assuming your OU path OU=x,DC=y,DC=com really exists - it should work :-)
Things to check:
you're adding a value to the "samAccountName" - why don't you just set its value:
user.Properties["sAMAccountName"].Value = username;
Otherwise you might end up with several samAccountNames - and that won't work.....
you're not setting the userAccountControl property to anything - try using:
user.Properties["userAccountControl"].Value = 512; // normal account
do you have multiple domain controllers in your org? If you, and you're using this "server-less" binding (not specifying any server in the LDAP path), you could be surprised where the user gets created :-) and it'll take several minutes up to half an hour to synchronize across the whole network
do you have a strict password policy in place? Maybe that's the problem. I recall we used to have to create the user with the "doesn't require password" option first, do a first .CommitChanges(), then create a powerful enough password, set it on the user, and remove that user option.
Marc
Check the below code
DirectoryEntry ouEntry = new DirectoryEntry("LDAP://OU=TestOU,DC=TestDomain,DC=local");
for (int i = 3; i < 6; i++)
{
try
{
DirectoryEntry childEntry = ouEntry.Children.Add("CN=TestUser" + i, "user");
childEntry.CommitChanges();
ouEntry.CommitChanges();
childEntry.Invoke("SetPassword", new object[] { "password" });
childEntry.CommitChanges();
}
catch (Exception ex)
{
}
}

How to get a user's e-mail address from Active Directory?

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.

Categories

Resources