Active Directory lastLogon only changes after two unsuccessful attempts - c#

I'm able to validate a user by forcing a bind on a DirectoryEntry object like so:
public bool ValidateUser(string username, string password)
{
using (var directoryEntry = new DirectoryEntry("LDAP:" + "//" + server +
"/" + containerDistinguishedName,
domain + #"\" + username, password,
AuthenticationTypes.Secure))
{
// invoke a bind to the directory entry
try
{
var obj = directoryEntry.NativeObject;
return true;
}
catch
{
return false;
}
}
}
This is similar to the previous way I was doing this here:
PrincipalContext.ValidateCredentials doesn't set lastLogon date for
user
However, I was unable to get that working and I eventually started decompiling some other code which led me to write this.
When a user is validated with this code, the lastLogon Active Directory attribute is not set regardless of how many successful validations they go through. One strange feature (which I have specified in the title of this question) is that two failed attempts and then one successful login will update the lastLogon value in the directory.
If the user fails to login once but then succeeds on the second attempt, the value isn't updated. It's only specifically after two failures that their lastLogon attribute updates.
Does anybody know why this is?

Related

ldapUser corresponds to which filed in Active Directory?

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.

C# Nested impersonation - impersonate a user whilst already impersonating another

I have a fairly odd requirement to be able to impersonate a user, when I'm already impersonating another, using C#.
I'm writing an app to allow the management of Active Directory users. This app will provide the ability for anyone in the company to view and maintain certain details about themselves (some of which will not actually be saved to Active Directory, but some of which will), for managers to be able to view and maintain details about their team, and for HR to be able to view and maintain details about anyone.
For obvious reasons I don't want to develop or test this against the live domain. We have recently ported all users over to this domain from another domain, which means I can actually test against the old domain without affecting anything. However, to enable me to do this I have to impersonate my old account on the old domain, which I do on loading the application.
Although for me everything will work fine as I'm setup as a domain admin, going forward obviously not all users will be domain admins, and won't be able to write to AD under their own account, and therefore we have another domain admin user setup specifically for this application, whenever data needs to be saved to AD that user is impersonated. This was working great before when I was testing against an Active Directory I'd setup on a virtual machine because I was logging onto the local domain, however that didn't allow me to step through the code in Visual Studio so debugging was slow, and hence I've stopped using that virtual machine and am using this old domain. Now I'm already impersonating another user (i.e. my old domain account), when it then tries to impersonate the domain admin user it fails with an "System.Security.SecurityException: Access is denied." exception. The line this fails on is just writing out some debugging information using "WindowsIdentity.GetCurrent().Name".
If I change my code so I'm actually logging in using the new domain admin rather than my old account, the first time it goes through it logs in successfully (so the credentials are correct), however when it then goes through and tries to do the same again to write to AD it fails with the above exception. Therefore I think it must be a problem with trying to do a nested impersonate.
Is it possible to do a nested impersonate?
Below is the code I'm using:
private static WindowsImpersonationContext ImpersonateUser(out string result, string sUsername,
string sDomain, string sPassword)
{
// initialize tokens
var pExistingTokenHandle = new IntPtr(0);
var pDuplicateTokenHandle = new IntPtr(0);
// if domain name was blank, assume local machine
if (sDomain == "")
{
sDomain = Environment.MachineName;
}
try
{
result = null;
const int logon32ProviderDefault = 0;
// create token
const int logon32LogonInteractive = 2;
// get handle to token
var bImpersonated = LogonUser(sUsername, sDomain, sPassword,
logon32LogonInteractive,
logon32ProviderDefault,
ref pExistingTokenHandle);
// did impersonation fail?
if (!bImpersonated)
{
var nErrorCode = Marshal.GetLastWin32Error();
result = "LogonUser() failed with error code: " + nErrorCode + "\r\n";
}
// Get identity before impersonation
result += string.Format("Before impersonation: {0}\r\n", WindowsIdentity.GetCurrent().Name);
var bRetVal = DuplicateToken(pExistingTokenHandle, (int)SecurityImpersonationLevel.SecurityImpersonation,
ref pDuplicateTokenHandle);
// did DuplicateToken fail?
if (bRetVal)
{
// create new identity using new primary token
var newId = new WindowsIdentity(pDuplicateTokenHandle);
var impersonatedUser = newId.Impersonate();
// check the identity after impersonation
result += "After impersonation: " + WindowsIdentity.GetCurrent().Name + "\r\n";
return impersonatedUser;
}
else
{
var nErrorCode = Marshal.GetLastWin32Error();
CloseHandle(pExistingTokenHandle); // close existing handle
result += "DuplicateToken() failed with error code: " + nErrorCode + "\r\n";
return null;
}
}
finally
{
// close handle(s)
if (pExistingTokenHandle != IntPtr.Zero)
{
CloseHandle(pExistingTokenHandle);
}
if (pDuplicateTokenHandle != IntPtr.Zero)
{
CloseHandle(pDuplicateTokenHandle);
}
}
}
When this is called for the nested impersonation which fails, "bImpersonated" is actually "true", as is bRetVal, which suggests its worked, however when it gets to "WindowsIdentity.GetCurrent().Name" it fails with the exception above.
I hope this makes sense, and would appreciate any assistance.

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