Check Password Reset on Active Directory Server - c#

I need to reset windows password of any user through my .Net application. I am using the user's username to get its Directory Entry from AD server. I got these two different methods for changing password :
entry.Invoke("ChangePassword", oldPass, newPass);
&
entry.Invoke("SetPassword", "pass#123");
But I am getting the following error when am trying these methods on live AD server :
Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
I have 2 AD servers. One of them is live and another is for testing purpose. I just want to check if my code is working or not. Since, access is denied on live server I can not change and check later my own password through code.
And if I am using the test AD server to change password, I don't know how to check whether the pasword is changed or not.
Kindly give any suggestions to check if my code is working properly or not.
Thanks in advance.

I think you're not getting a proper context setup before you call the invoke. Here's what I use for something similar. You'll need to set your own variables:
I'm using System.DirectoryServices.AccountManagement to get the functions.
//Domain related info
string _DCToUse = "myserver.domain.local";
string _ADConDomain = "DC=domain,DC=local";
string _AdDomain = "domain";
string _ADAdminUser = "administrator";
string _ADAdminPass = "password";
//User specific
string _UserName = "jsmith";
string _CurrentPass = "oldPass";
string _NewPass = "newPass";
PrincipalContext principalContext =
new PrincipalContext(ContextType.Domain, _DCToUse,
_ADConDomain, _ADDomain+#"\"+_ADAdminUser, _ADAdminPass);
UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, _UserName);
if (user == null)
{
string ADErrorMsg = "Couldn't find user, check your spelling.";
return Changed;
}
user.ChangePassword(oldPass, newPass);
user.Save();

Related

Converting username to SID in C#

I'm trying to use this code to convert a Windows username (in the classic .\username form) to a SID object:
NTAccount account = new NTAccount(".\\MyUser");
SecurityIdentifier sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
However, I keep getting the following exception when executing the last instruction:
System.Security.Principal.IdentityNotMappedException: 'Some or all
identity references could not be translated.'
What am I doing wrong?
Answering my own question after some trial and error:
The code is correct, but the Translate function doesn't seem to support the shorthand . to indicate the account is local and not in a domain. So in case you have a username that starts with .\ you need to replace the dot with the machine name. The following code works correctly:
public static SecurityIdentifier usernameToSid(string user)
{
if (user.StartsWith(#".\"))
{
user = user.Replace(#".\", Environment.MachineName + #"\");
}
NTAccount account = new NTAccount(user);
return (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
}

WindowsIdentity: Exception: System.Security.SecurityException: The user name or password is incorrect

On Windows Server 2012 R2 we want to get the Security Groups of an user from the Active Directory by C#-code. The application is an ASP.NET MVC5-project and is hosted by an IIS.
At first, we query the user account from the Active Directory by UserPrincipal.FindByIdentity(...). That works fine. At next, we use the WindowsIdentity class to query the Security Groups from the active directory. We use the class WindowsIdentity because the response is very fast.
Here is the code:
var lDomain = "MyDomain";
var lSamAccountName = "Simon";
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, lDomain))
{
// Get User Account from Active Directory
using (UserPrincipal lUserPrincipal = UserPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, lSamAccountName))
{
var lUpn = lUserPrincipal.UserPrincipalName; // get UPN
// get Security Groups of the user
using (WindowsIdentity lWindowsIdentity = new WindowsIdentity(lUpn)) // Exception: System.Security.SecurityException: The user name or password is incorrect
{
var lGroups = lWindowsIdentity.Groups;
}
}
}
The problem: When instanciating the WindowsIdentity class (and passing the UPN) then an exception occurs:
“Upn: 'simon#MyDomain' Exception: System.Security.SecurityException: The user name or password is incorrect.
at System.Security.Principal.WindowsIdentity.KerbS4ULogon(String upn, SafeAccessTokenHandle& safeTokenHandle)
at System.Security.Principal.WindowsIdentity..ctor(String sUserPrincipalName, String type)
at System.Security.Principal.WindowsIdentity..ctor(String sUserPrincipalName)
at ...
This is curious because the query with UserPrincipal.FindByIdentity(...) was successful. Some accounts are working. Some accounts are not working. I cannot find a difference between the working und not working accounts.
Question: Who knows what I am doing wrong?
Additional Notes:
The Applicationpool Identity is: LocalSystem
In the Web.config, there is the following entry: <identity impersonate="false" />
When I query the groups by the following alternative, then no exception occurs and the result is satisfying. That´s curious:
var lResult = new List<SecurityIdentifier>();
DirectorySearcher lDirectorySearcher = new DirectorySearcher();
lDirectorySearcher.Filter = string.Format(CultureInfo.InvariantCulture, "(&(objectClass=user)(distinguishedName={0}))", lUserPrincipal.DistinguishedName); // for the object lUserPrincipal, look # code above
lDirectorySearcher.SearchScope = SearchScope.Subtree;
SearchResult lSearchResult = lDirectorySearcher.FindOne();
DirectoryEntry lDirectoryEntry = lSearchResult.GetDirectoryEntry();
lDirectoryEntry.RefreshCache(new string[] { "tokenGroups" });
// get groups
for (int i = 0; i < lDirectoryEntry.Properties["tokenGroups"].Count; i++)
{
SecurityIdentifier lSid = new SecurityIdentifier((byte[])lDirectoryEntry.Properties["tokenGroups"][i], 0);
lResult.Add(lSid);
// Translate into NTAccount to get Domain and SAMAccountname etc...
[...]
}
I really do not know if this helps, but somewhere I found this:
If IIS is set to allow anonymous access you can use the userPrincipleName format (username#domain).
If anonymouse access is NOT enabled in IIS you need to use the username in the form domain\username.
Seems weird though that for some accounts the UPN works and for others it does not..
Resolution 1:
Use ADSI Edit (via MMC.exe) to manually populate the userPrincipalName attribute of the desired Windows Service Account.
The format of a userPrincipalName is
<username>#<fully_qualified_domain_name>
For example, for the example log lines above, the expected UPN value should be
goodadmin#example.com
Allow a few minutes for Active Directory replication to complete before attempting to use the service account credentials.
Resolution 2:
Ensure the Active Directory Service Account information has the necessary permissions on the local BEMS host as Local Administrator and Logon As A Service.
Ensure the Active Directory Service Account information entered is actually valid. For example, ensure the password is entered correctly and that the account is not locked out in Active Directory.
Ensure the Active Directory Service Account information has the necessary Active Directory Group Permissions.
RTCUniversalReadOnlyAdmins
Here you can find how to get groups base on the UserPrincipal
How to create WindowsIdentity/WindowsPrincipal from username in DOMAIN\user format
// find all groups the user is member of (the check is recursive).
// Guid != null check is intended to remove all built-in objects that are not really AD gorups.
// the Sid.Translate method gets the DOMAIN\Group name format.
var userIsMemberOf = p.GetAuthorizationGroups().Where(o => o.Guid != null).Select(o => o.Sid.Translate(typeof(NTAccount)).ToString());
// use a HashSet to find the group the user is member of.
var groups = new HashSet<string>(userIsMemberOf), StringComparer.OrdinalIgnoreCase);
groups.IntersectWith(groupNames);

Can not retrieve Active Directory user in C#

I built a test Active Directory server in Window 2008 and I also run the DNS server on it. On my client machine which runs the C# application, I can authenticate the user against the Active directory server using the function below:
public static UserPrincipal GetUserPrincipal(string usrName,string pswd,string domainName)
{
UserPrincipal usr;
PrincipalContext ad;
// Enter Active Directory settings
ad = new PrincipalContext(ContextType.Domain, domainName,usrName,pswd);
//search user
usr = new UserPrincipal(ad);
usr.SamAccountName = usrName;
PrincipalSearcher search = new PrincipalSearcher(usr);
usr = (UserPrincipal)search.FindOne();
search.Dispose();
return usr;
}
In a separate logic I tried to retrieve a user back from the server using a user name. I used the functions below:
public static DirectoryEntry CreateDirectoryEntry()
{
// create AD connection
DirectoryEntry de = new DirectoryEntry("LDAP://CN=Users,DC=rootforest,DC=com","LDAP","password");
de.AuthenticationType = AuthenticationTypes.Secure;
return de;
}
public static ResultPropertyCollection GetUserProperty(string domainName, string usrName)
{
DirectoryEntry de = CreateDirectoryEntry();
DirectorySearcher deSearch = new DirectorySearcher();
deSearch.SearchRoot = de;
deSearch.Filter = "(SamAccountName=" + usrName + ")";
SearchResult results = deSearch.FindOne();
return null;
}
However, I got no response back from the LDAP server at all, not even an exception. Am I missing certain settings on LDAP server, any of you able to see a flaw in my code (pls don't mind the hard code values, I was testing with this code).
As part of my troubleshooting, I confirmed that I can ping to the rootforest.com from the client machine. I confirmed the user with property samaccountname "LDAP" exists. My path seems to be right because when I go onto the LDAP server and type :
dsquery user -name LDAP*
I got the following:
CN=LDAP L. LDAP,CN=Users,DC=rootforest,DC=com
Any help would be greatly appreciated, I've spent most of my day troubleshooting and researching this little bugger and I think it could be something small which I overlooked.
I don't understand why you're using the new PrincipalContext / UserPrincipal stuff in your first example, but fall back to the hard to use DirectoryEntry stuff in your second example.... doesn't really make sense... also: your second function GetUserProperty seems to return null always - typo or not??
Since you're on already using the System.DirectoryServices.AccountManagement (S.DS.AM) namespace - use it for your second task, too! Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
public static ????? GetUserProperty(string domainName, string usrName)
{
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, usrName);
if(user != null)
{
// return what you need to return from your user principal here
}
else
{
return null;
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD:
I think your code have a few problems:
Why do you return null in your GetUserProperty() function? You should return results instead.
The attribute you are using in your search filter is misspelled. Use sSAMAccountName instead. Furthermore extend your query to search only for user accounts. Here is an example: (&(objectCategory=person)(objectClass=user)(sAMAccountName=usrName))
You could also use the UserPrincipal class to search for an identity in Active Directory. The UserPrincipal class provides a static method called FindByIdentity() to search for a user identity.
Hope, this helps.

Creating Active Directory user with password in C#

I'm looking for a way to create Active Directory users and set their password, preferably without giving my application/service Domain Admin privileges.
I've tried the following:
DirectoryEntry newUser = _directoryEntry.Children.Add("CN=" + fullname, USER);
newUser.Properties["samAccountName"].Value = username;
newUser.Properties["userPassword"].Value = password;
newUser.Properties["mail"].Value = email;
newUser.CommitChanges();
The user is created, but it seems the password is never set on the user.
Does anyone have an idea on how to set the user's password initially when creating the user? I know about
.Invoke("SetPassword", new object[] { password })
But that requires my code to be run with Domain Admin privileges. As I don't really see the point to grant my code Domain Admin privileges, just to set the initial password (I also allow user password resets, but those run in the context of that particular user), I am hoping someone has a clever solution that doesn't require me to do so.
Thanks in advance!
You can do this whole process much easier now with System.DirectoryServices.AccountManagement (long as you're on .Net 3.5):
See here for a full rundown
Here's a quick example of your specific case:
using(var pc = new PrincipalContext(ContextType.Domain))
{
using(var up = new UserPrincipal(pc))
{
up.SamAccountName = username;
up.EmailAddress = email;
up.SetPassword(password);
up.Enabled = true;
up.ExpirePasswordNow();
up.Save();
}
}
I'd use #Nick's code (wrapped in using statements so the context and principal are disposed properly). As for privileges, you'll need to at least have enough privileges on the OU where you are creating the user to create and manage objects. I'd create a specific user under which your program will run and give it just enough privileges to do the tasks that it needs in that specific OU and no more.
Yes can also use below code to create bulk of users
DirectoryEntry ouEntry = new DirectoryEntry("LDAP://OU=TestOU,DC=TestDomain,DC=local");
for (int i = 0; i < 10; 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)
{
}
}
The actual attribute for the password is unicodePwd, which requires a specific format that is described in the documentation. But this is how you can do it:
newUser.Properties["unicodePwd"].Value = Encoding.Unicode.GetBytes("\"NewPassword\"");
Doing it this way, you can create the user with a password in one step.

Active Directory Incorrect password attempts double counting

I am using the following C# code to connect to active directory and validate the login,
DirectoryEntry de = new DirectoryEntry();
string username = "myuser", path = "LDAP://addev2.dev.mycompany.com/CN=myuser,DC=dev,DC=mycompany,DC=com", password = "test";
for (int i = 0; i < 4;i++ )
{
try
{
de.AuthenticationType = AuthenticationTypes.Sealing | AuthenticationTypes.Secure | AuthenticationTypes.FastBind;
de.Username = username;
de.Password = password;
de.Path = path;
//de.RefreshCache();
Object obj = de.NativeObject;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
this works fine when the password is correct. However when the password is incorrect this shows as 2 invalid attempts in AD.
So what happens is when the AD admin allows 5 invalid attempts the user is locked out on the 3rd attempt.
when i look in the AD's event log 1 see 2 entries.
1)Pre-authentication failed:
2)Logon attempt by:
MICROSOFT_AUTHENTICATION_PACKAGE_V1_0
Logon account: m0707b#dev.mycompany.com
Source Workstation: WKSXXXX
Error Code: 0xC000006A
Stepping thro the code i see 2 event entries on the line
de.RefreshCache()
I tried using de.NativeObject to see if that would solve the problem. No Dice
Anyone have any pointers?
Finally found the answer to this perplexing issue when you use the format username#domain the IIS app uses 2 calls once using Kerebros and when that fails using NTLM causing a double count The fix is to use the following format for authentication domain\username and that fixed the issue.
http://support.microsoft.com/kb/264678/EN-US/
You might check out the System.DirectoryServices.AccountManagement namespace. You can access an account and then cast one of the methods it has into a DirectoryEntry object. It might get around your double-authentication problem and it's easier to use.

Categories