C# PrincipalContext only changes password for some users, not all - c#

I'm trying to change the AD password of all members in my system, but my code only changes the password for some members successfully. For members whose password can't be changed, it shows the following error:
System.NullReferenceException: Object reference not set to an instance of an object. at changep1.changep2.changeUserPassword(String _userID, String _oldPassword, String _newPassword) in C:\Users\Intern\source\repos\changep1\changep1\changep2.aspx.cs:line 52
Here is my c# code:
public string changeUserPassword(string _userID, string _oldPassword, string _newPassword)
{
string message="";
try
{
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain, "extra.sales-comm.local", "DC=sales-comm,DC=local",
ContextOptions.SimpleBind, #"admin", "Passw#rd");
UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(oPrincipalContext, _userID);
oUserPrincipal.ChangePassword(_oldPassword, _newPassword);
oUserPrincipal.Save();
}
catch (Exception e)
{
message = e.ToString();
}
return message;
}
I don't understand why my code doesn't change passwords for all AD members. Please help thanks.

Your code needs to check if the UserPrincipal value while finding the identity is null, in case if the passed user-id is not found. You've not checked the same in your code. This looks like the reason why it is giving a Null Pointer Exception.
Read the docs for the method UserPrincipal.FindByIdentity Method (PrincipalContext, String):
Returns a user principal object that matches the specified identity
value.
Parameters
context Type: System.DirectoryServices.AccountManagement.PrincipalContext
The PrincipalContext that specifies the server or domain against which
operations are performed.
identityValue Type: System.String
The identity of the user principal.
This parameter can be any format that is contained in the IdentityType
enumeration.
... // IdentityType Enumeration members are listed below:
Member name----------------------- Description
DistinguishedName-------------- The identity is a
Distinguished Name (DN).
Guid ---------------------- The identity is a Globally Unique Identifier (GUID).
Name ---------------- The identity is a name.
SamAccountName--------------- The identity is a Security Account Manager (SAM) name.
Sid ------------- The identity is a Security Identifier (SID) in Security
Descriptor Definition Language (SDDL) format.
UserPrincipalName The identity is a User Principal Name (UPN).
Do as shown below:
try
{
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain, "extra.sales-comm.local", "DC=sales-comm,DC=local", ContextOptions.SimpleBind, #"admin", "Passw#rd");
UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(oPrincipalContext, _userID);
if ( null != oUserPrincipal){
oUserPrincipal.ChangePassword(_oldPassword, _newPassword);
oUserPrincipal.Save();
}
else {
// return the message that the user-id could not be found.
// preferably passed argumnet in user-id should be **SamAccountName**
// please make sure that the user-id corresponds to the members mentioned above
}
}
catch (Exception e)
{
message = e.ToString();
}

Related

Cannot connect to AD LDS when using AuthenticationTypes.Secure with DirectoryEntry in C#

I set up an instance of Active Directory Lightweight Directory Services (AD LDS) on my machine, then created a user and assigned it to the "Administrators" role.
I'm trying connect to the instance via LDAP in C# with the DirectoryEntry class and can't figure out why the AuthenticationTypes.Secure flag doesn't work when passed in the DirectoryEntry constructor.
I created the test code below to try out all possible AuthenticationTypes values.
string query = "LDAP://<IP_ADDRESS_HERE>:389/CN=Users,CN=Container1,DC=Test,DC=COM";
string username = "test.user";
string password = "the_password";
foreach (AuthenticationTypes authType in Enum.GetValues(typeof(AuthenticationTypes)))
{
Console.WriteLine(authType.ToString());
try
{
using (DirectoryEntry entry = new DirectoryEntry(query, username, password, authType))
{
using (DirectorySearcher search = new DirectorySearcher(entry))
{
search.Filter = $"(SAMAccountName={username})";
var result = search.FindOne();
Console.WriteLine("\tSuccess!");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"\t{ex}");
}
}
For AuthenticationTypes values None, ReadonlyServer, FastBind, Signing, Sealing, Delegation, and ServerBind, I'm able to authenticate and successfully query the AD LDS instance.
Encryption/SecureSocketsLayer and Anonymous fail as expected (with "the server is not operational" and "an operations error occurred" exceptions respectively).
Secure fails with an error message stating "the user name or password" is incorrect, but I feel like this should work? Am I doing something wrong?

PrincipalContext.ValidateCredentials doesn't set lastLogon date for user

I'm validating users in an Active Directory store as follows:
// using System.DirectoryServices.AccountManagement;
// located in System.DirectoryServices.AccountManagement.dll
using (var context = new PrincipalContext(ContextType.Domain, server, container,
ContextOptions.Negotiate, validateUsername, validatePassword))
{
var valid = context.ValidateCredentials(validateUsername, validatePassword);
if (valid)
{
Console.WriteLine("SUCCESS!");
using (var userContext = UserPrincipal.FindByIdentity(context,
IdentityType.SamAccountName, validateUsername))
{
Console.WriteLine("LastLogon = " + userContext.LastLogon);
}
}
else
Console.WriteLine("FAILED!");
}
The validation is successful, but the lastLogon value is never changed. It's essential that this value is changed when we authenticate a user in code due to other software using this value. I know ActiveDirectoryMembershipProvider authentication changes this property, so I'm wondering if there's a way I can use PrincipalContext (to reuse AD connections) but perform this validation to change the lastLogon value.
Use lastLogonTimestamp. This is the field that gets updated in AD when you're attempting to connect via a PrincipalContext object.

UserPrincipal.FindByIdentity throws exception - There is no such object on the server

I'm struggling with a simple scenario: I would like to retrieve my account from Active Directory using the username and password which I use to log into my computer.
My first issue was that I was receiving a referral from the server when attempting to call UserPrincipal.FindByIdentity. I thought that this was a bit weird, given the fact that PrincipalContext.ValidateCredentials was working fine, but it turns out that my DC path was incorrect.
I wasn't sure how to properly craft my OU/DC string. As such, I found this SO post
which helpful provided the following bit of code:
private static string GetDomainControllerString()
{
string pdc;
using (var context = new PrincipalContext(ContextType.Domain))
{
string server = context.ConnectedServer; // "pdc.examle.com"
string[] splitted = server.Split('.'); // { "pdc", "example", "com" }
IEnumerable<string> formatted = splitted.Select(s => String.Format("DC={0}", s));// { "DC=pdc", "DC=example", "DC=com" }
string joined = String.Join(",", formatted); // "DC=pdc,DC=example,DC=com"
// or just in one string
pdc = String.Join(",", context.ConnectedServer.Split('.').Select(s => String.Format("DC={0}", s)));
}
return pdc;
}
After using this code to properly generate my DC string, my error message changed. Now, I am receiving the error "There is no such object on the server." I suspect the issue is either with my OU or how I am calling FindByIdentity.
Here is the location of my user account which I am trying to retrieve:
And here is how I am attempting to access said user:
private static void Main(string[] args)
{
const string Domain = "SLO1.Foo.Bar.biz";
const string DefaultOU = "OU=Users,DC=SLO1,DC=Foo,DC=Bar,DC=biz";
const string username = #"sanderso";
const string password = "**********";
var principalContext = new PrincipalContext(ContextType.Domain, Domain, DefaultOU, ContextOptions.Negotiate, username, password);
bool areCredentialsValid = principalContext.ValidateCredentials(username, password, ContextOptions.Negotiate);
if (areCredentialsValid)
{
UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(principalContext, username);
}
}
I have also tried calling:
UserPrincipal.FindByIdentity(principalContext, IdentityType.Name, "Sean Anderson");
UserPrincipal.FindByIdentity(principalContext, "Sean Anderson");
these were equally unsuccessful.
I belive the object that does not exist is:
"OU=Users,DC=SLO1,DC=Foo,DC=Bar,DC=biz"
Users is a container, not an OU. So correcty you need:
"CN=Users,DC=SLO1,DC=Foo,DC=Bar,DC=biz"
This Code should work for you Sean
I work on AD for BOA currently and use this many times..
public bool UserExists(string username)
{
// create your domain context
PrincipalContext domain = new PrincipalContext(ContextType.Domain);
// find the user
UserPrincipal foundUser = UserPrincipal.FindByIdentity(domain, IdentityType.Name, username);
return foundUser != null;
}
from MSDN what each parameter is see the list below
Parameters
context
Type: System.DirectoryServices.AccountManagement.PrincipalContext
The PrincipalContex that specifies the server or domain against which operations are performed.
identityType
Type: System.DirectoryServices.AccountManagement.IdentityType
A IdentityType enumeration value that specifies the format of the identityValue parameter.
identityValue
Type: System.String
The identity of the user principal. This parameter can be any format that is contained in the IdentityType enumeration.
Return Value
Type: System.DirectoryServices.AccountManagement.UserPrincipal
A UserPrincipal object that matches the specified identity value and type, or null if no matches are found.
UserPrincipal.FindByIdentity Method()

Ldap connection in .net C#

I have an application where I can send emails. Now am asked to use ldap to authenticate the user email. Am very new to this concept. I have been given a ldap server link. No idea how to proceed with that. Any article or hits will be greatly helpful.
Here is the code am trying with
public static UserDetail GetUserDetails(string EmailId, string domainName)
{
UserDetail userDetail = new UserDetail();
try
{
string filter = string.Format("(&(ObjectClass={0})(sAMAccountName={1}))", "person", EmailId);
string[] properties = new string[] { "fullname" };
DirectoryEntry adRoot = new DirectoryEntry("LDAP://" + domainName, null, null, AuthenticationTypes.Secure);
DirectorySearcher searcher = new DirectorySearcher(adRoot);
searcher.SearchScope = SearchScope.Subtree;
searcher.ReferralChasing = ReferralChasingOption.All;
searcher.PropertiesToLoad.AddRange(properties);
searcher.Filter = filter;
SearchResult result = searcher.FindOne();
DirectoryEntry directoryEntry = result.GetDirectoryEntry();
string displayName = directoryEntry.Properties["displayName"[0].ToStrin();
string firstName = directoryEntry.Properties["givenName"][0].ToString();
string lastName = directoryEntry.Properties["sn"][0].ToString();
string emailId = directoryEntry.Properties["mail"][0].ToString();
userDetail.EmailId = emailId;
}
catch (Exception)
{
}
return userDetail;
}
I want to achieve it on click of search button. How do I call the method and pass variables.
If you're on .NET 3.5 or newer, you can use a PrincipalSearcher and a "query-by-example" principal to do your searching:
// create your domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// define a "query-by-example" principal - here, we search for a UserPrincipal
// and with the e-mail of "bruce#example.com"
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.EmailAddress = "bruce#example.com";
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
// try to find that user
UserPrincipal found = srch.FindOne() as UserPrincipal;
if(found != null)
{
// do whatever here - "found" is the user that matched the e-mail given
}
else
{
// there wasn't any user with that e-mail address in your AD
}
If you haven't already - absolutely read the MSDN article Managing Directory Security Principals in the .NET Framework 3.5 which shows nicely how to make the best use of the new features in System.DirectoryServices.AccountManagement. Or see the MSDN documentation on the System.DirectoryServices.AccountManagement namespace.
Of course, depending on your need, you might want to specify other properties on that "query-by-example" user principal you create:
DisplayName (typically: first name + space + last name)
SAM Account Name - your Windows/AD account name
User Principal Name - your "username#yourcompany.com" style name
You can specify any of the properties on the UserPrincipal and use those as "query-by-example" for your PrincipalSearcher.
Given the input of emailAddress (type string) this code will search the LDAP directory for a user with a matching email address and return some information on the user:
string fullName = string.Empty;
string givenName = string.Empty;
string distinguishedName = string.Empty;
string sAMAccountName = string.Empty;
using (var context = new PrincipalContext(ContextType.Domain, "DOMAIN"))
{
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
foreach (Principal result in searcher.FindAll())
{
var de = result.GetUnderlyingObject() as DirectoryEntry;
if (de.Properties["cn"].Value.ToString().Contains(" "))
{
//var userEntry = new DirectoryUser(de.Properties["sAMAccountName"].Value.ToString());
var currentUserEmail = de.Properties["mail"].Value.ToString().ToLower();
if (currentUserEmail == emailAddress)
{
if (de.Properties["cn"].Value != null)
fullName = de.Properties["cn"].Value.ToString();
if (de.Properties["givenName"].Value != null)
givenName = de.Properties["givenName"].Value.ToString();
if (de.Properties["distinguishedName"].Value != null)
distinguishedName =de.Properties["distinguishedName"].Value.ToString();
if (de.Properties["sAMAccountName"].Value != null)
sAMAccountName = de.Properties["sAMAccountName"].Value.ToString();
}
}
}
}
}
It requires a reference to :
System.DirectoryServices;
System.DirectoryServices.AccountManagement;
One caveat I would like to mention is, directory look up routines can be quite slow. If you have 100,000 users on your domain, this process will take a while to run. WHat I tend to do, is dump the output of a directory search to a database table on a regular basis, and perform any lookups on that table. The frequency of the database dumps will of course depend on your business logic. Sometimes I simply truncate the table before performing a new dump, and in other circumstances, I dump to a 'staging' table, and only apply 'delta' updates to the active directoy record table.
Connect to the directory server, using SSL if possible. Promoting a non-secure connection to a secure connection with the StartTLS extended operation is also possible.
Transmit a SEARCH request to the server that contains the base DN from which the client wishes the search to begin, the scope of the search (base, one-level, or sub-tree), a filter using the information the LDAP client knows that will narrow the search results to the desired user, and the attribute 1.1.
The server will respond with the a SEARCH response containing the number of entries that matched the search request parameters and the distinguished names of each entry that matched.
Transmit a BIND request to the directory server over the secure connection. The BIND request contains a distinguished name and the credentials for the distinguished name
The directory server will verify the credentials and return a BIND response with an integer result code indicating whether the credentials matched those stored in the server database
see also
LDAP: programming practices
LDAP: search practices

Active Directory PrincipalContext.ValidateCredentials domain disambiguation

I'm dealing with two domains - one is a trusted domain. There may be a JohnSmith on one domain and another JohnSmith on the other. Both of these people need to log into my application.
My problem: it doesn't matter which domain I pass in - this code returns true! How do I know which JohnSmith is logging in?
static public bool CheckCredentials(
string userName, string password, string domain)
{
using (var context = new PrincipalContext(ContextType.Domain, domain))
{
return context.ValidateCredentials(userName, password);
}
}
The ValidateCredentials works with userPrincipalName you perhaps can try to build the first parameter (username) combining the login and the domain to create the username JohnSmith#dom1.com versus JohnSmith#dom2.com.
You can always retrieve the full DN of the user who has logged in using
UserPrincipal up = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, userName);
up.UserPrincipalName // shows user#domain.com
up.DistinguishedName // shows CN=Surname,OU=group,DC=domain,DC=com
up.SamAccountName // shows login name
Use the up.SamAccountName to subsequent calls to ValidateCredentials including the domain name - you can't have 2 users who log in using the same sAMAccountName after all!
The DistinguishedName will definitely show you which JohnSmith logged in.
Based on JPBlanc's answer, I've re-written my code. I've also added a try/catch in case a bogus domain is passed in.
static public bool CheckCredentials(
string userName, string password, string domain)
{
string userPrincipalName = userName + "#" + domain + ".com";
try
{
using (var context = new PrincipalContext(ContextType.Domain, domain))
{
return context.ValidateCredentials(userPrincipalName, password);
}
}
catch // a bogus domain causes an LDAP error
{
return false;
}
}
The accepted answer will fail with Domains that contain different email addresses within them. Example:
Domain = Company
User1 = employee#department1.com (under company Domain)
User2 = employee2#Department2.com (under company Domain)
The provided answer will return false using:
userName = "employee";
domain = "company";
string userPrincipalName = userName + "#" + domain + ".com";
The correct way to encompass users across domains is:
string userPrincipalName = userName + "#" + domain;
without the .com portion it searches the user AT that domain instead of searching for an email within a global domain.

Categories

Resources