LDAP Queries, Core 2.x, IdSvr, Self Host - c#

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);

Related

Disconnect Local Windows User Using C#

I have created two test accounts test and test1.
I am able to login to one local windows user account "test" using the following code.
bool result = false;
ContextType contextType = ContextType.Machine;
//if (InDomain())
//{
// contextType = ContextType.Domain;
//}
try
{
using (PrincipalContext principalContext = new PrincipalContext(contextType))
{
result = principalContext.ValidateCredentials(
userName,
new NetworkCredential(string.Empty, password).Password
);
}
}
catch (PrincipalOperationException)
{
// Account disabled? Considering as Login failed
result = false;
}
catch (Exception)
{
throw;
}
return result;
However when I try to login with another account "test1", it throws the following exception.
Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again
It seems to me that the previous user "test" is already connected and occupies the resources, that is why it is not letting the user "test1" to get logged in and acquire the resources.
I have tried to find a way so that the first user "test" can be disconnected (or logged off) so that the 2nd user can get logged in.
I had tried to access the UserPrinciple through following code to get the connected user to disconnect but there is no method available in UserPrinciple
UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(principalContext, "test");
There must be some way to log off (or disconnect) the logged in user.
I don't know whether there's a nicer solution, but you could try a workaround using the net use command. By hand:
Run net use
Identify the relevant connections
Run net use /d <connection> for all relevant connections
Connect using the new user

How to authenticate with Azure Active Directory programatically in a Connect App for Business Central?

I am attempting to write a connect app that will receive a set of data from an external source and put it inside an instance of microsoft dynamics 365 business central via its APIs. Documentation says there are two ways to do this, using basic authentication and logging in via Azure Active Directory. The former is easy and straightforward to do programmatically, but the documentation makes it very clear that it is not meant for production environments. I'm capable of doing the latter using Postman, but part of the process involves me typing in credentials in a popup window. Since the use case for the final product will be to run without user interaction, this won't do. I want the application to handle the credentials of what will be a service account by itself.
I'm able to modify records using basic authentication, and active directory if I fill out the login form when prompted. I've tried using a library called ADAL, but passing my account's credentials that way led to the following response: {"error":"invalid_request","error_description":"AADSTS90014: The request body must contain the following parameter: 'client_secret or client_assertion.}
I have access to the client secret, but there seems to be no means of passing it via ADAL, that I've found.
I've also tried, at a colleague's recommendation, to log in using the client id and client secret as username and password. The following code is what we ended up with:
RestClient client = new RestClient("https://login.windows.net/[my tenant domain]/oauth2/token?resource=https://api.businesscentral.dynamics.com");
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("undefined", "grant_type=%20client_credentials&client_id=%20[my client id]&client_secret=[my client secret]&resource=[my resource]", ParameterType.RequestBody);
string bearerToken = "";
try
{
bearerToken = JsonConvert.DeserializeObject<Dictionary<string, string>>(client.Execute(request).Content)["access_token"];
Console.WriteLine(bearerToken);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
The code above successfully retrieves a token, but if I use that token I get the following response:
<error xmlns="http://docs.oasis-open.org/odata/ns/metadata"><code>Unauthorized</code><message>The credentials provided are incorrect</message></error>
I've never used Microsoft dynamics 365. But I've validated an user using a local active directory server using C# code.
using System.DirectoryServices.AccountManagement;
public class ActiveDirectoryService {
// The domain url is the url of the active directory server you're trying to validate with.
public bool ValidateWithActiveDirectoryAsync(string domainUrl, string userName, string password) {
using (var context = new PrincipalContext(ContextType.Domain, domainUrl)) {
UserPrincipal UserPrincipal1 = new UserPrincipal(context);
PrincipalSearcher search = new PrincipalSearcher(UserPrincipal1);
if (context.ValidateCredentials(userName, password)) {
return true;
}
}
return false;
}
}
I hope it works for you.

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 :-)

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

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.

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