Using PrincipalContext & PrincipalSearcher to access Active Directory users from separate server - c#

I have the following code to retrieve the current active directory users:
public List<DomainContext> GetADUsers(string term=null)
{
List<DomainContext> results = new List<DomainContext>();
string ADServerName = System.Web.Configuration.WebConfigurationManager.AppSettings["ADServerName"];
using (var context = new PrincipalContext(ContextType.Domain, ADServerName))
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
var searchResults = searcher.FindAll();
foreach (Principal p in searchResults)
{
if (term == null || p.SamAccountName.ToString().ToUpper().StartsWith(term.ToUpper()))
{
DomainContext dc = new DomainContext();
dc.DisplayName = p.DisplayName;
dc.UserPrincipalName = p.UserPrincipalName;
dc.Name = p.Name;
dc.SamAccountName = p.SamAccountName;
results.Add(dc);
}
}
}
return results;
}
My current situation is as follows:
I am working on the development machine, where both the ASP.NET MVC web application and the Active Directory are on the same machine.
I am not passing usernames and password to get the AD users.
So I have the following concerns when I move my ASP.NET MVC web application which contains the above code to my UAT server:
On the UAT server the ASP.NET MVC web application and AD will be on two different machines.
To access the AD from certain machine I might need to send a username and password to the AD server.
So will my original code works if:
The AD and the ASP.NET MVC web application are on different machines?
And if the AD server required passing a user name and password along with?

There are two possibilities.
Either you pass the username/password to the PricipalContext or the identity of the application pool has enough priviledges to query the AD and you don't have to provide username/password then.
If the application server is in the AD, the latter is very convenient as there is no username/password in code and/or configuration files. If on the other hand the application server is not in AD, passing username/password in an explicit way is the only possibility for this to work.

Related

LDAP Queries, Core 2.x, IdSvr, Self Host

Writing a C#, Core 2.1, self-hosted web api to stand up an Identy Sewrver 4 instance...
Trying to use LDAP for (temp) user and role store. Yet, when I run the STS I am working on, I keep getting a "bad password" error when I query LDAP. I am working on my Laptop (in a workgroup), am running a DC in Hyper-V (domain).
I am trying to use System.DirectoryServices.AccountManagement and have a simple search setup as:
// Pass in userName as someone#domain.xyz
// Example: userName = keith#sol3.net
// Domain Controller is found at dc01.sol3.net
public static bool CanSignIn(string userName, string domainName)
{
using (var pc = new PrincipalContext(ContextType.Domain, domainName))
{
UserPrincipal user = null;
try
{
var name = userName.Split('#');
user = UserPrincipal.FindByIdentity(pc, IdentityType.Name, name[0]);
}
catch (Exception ex)
{
Log.Warning(ex, $"Could not find {userName} in Active Directory, Domain: {domainName}!");
}
return user != null;
}
}
I am wondering if:
I need to attach my laptop to the domain?
Using Kestrel is interfering?
Should I run in IIS Express mode?
Should I research how to run under HTTP.SYS?
What path will help here?
TIA
I think you need to manually authenticate using a password in order to query the directory. Using the method you detail in your question there is no way for the calling app to identify itself. If you want the app to run independently of the domain (i.e. not have to run as a domain user on a domain-joined server) then the best way would be to use a raw LdapConnection and bind a username/password NetworkCredential to it.
This example uses a pre-configured service account to query for a user by their primary email address:
var connection = new LdapConnection(ldapServerName);
connection.AuthType = AuthType.Basic;
//Additional connection setup omitted for brevity
connection.Bind(new NetworkCredential(serviceUserName, servicePassword));
var request = new SearchRequest(
baseDistinguishedName,
"(&(objectClass=person)(mail=" + EscapeFilterValue(emailAddress) + "))",
System.DirectoryServices.Protocols.SearchScope.Subtree,
null
);
SearchResponse response = (SearchResponse)_connection.SendRequest(request);

C# Active directory Principal display name is null

I am trying to display full name of user from active directory. It works fine on my local. But when I publish this code to IIS on server it shows display name as null. What may be the issue? My account is using windows authentication.
using (var context = new PrincipalContext(ContextType.Domain))
{
var principal = UserPrincipal.FindByIdentity(context, User.Identity.Name);
var displayName = principal?.DisplayName;
}
I had to use HostingEnvironment.Impersonate() in order to access my AD properties on my IIS server. (It worked without it on local)
using (HostingEnvironment.Impersonate())
{
// your code
}
Also, you'd have to change your Application Pool's identity from "AppPoolIdentity" to "NetworkService".

Validate users of Remote Active Directory in C#

I try to authenticate users belonging to remote ActiveDirectory from my machine, which is not the same domain as the current machine or user domain. There will be no trust between my machine and remote ActiveDirectory machine.
Initial Try
I tried to authenticate a user(Input: sAMAccountName, machine's ipaddress, machine's domain username("Administrator") and machine's password(***). Able to get result that the user with 'sAMAccountName' do exist in ActiveDirectory.
My Requirement:
Imagine that already a user("qwerty") is created in ActiveDirectory
From my local machine, I will have the following information,
a. Remote ActiveDirectory ipaddress
b. Remote ActiveDirectory machine's username and password.
c. Username and password of User "qwerty"
I need to check whether User "qwerty" is present in remote ActiveDirectory's users list and validate whether the password entered is same in ActiveDirectory's Users list
Code I tried:
DirectoryEntry entry = new DirectoryEntry("LDAP://ipaddress/DC=dinesh,DC=com", name, password);
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = "(sAMAccountName=" + name + ")";
try
{
SearchResult adsSearchResult = adsSearcher.FindOne();
isValid = true;
adsEntry.Close();
}
catch (Exception ex)
{
adsEntry.Close();
}
Do I need to create a trust between local machine and remote ActiveDirectory machine before validating Users in a remote ActiveDirectory? If yes please tell how it can be done;
After creating trust, how can I validate Users?
===========================================================================
I am able to use the solution suggested by Rainer, but with a new problem. When I create a new user via C# code from a different machine, then some properties do not set properly.
Does this need to be set compulsorily while creating user?
First some basics (independent of this question)
Authentication
The system checks if Bob is really Bob. In an Active Directory environment, this is usually done with a domain login from the workstation, Bob enters his username and password, and he gets a Kerberos ticket. Later, if he wants to access e.g. a file share on a remote fileserver, he does not need to login anymore, and can access the files without entering username/password.
Authorization
The system checks which resources Bob is allowed to access. Usually Bob is in domain groups, and a group is in the ACL (access control list) of the resource.
If there are multiple trusting domains, Bob needs to login in one domain, and can access resources in all other domains.
This is one of the main reasons using Active Directory: single sign on
Checking if user / password is valid
If you have a username and password and want to check if the password is valid, you have to do a login to the domain. There is no way of just “checking if the password is correct”.
Login means: if there is a security policy “lock account if more than 3 invalid logins”, the account will be locked out checking with wrong password, even if you “only want to check the user+password”.
Using .NET Directory Service functions
I assume here that the process is either run by a human account as a normal program, or the program is a Windows service or a scheduled task which runs under a domain “technical user” account. In this case, you do not need to provide credentials for using the AD functions. If accessing other trusting AD domains, this is also true.
If you want to login to a “foreign domain”, and there is no trust, you need to provide a username+password (as in your code).
"Manually" authenticating a user
Normally, this should not be needed. Example: ASP.NET intranet usage. The user access a web application on the current domain or trusting domain, the authentication is done “in the background” by browser and IIS (if integrated Windows authentication is on). So you never need to handle user passwords in the application.
I don’t see many use cases where a password is handled by code.
One may that your program is a helper tool for storing emergency user accounts/passwords. And you want to check periodically if these accounts are valid.
This is a simple way to check:
using System.DirectoryServices.AccountManagement;
...
PrincipalContext principalContext =
new PrincipalContext(ContextType.Domain, "192.168.1.1");
bool userValid = principalContext.ValidateCredentials(name, password);
One can also use the older, raw ADSI functions:
using System.DirectoryServices;
....
bool userOk = false;
string realName = string.Empty;
using (DirectoryEntry directoryEntry =
new DirectoryEntry"LDAP://192.168.1.1/DC=ad,DC=local", name, password))
{
using (DirectorySearcher searcher = new DirectorySearcher(directoryEntry))
{
searcher.Filter = "(samaccountname=" + name + ")";
searcher.PropertiesToLoad.Add("displayname");
SearchResult adsSearchResult = searcher.FindOne();
if (adsSearchResult != null)
{
if (adsSearchResult.Properties["displayname"].Count == 1)
{
realName = (string)adsSearchResult.Properties["displayname"][0];
}
userOk = true;
}
}
}
If your real requirement is actually a validity check of user+password, you can do it in one of these ways.
However, if it is a "normal application", which just wants to check if the entered credentials are valid, you should rethink your logic. In this case, you better should rely on the single sign on capabilities of AD.
If there are further questions, please comment.
b. Remote ActiveDirectory machine's username and password.
This sounds a bit unclear. I assume you mean "a username and corresponding password in the remote domain".
There is also the concept of a machine account, which is the hostname appended with $. But that's another topic.
Creating new user
Option 1
using (DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://192.168.1.1/CN=Users,DC=ad,DC=local",
name, password))
{
using (DirectoryEntry newUser = directoryEntry.Children.Add("CN=CharlesBarker", "user"))
{
newUser.Properties["sAMAccountName"].Value = "CharlesBarker";
newUser.Properties["givenName"].Value = "Charles";
newUser.Properties["sn"].Value = "Barker";
newUser.Properties["displayName"].Value = "CharlesBarker";
newUser.Properties["userPrincipalName"].Value = "CharlesBarker";
newUser.CommitChanges();
}
}
Option 2
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, "192.168.1.1",
"CN=Users,DC=ad,DC=local", name, password))
{
using (UserPrincipal userPrincipal = new UserPrincipal(principalContext))
{
userPrincipal.Name = "CharlesBarker";
userPrincipal.SamAccountName = "CharlesBarker";
userPrincipal.GivenName = "Charles";
userPrincipal.Surname = "Barker";
userPrincipal.DisplayName = "CharlesBarker";
userPrincipal.UserPrincipalName = "CharlesBarker";
userPrincipal.Save();
}
}
I leave as an exercise to you to find out which attribute goes into which User dialog entry field :-)

MVC4 web getting the user details from wrong Active directory.

I have MVC 4 web app deployed on 'Alpha' domain and web app is getting the Windows User details using the following code. We have trusted relationship between 3 different domains (Alpha, Beta and Gamma). When a user called Beta\bloggs login opens the web app following code returns the details of alpha\bloggs. Is there a way to get the right user from active directory.
Thanks in advance.
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(context, username))
{
if (user != null)
{
HttpContext.Current.Session["FullUserName"] = fullName;
HttpContext.Current.Session["EmailAddress"] = user.EmailAddress;
}
}
}
As far as I understand, you should test against a global catalog.
Can you try to change you context like this :
PrincipalContext context = new PrincipalContext(ContextType.Domain, "DNSName.Of.GlobalCatalog.com:3268", "DC=yourcompany,DC=com");
Context.domain identifies the credential store to use. A connection to the correct domain would still be required to lookup information in another domain. Domain trust just specifies credential mapping, who in domain x maps to credential y in domain z.
I don't think that FindByIdentity can handle implicit domain trust lookups.

Create PrincipalContext using windows authentication

I'm creating a PrincipalContext object for retrieving a user's groups from our AD database (we use these then for authentication to various parts of the site).
This used to be done using forms authentication, so the code looked something like this
PrincipalContext pc =
new PrincipalContext(ContextType.Domain, "domain.com", username, password);
UserPrincipal usp =
UserPrincipal.FindByIdentity(pc, IdentityType.Guid, user.Guid.ToString());
foreach (var group in usp.GetGroups())
{
// Add group to collection
}
However, we recently switched to windows authentication, and I no longer have access to the user's password.
How can I search the AD database using the current user's credentials? I've tried using impersonation, but it throws an An operations error occurred error on the FindByIdentity line. If I forget about authentication all together I'm limited in the number of groups that are returned.
Here is a method I use, You could change it to return a collection:
public static List<string> getGrps(string userName)
{
List<string> grps = new List<string>();
try
{
var currentUser = UserPrincipal.Current;
RevertToSelf();
PrincipalSearchResult<Principal> groups = currentUser.GetGroups();
IEnumerable<string> groupNames = groups.Select(x => x.SamAccountName);
foreach (var name in groupNames)
{
grps.Add(name.ToString());
}
return grps;
}
catch (Exception ex)
{
// Logging
}
}
I assume you want the results IEnumerable, which is what I did here.
Anon's answer works for what I asked, but I also wanted to be able to search for other user's groups. The best way I've found to do this is to run the asp.net program's app pool under a service account, and then use my original code.
To do this in IIS Manager 7.5, go to the Application Pools, right click on the one your app is running under -> Advanced Settings, and change the identity from "ApplicationPoolIdentity" to a custom domain account.

Categories

Resources