Create PrincipalContext using windows authentication - c#

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.

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#: Check If User Has Access To Server Without Attempting Connection

Good afternoon,
I am currently facing an issue where I have a list of server names, and I need to verify that a windows authenticated user has permissions to the specified server names without attempting to establish a connection to the specified server, before I present the list of servers to them. So for example:
Servers: A, B, C, D
Joe has permissions to A and D but not B and C so Joe should only see A and D in his server list.
How should I be tackling this issue? Should I be pulling from Active Directory? I know how to pull my user's identity, but where to pull the server information and find out if a user has permissions is a completely different story. Any documentation, code samples, articles, etc. are helpful.
Notes About The Work Environment
All of the servers in the list are database servers running SQL Server 2008 R2 and higher.
This is a government environment where there are heavy restrictions.
I am unable to modify Active Directory in any way.
I can query Active Directory and SQL Server all day, provided the user has permissions.
Using third party libraries and tools are not authorized.
Side Note About The Server List
This list is stored in a database, however I don't think this really helps with the permissions side. I've already solved the issue of checking permissions against the individual databases with the following SQL query:
SELECT name
FROM sys.databases
WHERE HAS_DBACCESS(name) = 1
ORDER BY name
Thank you for your help!
-Jamie
Conclusion
Essentially, I have found no possible way to query the AD groups on the remote server without attempting to establish a connection, thus the execution of all samples found will flag the users account with IA. For others with a similar issue that don't have to worry about IA dropping a hammer on users, I have included a few solutions below you can try to meet your needs (all of the following will work when implemented correctly).
Solutions
Query the server in active directory and retrieve the groups from the server, if this fails 9 times out of ten the exception message will be 'Access is denied.'. If it succeeds, proceed to pull the current user's groups and compare to the groups pulled from the server. The selected answer to this post combined with this post and/or this post will get you where you need to be with this solution.
If you have access to the server already through impersonation or other means (SQL Server Auth) then you can use the following to see if the member has any server roles assigned:
SELECT IS_SRVROLEMEMBER('public')
You could also use the Login class available in the Microsoft.SqlServer.Smo assembly using the Microsoft.SqlServer.Management.Smo namespace. However, you may or may not have issues moving the Microsoft.SqlServer.SqlClrProvider namespace from the GAC to the BIN. More information about this can be found at this StackOverflow post, and at this Microsoft Connect thread which states the following:
Client applications should not be using the assemblies from the Program Files folders unless they are from the specific SDK folders (such as "C:\Program Files (x86)\Microsoft SQL Server\130\SDK")
You could even do a basic connection test wrapped in a try catch to see if the connection will work.
using (SqlConnection conn = new SqlConnection(connString)) {
try {
conn.Open();
conn.Close();
} catch (Exception e) { Console.Write($"Connection test failed: {e.Message}"); }
}
There are a small variety of ways to achieve the overall goal, it just depends on your particular situation and how you want to approach it. For me, none of the solutions will work since in each scenario the test will attempt a connection to the server in question which will flag the user due to lack of permissions.
If you have Active Directory implemented then you should be giving users access rights to things like servers via AD groups anyways or else that creates a management nightmare. Imagine if John Smith joins your company as a sys admin, are you going to go to every server and explicitly assign him rights? Much easier to just create a server admin AD group then assign it to the server (or dictate what AD groups exists on servers and permission levels by group policy.
Why this also helps you is that when you develop applications, you can use the built in AD role provider to serve up things like this. Here is a simple example of grabbing a users groups by AD user Name
using System.DirectoryServices.AccountManagement;
public List<string> GetGroupNames(string userName)
{
List<string> result = new List<string>();
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAINHERE"))
{
using (PrincipalSearchResult<Principal> src = UserPrincipal.FindByIdentity(pc, userName).GetGroups(pc))
{
src.ToList().ForEach(sr => result.Add(sr.SamAccountName));
}
}
return result;
}
EDIT: So if you absolutely refuse to use active directory groups to manage permissions on servers and buying a tool is out of the question, here is a class that will iterate through all of your local machine groups and give you a list of users within those groups. You could do something like have it run as a scheduled task on the server (or win service) and save it's results back to a DB so you can query or build a UI to pull and monitor this info at any time. This doesn't reach out and grab sql server permissions as you said you already have that.
public class MachinePermissions
{
string machineName { get; set; }
public List<LocalGroup> localGroups { get; set; }
public List<string> GetGroupMembers(string sGroupName)
{
List<String> myItems = new List<String>();
GroupPrincipal oGroupPrincipal = GetGroup(sGroupName);
PrincipalSearchResult<Principal> oPrincipalSearchResult = oGroupPrincipal.GetMembers();
foreach (Principal oResult in oPrincipalSearchResult)
{
myItems.Add(oResult.Name);
}
return myItems;
}
private GroupPrincipal GetGroup(string sGroupName)
{
PrincipalContext oPrincipalContext = GetPrincipalContext();
GroupPrincipal oGroupPrincipal = GroupPrincipal.FindByIdentity(oPrincipalContext, sGroupName);
return oGroupPrincipal;
}
private PrincipalContext GetPrincipalContext()
{
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Machine);
return oPrincipalContext;
}
public MachinePermissions()
{
machineName = Environment.MachineName;
PrincipalContext ctx = new PrincipalContext(ContextType.Machine, Environment.MachineName);
GroupPrincipal gp = new GroupPrincipal(ctx);
gp.Name = "*";
PrincipalSearcher ps = new PrincipalSearcher();
ps.QueryFilter = gp;
PrincipalSearchResult<Principal> result = ps.FindAll();
if(result.Count() > 0)
{
localGroups = new List<LocalGroup>();
foreach (Principal p in result)
{
LocalGroup g = new LocalGroup();
g.groupName = p.Name;
g.users = GetGroupMembers(g.groupName);
localGroups.Add(g);
}
}
}
}
public class LocalGroup
{
public string groupName { get; set; }
public List<String> users { get; set; }
}
You can create AD group for accessing each database, then add users to them.
In your app you can add list of groups and check if user Is member of them.
It's common practice and allow to create secure scenarios for different access right for different users. You only set permissions for group once and all members can benefit from access rights.

Getting "Access is denied." when trying to update UserPrincipal object

I have a SharePoint solution with a custom application where a user should be able to change some properties in his own Active Directory object.
I am doing the following:
PrincipalContext ctx = ActiveDirectory.GetPrincipalContext("lab");
UserPrincipal user = ActiveDirectory.GetUserPrincipal(ctx, "Administrator");
user.DisplayName = user.DisplayName + DateTime.Now.ToString("ddMMyyyyHHmmss");
user.Save();
I am logged in to SharePoint as the domain administrator and i am trying to change my own DisplayName.
What is wrong with my code?
Update 20.04.2016
I have built a small Console Application with the following code:
static void Main(string[] args)
{
try
{
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "lab");
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "administrator");
DirectoryEntry de = (user.GetUnderlyingObject() as DirectoryEntry);
user.DisplayName = user.DisplayName + DateTime.Now.ToString("ddMMyyyyHHmmss");
user.Save();
Console.WriteLine("OK");
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
This works fine! That means normally, that the Authentication goes wrong or the user which is logged into SharePoint is not getting used to connect to AD and do the changes. If this could be the case, how could i find out with which user i am doing the operation?
Update 20.04.2016 - 2
I have now tried to put the username and password in the PrincipalContext contructor as below:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "lab", "administrator", "pass");
This works, too! That means now definitely, that the user which is logged in to SharePoint is not used to create the PrincipalContext. But why? Normally code is always executed in the context of the current user!?
How can i find out which user is used to create the PrincipalContext and how can i change it that the logged in user is getting used?
I had the same problem, I resolved it like this:
In IIS, in the advanced settings of my app -> In Identity I add my service account (example: domain\service-account)
In my active directory, I give to this service account full control on folder and sub-folder where I want to create/update user (you need to modify view to see security tab -> right click -> view -> advanced features)
If you already have that, check if your service account is not disabled
And now everything is working :)

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.

Active Directory, enumerating user's groups, COM exception

while enumerating current user's groups through AD .NET API I sometimes get
COMException: Unknown error (0x80005000)
Here's my code :
var userName = Environment.UserName;
var context = new PrincipalContext(ContextType.Domain);
var user = UserPrincipal.FindByIdentity(context, userName);
foreach (var userGroup in user.GetGroups())
{
Console.WriteLine(userGroup.Name);
}
What's the problem? I thought every user can retrieve list of HIS groups?It seems to be strange behavior, sometimes It can be reproduced like this : when running on 'userA' PC, It crashes, but it is enumerating OTHER 'userB' groups successfully (under 'userA')!
Try using
var context = new PrincipalContext(ContextType.Domain, "yourcompany.com", "DC=yourcompany,DC=com", ContextOptions.Negotiate);
With the ContextOption set to Negotioate the client is authenticated by using either Kerberos or NTLM so even if the user name and password are not provided the account management API binds to the object by using the security context of the calling thread.
I had the same problem, I solved it by supplying the domain name when creating the PrincipalContext:
var domain = new PrincipalContext(ContextType.Domain, Environment.UserDomainName);
var user = UserPrincipal.FindByIdentity(domain, Environment.UserName);
0x80005000 = E_ADS_BAD_PATHNAME so you supply an invalid adspath somewhere, maybe you must add LDAP:// prefix or opposit are doing this twice? Set a breakpoint and inspect value...
EDIT:
AdsPath should be a value like "LDAP://CN=Administator,CN=Users,DC=contoso,DC=com", you seem to have a misformed path.

Categories

Resources