Read user authorization groups from Active Directory - c#

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.

Related

Active Directory: How to determine whether account is service account?

Question: Is it possible to determine whether an account is a service account in Active Directory using C# LDAP? If yes, how?
Context: I have a program that is retrieving all objects of schema class type USER, GROUP, COMPUTER, FOREIGN SECURITY PRINCIPAL, and CONTACT. Currently, a service account is identified by string parsing the canonical name for 'service account'. I do not like this solution because string parsing is dependent on a folder location in the hierarchy that literally says 'service account'. It seems possible that a service account could be created and then placed in a folder path that does not include the string 'service account'. Unfortunately, I cannot test this because I am not an AD admin.
I have browsed around online without any luck so I am not sure if it is even possible.
Update:
Per Microsoft, it appears that the service account is contained in objectClass msDS-ManagedServiceAccount. However, when I set the DirectoryEntry filter to msDS-ManagedServiceAccount, no results are returned.
directoryEntry = new DirectoryEntry(strActiveDirectoryHost, null, null, AuthenticationTypes.Secure);
string strDsFilter = "(objectClass=msDS-ManagedServiceAccount)";
DirectorySearcher directorySearcher = new DirectorySearcher(directoryEntry)
{
Filter = strDsFilter,
SearchScope = SearchScope.Subtree,
PageSize = intActiveDirectoryPageSize,
};
return searchResultCollection = directorySearcher.FindAll();
I have testing your code, and it does in fact return results in my environment. A few things to note:
Be sure that strActiveDirectoryHost is formatted correctly. The format should be LDAP://DC=contoso,DC=com
Check that you are searching from the root (or high enough to find the accounts you are looking for). MSAs are under the Managed Service Accounts container under the domain NC (i.e. LDAP://CN=Managed Service Accounts,DC=contoso,DC=com)
In my tests, I call new DirectoryEntry() with only the path. Not sure if passing AuthenticationTypes.Secure is causing an issue for you
The objectClass you have is correct.
So I am working on this to get the MSA as well as create them. I am able to get the MSA using the System.DirectoryServices.AccountManagement namespace, still working on creating it (unsure if this is really possible)
But for finding the accounts which are MSAs you can use the below code
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain, sDomain, sDefaultOU, ContextOptions.SimpleBind, sServiceUser, sServicePassword);
GroupPrincipal currentGroup = GroupPrincipal.FindByIdentity(oPrincipalContext, "YourGroupName");
foreach (Principal a_principal in currentGroup.GetMembers())
{
if (a_principal.StructuralObjectClass == "msDS-ManagedServiceAccount")
{
Console.Write(a_principal.SamAccountName); //To get the name
ComputerPrincipal oComputerPrincipal = ComputerPrincipal.FindByIdentity(oPrincipalContext, a_principal.Name); //creating a computerprincipal to get more details about the MSA
}
}
You can use the above logic and create a Principal for the user account and get the structural object class for that account to find out if it is MSA.
Something like this:
UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(oPrincipalContext, sUserName);
if (oUserPrincipal.StructuralObjectClass == "msDS-ManagedServiceAccount")
{
Console.Write(oUserPrincipal.SamAccountName); //To get the samaccountname
}

How can I get a UserPrincipal for a user in a different domain?

The following code works great for users in my domain (e.g., "TESTER" instead of "DEVELOPER"), but I can't figure out how to search higher than the current domain. I tried variation combinations of searches with the PrincipalSearcher class, but I'm not sure how to pass in a search by email address or username to look for other domains within my organization even though they're all in a single forest.
var name = "DEVELOPER\\JULIANI99";
var p = UserPrincipal.FindByIdentity(new PrincipalContext(ContextType.Domain), name);
FindByIdentity doesn't work well for searching a forest.
I tried something like this:
var d = new PrincipalContext(ContextType.Domain, "domain.com:3268", "DC=com");
var p = UserPrincipal.FindByIdentity(d, IdentityType.SamAccountName, username);
But I keep getting an error saying a referral was returned. It might be different for you. The "3268" port tells it to use the global catalog (forest-wide search). The root (which I have as "DC=com") has to be the common across all the domains in your forest. So if all your domains are sub-domains of "domain.com", then you could put "DC=domain,DC=com". But if you have "domain.com" and "otherdomain.com" part of the same AD forest, then that wouldn't work.
FindByIdentity also won't work for searching by email address, so you may just be better off using PrincipalSearcher.
If you get the same referral error I got, you can tell it to follow the referral:
PrincipalSearcher srch = new PrincipalSearcher(User);
((DirectorySearcher) srch.GetUnderlyingSearcher()).ReferralChasing = ReferralChasingOption.All;

IsUserInRole issue with Domain Admins group (not a configuration issue)

I am seriously beginning to think that where I work is cursed as far as development efforts go, I keep running into very strange issues.
I am using Roles.IsUserInRole(#"Domain\Domain Admins") to check if a user is a Domain Administrator.
For some reason, it does not recognize me in that group, although I have been in it for years. I thought at first it MIGHT have had something to do with the space, but Roles.IsUserInRole(#"Domain\Domain Users") works just fine. Both groups reside in the same AD OU.
Am I losing my mind or is there really something special about the "Domain Admins" group?
EDIT:
List<GroupPrincipal> result = new List<GroupPrincipal>();
PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain);
UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, User.Identity.Name);
if (user != null)
{
PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups();
foreach (Principal p in groups)
{
if (p is GroupPrincipal)
{
result.Add((GroupPrincipal)p);
}
}
}
var myRoles = Roles.GetRolesForUser(User.Identity.Name);
I used the above code to verify group membership. Domain Admins IS listed in the result variable, but NOT in myRoles
Turns out, it has to do with elevated privledges.
Please see: https://www.reddit.com/r/csharp/comments/4cvr0p/domain_admin_is_not_showing_up_in_my_role_list_im/
(Does not explain a work around, only a reason)

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.

Querying LDAP for Usergroup of Specific User

I have to check usergroups of LDAP Active Directory for a specific user in C#. Mean I pass this username to a method and it returns me list of group from that user belongs. Can You Please help me in this. Im Searching alot But Everytime get new error.
LDAP Path: 192.168.1.4
Domain Name: Arslan
UserName: ArslanP
Password: testad
Since you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
Basically, add a reference to the assembly System.DirectoryServices.AccountManagement, and then you can define a domain context and easily find users and/or groups in AD:
using System.DirectoryServices.AccountManagement;
public List<GroupPrincipal> GetGroupsForUser(string username)
{
List<GroupPrincipal> result = new List<GroupPrincipal>();
// set up domain context - if you do a lot of requests, you might
// want to create that outside the method and pass it in as a parameter
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find user by name
UserPrincipal user = UserPrincipal.FindByIdentity(username);
// get the user's groups
if(user != null)
{
foreach(GroupPrincipal gp in user.GetAuthorizationGroups())
{
result.Add(gp);
}
}
return result;
}
The new S.DS.AM makes it really easy to play around with users and groups in AD:
This related question may help you:
Get List of Users From Active Directory In A Given AD Group
It asks the reverse question, which is how to qet a list of users when you know the group, but other answers may be of use to you as well.
See also the answer to this question:
How to get all the AD groups for a particular user?

Categories

Resources