C# LDAP query fails sometimes - c#

I have this piece of code in a program, to query what groups a Windows-domain user belongs to.
public void GetGroupNames(string userName, List<string> result)
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain))
{
UserPrincipal uPrincipal = UserPrincipal.FindByIdentity(pc, userName);
if (uPrincipal != null)
{
PrincipalSearchResult<Principal> srcList = uPrincipal.GetGroups();
foreach (Principal item in srcList)
{
result.Add(item.ToString());
}
}
}
}
When I just implemented it and was debugging it,
UserPrincipal uPrincipal = UserPrincipal.FindByIdentity(pc, userName);
always got null.
I then had to close visual studio to do something else. When I came back, opened up visual studio, this code just worked. A few days ago, there was a network problem in the organisation, I did not switch off my PC during that period. After the network went back to normal, I could connect to internet OK, I could remote desktop to servers etc, which proves that Active Directory authentication was done all right, but the above piece of code failed to find UserPrinical for a given name, e.g. my own. I then reboot the PC, the code worked fine. I am quite puzzled regarding this matter. Is anyone able to provide a good explanation for this??

UserPrincipal has some known bugs with in cross domain scenarios. When it happens again, look and see if you can resolve groups from your machine. I have also encountered problems when unresolvable SIDs where group members.

Related

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.

HttpContext.Current.User.IsInRole("Domain\\Domain Users") takes forever

Determined this can't be a network issue. I'm having this issue in debug (VS2012 / .Net 4.5 / IIS Express 8.0)
Code:
bool rtn2 = HttpContext.Current.User.IsInRole("MyDomain\\Domain Users");
Eventually returns true. But, can take several minutes.
var test = HttpContext.Current;
var test2 = HttpContext.Current.User;
var test3 = HttpContext.Current.User.Identity;
...all extremely fast.
var test = HttpContext.Current.User.IsInRole("MyDomain\\Domain Users");
var test2 = HttpContext.Current.User.IsInRole("MyDomain\\Domain Users");
First call takes several minutes, the second is instant. If I change the second to look for some other group (assuming the first was cached), it is still instant.
I thought maybe i'm having network issues (I connect to the domain and debug over VPN.) However, if I create a new VS2012 web project and put that code in the startup page, it's instant. I can also search Active Directory from my machine and pull up the Domain Users group and see all people in it pretty much instantly (there are over 10 thousand users) - no problem. So, this must be project / config based issue?
Going out of my mind trying to figure this out. Some info:
Tried re-installing IIS Express
I've tried rebooting
I've tried in a new tester web project - works instantly
Problem seems to be machine specific. Any assistance or even just recommendations for additional trouble-shooting steps would be appreciated.
Try using the System.DirectoryServices.AccountManagement namespace instead.
public static bool IsUserGroupMember(string userName, string groupName)
{
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
using (UserPrincipal user = UserPrincipal.FindByIdentity(context, userName))
using (PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups())
{
return groups.OfType<GroupPrincipal>().Any(g => g.Name.Equals(groupName, StringComparison.OrdinalIgnoreCase));
}
}
I had the same problem. IsInRole was taking forever to finish on a production server. The following code worked instead. Saw it somewhere, unfortunately can't remember the source.
// Is in AD Group?
private static bool IsInADGroup(String inGroup)
{
foreach (System.Security.Principal.IdentityReference group in
System.Web.HttpContext.Current.Request.LogonUserIdentity.Groups)
{
String sGroup = (group.Translate(typeof(System.Security.Principal.NTAccount)).ToString());
if (sGroup.Equals(inGroup))
return true;
}
return false;
}

WCF Service throws error when validating Credentials against PrincipalContext?

I have a WCF service, which works if I use one login, but throws the following error if I try logging in with any other login. Strangely enough, if I change the password to the working login, the new password doesn't work but the old one still does. It's almost like it is caching something.
The error I get is this:
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
The code that causes the error is this:
public UserModel Login(string username, string password)
{
if (username == null || password == null)
return null;
using (var pContext = new PrincipalContext(ContextType.Machine))
{
if (pContext.ValidateCredentials(username, password))
{
using (var context = new MyEntities())
{
// I can tell from a SQL trace that this piece never gets hit
var user = (from u in context.Users
where u.LoginName.ToUpper() == username.ToUpper()
&& u.IsActive == true
select u).FirstOrDefault();
if (user == null)
return null;
var userModel = Mapper.Map<User, UserModel>(user);
userModel.Token = Guid.NewGuid();
userModel.LastActivity = DateTime.Now;
authenticatedUsers.Add(userModel);
sessionTimer.Start();
return userModel;
}
}
}
return null;
}
I see a related question here, which suggests the problem is with the PrincipalContext, but no answer
Update
Got it working..... I restarted our production server because we needed to have this working for someone important within the next hour, and I thought since it that previous link suggested that a reboot would get a single login in that I would just reboot and login with the login needed to get it working for now, and after rebooting everything works absolutely perfectly. I spent most of yesterday, staying late, and all of this morning trying to figure this out. We're not supposed to reboot our web server, but it was important to get this working so I did it anyways, and now everything works the way it should.
I would still like to know what its problem was though. My best guess is that something caused the PrincipalContext to not dispose correctly, which was preventing me from logging in with any other set of credentials.
Restarting the server fixed the issue, although I'd still love to know what the problem was.
My best guess is that something caused the PrincipalContext to not dispose correctly, which was preventing me from logging in with any other set of credentials.

Cant find users or groups with System.DirectoryServices.AccountManagement

I'm trying to integrate a system with Active Directory using the System.DirectoryServices.AccountManagement stuff. Our IT people have setup an AD box and my dev box is not part of this (or any) domain.
So far, I have 3 lines of code as a test:
var pc = new PrincipalContext(ContextType.Domain, "machine", "CN=Administrator,CN=Users,DC=domain,DC=com", "Password");
var user = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, "Administrator");
var gp = GroupPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, "Admins");
Creating the PrincipalContext works as listed above, but if I try to use the domain name instead of the server name then I get an error : The server could not be contacted. So, I left this on the machine name.
When getting the user or group, I get an error : A local error has occurred.
For the user, I also tried this with the same result:
var user = UserPrincipal.FindByIdentity(pc, IdentityType.DistinguishedName, "cn=Administrator,ou=users,dc=domain,dc=com");
So, overall, I'm confused :(
Does anyone have any suggestions?
As a side note, I'd like to kick the programmer who thought that 'a local error has occurred' would be a useful error message!
Cheers
PS: I can use the SysInternals AD Explorer just fine from my machine and I can see the dn's I'm trying to use.
PPS: If I use machine.domain.com for the name when creating the PrincipalContext, it also fails to connect.
So this is one of those things that makes perfect sense AFTER you hack through to the solution. The problem was the Context was trying to use a Negotiated security context which is not configured. When I used SimpleBind it works just fine:
var pc = new PrincipalContext(ContextType.Domain, "machine", "DC=domain,DC=com", ContextOptions.SimpleBind, "CN=Administrator,CN=Users,DC=domain,DC=com", "Password");
Cheers
PS: A more useful error message would have saved me a days head scratching!
To do the search using the credentials of the current user, specify the domain as such:
new PrincipalContext(ContextType.Domain, "xyz.mycorp.com:3268", "DC=mycorp,DC=com");
From
When do I need a Domain Name and a Domain Container to create a PrincipalContext?

Read user authorization groups from Active Directory

In our system we are reading user security groups from an Active Directory in two slightly different ways. In one case the list of groups returned by the AD is missing the domain local groups. The response from GetAuthorizationGroups () is dependent on the used PrincipalContext. In the failing scenarios GetAuthorizationGroups() will only return global groups. The result is missing all domain local groups from the AD. Can anyone please explain why?
Failing solution:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "our.domain.net");
var userPrincipal = UserPrincipal.FindByIdentity(ctx, IdentityType.UserPrincipalName, "userB");
PrincipalSearchResult<Principal> groups = userPrincipal.GetAuthorizationGroups();
In this case the process is executed by “UserA”. “UserA” is a member of the domain “our.domain.net”. “UserA” is the very same user as the specifically identified user in the working solution. The PrincipalContext should because of that be identical to the PrincipalContext in the working solution. The response from GetAuthorizationGroups() in this solution miss domain local groups from the AD.
Working solution:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "our.domain.net", "UserA", "PasswordA");
var userPrincipal = UserPrincipal.FindByIdentity(ctx, IdentityType.UserPrincipalName, "userB");
PrincipalSearchResult<Principal> groups = userPrincipal.GetAuthorizationGroups();
In this case the calling user is identified specifically by use name and password when creating the Principal Context. In this case the AD returns all the groups that the user is a member of. This is the behavior I would like to see from the failing solution as well. In some cases I do not have the user password of UserA and of that reason the Working solution is not an option.
Please help me understand why the failing solution does not return all the groups that the user is a member of.
"It misses domain local groups from the AD" because you are probably iterating the resulting groups with foreach loop and you are getting NoMatchingPrincipalException exception for one of the groups that the user doesnt have read access and at that point it stops iterating, failing to get the rest of the groups.
As a solution you may use the following iterator (the code behind the foreach structure) to get all the rest of the groups:
var enumerator = groups.GetEnumerator();
while (enumerator.MoveNext())
{
try
{
var e = enumerator.Current;
listView1.Items.Add(e.Name);
}
catch (NoMatchingPrincipalException)
{
}
}
We finally found the problem. It turned out not to be a coding problem at all. The strange behaviour was caused by an erronious Domain Level in the Active Directory.
Domain Level had to be set to "2003 functional level"
Now it all works as expected.

Categories

Resources