I have the following code as part of a web application for my Active Directory users to be able to update their passwords (for active directory and gmail at the same time). I am using C# with System.DirectoryServices.AccountManagement.
This code worked until yesterday
try
{
State.log.WriteLine("Connecting LDAP.");
string ldapPath = "LDAP://192.168.76.3";
DirectoryEntry directionEntry = new DirectoryEntry(ldapPath, domainName + "\\" + userName, currentPassword);
if (directionEntry != null)
{
DirectorySearcher search = new DirectorySearcher(directionEntry);
State.log.WriteLine("LDAP Connected, searching directory for SAMAccountName");
search.Filter = "(SAMAccountName=" + userName + ")";
SearchResult result = search.FindOne();
if (result != null)
{
State.log.WriteLine("Getting User Entry.");
DirectoryEntry userEntry = result.GetDirectoryEntry();
if (userEntry != null)
{
State.log.WriteLine("Setting Password");
if (force)
{
userEntry.Invoke("SetPassword", new[] { newPassword });
}
else
{
userEntry.Invoke("ChangePassword", new object[] { currentPassword, newPassword });
}
userEntry.CommitChanges();
State.log.WriteLine("Changes Committed to ActiveDirectory.");
}
else
{
State.log.WriteLine("Could not get user Entry...");
}
}
else
{
State.log.WriteLine("Search returned no results.");
}
}
else
{
State.log.WriteLine("Could not connect to LDAP with given username and passwd");
}
}
Since yesterday, this code makes it to the line:
userEntry.Invoke("ChangePassword", new object[] { currentPassword, newPassword });
and then throws the following exception:
[8:37:00 AM] : Password Requirements Met.
[8:37:00 AM] : Connecting LDAP.
[8:37:00 AM] : LDAP Connected, searching directory for SAMAccountName
[8:37:01 AM] : Getting User Entry.
[8:37:01 AM] : Setting Password
[8:37:01 AM] : Failed to reset Windows Password for jason.
Exception has been thrown by the target of an invocation.
The system cannot contact a domain controller to service the authentication request. Please try again later. (Exception from HRESULT: 0x800704F1)
The "force" option using "SetPassword" still works just fine, but the "ChangePassword" method which can be invoked by non-administrator users does not.
Change userPrincipal.ChangePassword("Old pass", "New Pass"); to userPrincipal.SetPassword(model.NewPassword);
I found a work-around and forgot to post it. What I did was use the code above to authenticate the user and then just call my "ForceChangePassword" method:
public static void ForceChangeADPassword(String username, String newPassword)
{
String DN = "";
try
{
DN = GetObjectDistinguishedName(objectClass.user, returnType.distinguishedName, username, DOMAIN_CONTROLLER_IP);
}
catch(Exception e)
{
throw new PasswordException(String.Format("Could not find AD User {0}", username), e);
}
if(DN.Equals(""))
throw new PasswordException(String.Format("Could not find AD User {0}", username));
DirectoryEntry userEntry = new DirectoryEntry(DN.Replace("LDAP://", LdapRootPath), "accounts", AcctPwd);
userEntry.Invoke("SetPassword", new object[] { newPassword });
userEntry.Properties["LockOutTime"].Value = 0;
userEntry.CommitChanges();
userEntry.Close();
}
Earlier this month Microsoft released a security patch, resolving some vulnerabilities in the area of password change. Specifically, the update blocked fallback to NTLM authentication after a failed Kerberos authentication when changing a password.
You might want to read more about the update here.
Microsoft has updated this article: https://support.microsoft.com/en-us/kb/3177108 . Here they have given us problems created by the original "fixes" as well as some tips for working with Kerberos and self-service password reset.
As of October 11, 2016 Microsoft re-released the patches associated with https://technet.microsoft.com/en-us/library/security/ms16-101.aspx to resolve issues caused by the original updates (which you can read in https://support.microsoft.com/en-us/kb/3177108 including the fact that you could no longer change passwords on local accounts).
Related
I'm hoping someone can explain this to me because I am at my wits end trying to resolve an issue I am having.
The error I am receiving is, "The object already exists." whenever I am trying to run our ResetPassword function. The weird thing is I ONLY receive this error if the user account has had their password reset before. If I either create a new account with a new password, or search for a user in the database that has not had the ResetPassword function called on their account, then it will let me call this function once. Note: On this one time it lets me run the function the password does get reset. Any account that has already run ResetPassword will prompt the object already exists error.
public static void ResetPassword(DirectoryEntry User, string password)
{
User.Invoke("SetPassword", new object[] { password });
User.Properties["LockOutTime"].Value = 0x0000; //unlock account
int val = (int)User.Properties["userAccountControl"].Value;
User.Properties["userAccountControl"].Value = val & ~0x2; //Enable account
User.CommitChanges();
Logs.CreateLogEntry("Reset password", User);
User.Close();
}
As you can see, we are passing in a DirectoryEntry user, along with a generated new password. We are using an anonymous login on the backend of our IIS website to have admin credentials high enough to use SetPassword.
You need to provide credentials of a user in AD that has admin privileges to reset passwords.
public static void ResetPassword(string username, string password)
{
string adminUser = "YourAdminUserIdInAD";
string adminPass = "YourAdminUserPasswordInAD";
string ldapString = "LDAP://YourLDAPString";
DirectoryEntry dEntry = new DirectoryEntry(ldapString , adminUser, adminPass, AuthenticationTypes.Secure);
DirectorySearcher deSearch = new DirectorySearcher(dEntry) {
SearchRoot = dEntry,
Filter = "(&(objectCategory=user)(cn=" + username + "))"
};
var directoryEntry = deSearch.FindOne();
directoryEntry.Invoke("SetPassword", new object[] { password });
directoryEntry.Properties["LockOutTime"].Value = 0x0000; //unlock account
int val = (int)directoryEntry.Properties["userAccountControl"].Value;
directoryEntry.Properties["userAccountControl"].Value = val & ~0x2;
directoryEntry.CommitChanges();
Logs.CreateLogEntry("Reset password", User);
directoryEntry.Close();
}
I am connecting successfully to the LDAP with PHP, tried a whole lot of things but when I try with C# am always getting either "Server is not operational" or "The LDAP server in unavailable".
Here is the PHP code:
<?php
function login($username='user', $password='pass') {
if (empty($username) || empty($password)) {
throw new BadCredentialsException('Invalid username or password.');
}
if (($ldap = #ldap_connect($url = 'ldap://ds.xyz-example.com', $port = 636)) === false) {
echo('Error connecting LDAP server with url %s on port %d.');
}
if (!#ldap_bind($ldap, sprintf($dn='uid=%s,ou=People,dc=xyz-example,dc=com', $username), $password)) {
$error = ldap_errno($ldap);
if ($error === 0x31) {
echo('Invalid username or password.');
} else {
echo('error during authentication with LDAP.');
}
}
return true;
}
login(); // call the function
?>
This is working perfect but I need it with C#. How can I do this with C# using the port and the dn and the user and pass?
Here is what I tried with C# but with an error "Server is not operational"
string ldapPath = "LDAP://ds.xyz-example.com:636/UID=user,OU=People,DC=xyz-example,DC=com";
string user = "user";
string password = "pass";
DirectoryEntry deSSL = new DirectoryEntry(ldapPath, user, password, AuthenticationTypes.SecureSocketsLayer);
try
{
user = deSSL.Properties["uid"][0].ToString(); //if this works, we bound successfully
Response.Output.Write("Success... {0} has bound", user);
}
catch (Exception ex)
{
Response.Output.Write("Bind Failure: {0}", ex.Message);
}
Thanks in advance!
Could it be your library doesn't implement LDAP but rather a weird non-standard Microsoft version of LDAP called ActiveDirectory, which only works when the server is an actual ActiveDirectory Server and doesn't quite work as easily when you use non-microsoft servers, such as OpenLDAP?
Could it be?
We are using below code to authenticate user credentials,
string domainAndUsername = this.activeDirectoryConfiguration.Domain + #"\" + username;
DirectoryEntry entry = null;
try
{
entry = new DirectoryEntry(
this.activeDirectoryConfiguration.LdapPath,
domainAndUsername,
password);
// Bind to the native AdsObject to force authentication.
object nativeObject = entry.NativeObject;
if (nativeObject == null)
{
return AuthenticationDetails.InvalidCredentials;
}
}
catch (DirectoryServicesCOMException directoryServicesComException)
{
return this.CheckErrorResponse(username, directoryServicesComException);
}
finally
{
if (entry != null)
{
entry.Dispose();
}
}
This code works fine when using NTLM authentication. Here we are checking error code we get from the 'DirectoryServicesCOMException' to identify whether account is locked or disabled or if the password is expired.
In our production environment, kerberos authentication is used where this code is failing. It throws System.Runtime.InteropServices.COMException which doesn't have detailed description about the failure. Every time it just throws
Message=Logon failure: unknown user name or bad password.
Can anyone suggest why isn't Kerberos giving detailed exception or is there any way to identify various login failures while using kerberos?
I have attempted this with not much success. Basically I need to login to Exchange using EWS remotely.
The issue is I don't know if the user has logged in OK or if the credentials are wrong as I get nothing back! If I provide wrong credentials the software just carries on!
Is there something I'm missing, I've checked the MSDN stuff about EWS which shows you how to connect to exchange but nothing about validating credentials!
Below is the code I currently have to connect.
public void connect(string Email, string Password)
{
try
{
_useremail = Email;
_userpass = Password;
// Define the credentials to use.
var credentials = new WebCredentials(_useremail, _userpass);
_ExchangeServer = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
_ExchangeServer.Credentials = credentials;
_ExchangeServer.Url = new Uri(_ExchangeEWSURL);
_ExchangeServer.Timeout = 60;
_ExchangeServer.KeepAlive = true;
_ExchangeConnected = true;
}
catch (Exception ex)
{
_ExchangeConnected = false;
throw ex;
}
}
as you can see at present I just set a bool value to true in the class. Any ideas?
In order to check whether the given credentials are valid, you must query resources you expect the user to have access to (calendar, inbox, contacts, etc.). There is no explicit login method - the authentication occurs implicitly when you request user resources (via FindItems, FindFolders, FindAppointments, etc.).
Hi i want to create a system user in window server 2003 active directory for which i use following C# code ,
DirectoryEntry AD = new DirectoryEntry("WinNT://"+Environment.MachineName+",computer");
DirectoryEntry NewUser = AD.Children.Add(username, "user");
NewUser.Invoke("SetPassword", new object[] { password });
NewUser.Invoke("Put", new object[] { "Description", "Test User from .NET"});
NewUser.CommitChanges();
DirectoryEntry grp;
grp = AD.Children.Find("Guests", "group");
if (grp != null)
{
grp.Invoke("Add", new object[] { NewUser.Path.ToString() });
}
this code makes user when i run this application locally on visualy studio web engine but when i deploy this application on iis on window server 2003 this will give exception
Exception:
General Access Denied Error
kindly help me what im doing wrong which permission is required to create user in window server and how we give this perssion. thanks.
Look at System.DirectoryServices.AccountManagement namespace. Utilizing classes from it you can do this in such way:
using (var context = new PrincipalContext(ContextType.Machine, "MachineName", "AdminUserName", "AdminPassword"))
{
UserPrincipal user = new UserPrincipal(context, username, password, true);
user.Description = "Test User from .NET";
user.Save();
var guestGroup = GroupPrincipal.FindByIdentity(context, "Guests");
if (guestGroup != null)
{
guestGroup.Members.Add(user);
guestGroup.Save();
}
}
Also, you may configure impersonation on your application and skip all parameters in the PrincipalContext constructor exception the first one if this functionality allowed for administrators only.