I am using System.DirectoryServices.AccountManagement.GroupPrincipal FindByIdentity in C# to create an object containing the group members (user IDs and names) for the target group. My goal is to iterate through the resulting list of UserPrincipals and print the SamAccountName and DisplayName for each. For some target groups, this is working fine; for others it fails on a user (or perhaps more than one) that throws the following error:
System.DirectoryServices.AccountManagement.PrincipalOperationException
HResult=0x80131501
Message=The specified directory service attribute or value does not exist.
When I use PowerShell’s Get-ADGroup to get the group object for one of the failing targets and iterate through it, there is no problem.
I’ve looked into the AD Group memberships and I believe the problem is that in some groups (those failing), some members may have been disabled, or may be part of a cross-domain trust. However, their status is of no consequence to me; I just want to list everything so the group owner can decide which members get migrated to new groups.
The method I am using is:
private static ArrayList EnumerateGroupMembers()
{
ArrayList gmObjects = new ArrayList();
string ldapVal = "DC=dc1,DC=dc2,DC=dcMain,DC=dcSecondary";
string ldapDom = "dc1.dc2.dcMain.dcSecondary:389";
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, ldapDom, ldapVal);
GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, "AD-GROUPNAME");
if (group != null)
{
var users = group.GetMembers(true);
//*** PrincipalOperationException occurs here ***
foreach (UserPrincipal p in users)
{
Console.WriteLine(p.SamAccountName + ", " + p.DisplayName);
}
Console.WriteLine("Done");
Console.ReadKey();
}
//*** Please note: I know I am returning an empty list here. I'm writing to Console during development
return gmObjects;
}
Can anyone suggest how I can iterate through the list of UserPrincipals without throwing a PrincipalOperationException? Or, at least a way to bypass the UserPrincipal occurrences that are causing these errors? Even if I cannot list the failing users I will survive.
Unfortunately, the System.DirectoryServices.AccountManagement namespace does not play nicely with Foreign Security Principals, as you've found.
You can do it using the System.DirectoryServices namespace, which is what AccountManagement uses behind the scenes. You will probably find it performs better anyway, although it is a little bit more complicated.
I've been meaning to write up something like this for my website anyway, so here is a method that will find all members of a group and list them in DOMAIN\username format. It has the option to expand nested groups too.
public static List<string> GetGroupMemberList(DirectoryEntry group, bool recurse = false, Dictionary<string, string> domainSidMapping = null) {
var members = new List<string>();
group.RefreshCache(new[] { "member", "canonicalName" });
if (domainSidMapping == null) {
//Find all the trusted domains and create a dictionary that maps the domain's SID to its DNS name
var groupCn = (string) group.Properties["canonicalName"].Value;
var domainDns = groupCn.Substring(0, groupCn.IndexOf("/", StringComparison.Ordinal));
var domain = Domain.GetDomain(new DirectoryContext(DirectoryContextType.Domain, domainDns));
var trusts = domain.GetAllTrustRelationships();
domainSidMapping = new Dictionary<string, string>();
foreach (TrustRelationshipInformation trust in trusts) {
using (var trustedDomain = new DirectoryEntry($"LDAP://{trust.TargetName}")) {
try {
trustedDomain.RefreshCache(new [] {"objectSid"});
var domainSid = new SecurityIdentifier((byte[]) trustedDomain.Properties["objectSid"].Value, 0).ToString();
domainSidMapping.Add(domainSid, trust.TargetName);
} catch (Exception e) {
//This can happen if you're running this with credentials
//that aren't trusted on the other domain or if the domain
//can't be contacted
Console.WriteLine($"Can't connect to domain {trust.TargetName}: {e.Message}");
}
}
}
}
while (true) {
var memberDns = group.Properties["member"];
foreach (string member in memberDns) {
using (var memberDe = new DirectoryEntry($"LDAP://{member.Replace("/", "\\/")}")) {
memberDe.RefreshCache(new[] { "objectClass", "msDS-PrincipalName", "cn" });
if (recurse && memberDe.Properties["objectClass"].Contains("group")) {
members.AddRange(GetGroupMemberList(memberDe, true, domainSidMapping));
} else if (memberDe.Properties["objectClass"].Contains("foreignSecurityPrincipal")) {
//User is on a trusted domain
var foreignUserSid = memberDe.Properties["cn"].Value.ToString();
//The SID of the domain is the SID of the user minus the last block of numbers
var foreignDomainSid = foreignUserSid.Substring(0, foreignUserSid.LastIndexOf("-"));
if (domainSidMapping.TryGetValue(foreignDomainSid, out var foreignDomainDns)) {
using (var foreignUser = new DirectoryEntry($"LDAP://{foreignDomainDns}/<SID={foreignUserSid}>")) {
foreignUser.RefreshCache(new[] { "msDS-PrincipalName" });
members.Add(foreignUser.Properties["msDS-PrincipalName"].Value.ToString());
}
} else {
//unknown domain
members.Add(foreignUserSid);
}
} else {
var username = memberDe.Properties["msDS-PrincipalName"].Value.ToString();
if (!string.IsNullOrEmpty(username)) {
members.Add(username);
}
}
}
}
if (memberDns.Count == 0) break;
try {
group.RefreshCache(new[] {$"member;range={members.Count}-*"});
} catch (COMException e) {
if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
break;
}
throw;
}
}
return members;
}
There are a few things this has to do:
The member attribute will only give you 1500 accounts at a time, so you have to ask for more until there are none left.
Foreign Security Principal's have the SID of the account on the foreign domain, but you need to use the domain's DNS name to connect to it (i.e. $"LDAP://{foreignDomainDns}/<SID={foreignUserSid}>"). So this method will look up all the trusts for the domain and create a mapping between the domain's SID and its DNS name.
You use it like this:
var group = new DirectoryEntry($"LDAP://{distinguishedNameOfGroup}");
var members = GetGroupMemberList(group);
Or you can use GetGroupMemberList(group, true) if you want to find users in nested groups too.
Keep in mind that this will not find users who have this group as their primary group, since the primary group does not use the member attribute. I describe that in my What makes a member a member article. In most cases you won't care though.
Related
The UserPrincipal.FindByIdentity(…) method does not work cross domain. the users outside of the domain the SingleSignOn app pool is running as are not being signed in automatically. When trying to access the SingleSignOn page as one of those users, it presents a NullReferenceException error
If you want to have a method that checks user in various domains, you will have to loop through various domains (with users that have access to read through each domain).
If you do have the domain information available of the user, you can add the domain as an argument to your method.
I am not sure if the AuthUserSession has any property that might hold the user's domain as well. If it does, use that instead of the domainName argument passed in to the method.
public void LoadUserAuthInfo(AuthUserSession userSession, IAuthTokens tokens, Dictionary<string, string> authInfo, string domainName= someDefaultIfNoneProvided)
{
if (userSession == null)
return;
string lookup = userSession.Id;
List<string> domains = new List<string>() { "domain1", "domain2" };
bool userFound = false;
foreach (string domain in domains)
{
using (var pc = new PrincipalContext(ContextType.Domain, domain))
{
var user = UserPrincipal.FindByIdentity(pc, userSession.UserAuthName);
// ---> Add Check here to prevent NRE
if (user != null) {
SingleSignOnResponse ssoB = new SingleSignOnResponse();
ssoB.Username = user.Sid.Translate(typeof(NTAccount)).ToString();
ssoB.Timestamp = DateTime.Now;
SSOCache.Instance.TryAdd(lookup, ssoB);
userFound = true;
break; // No longer need to continue checking other domains.
}
else
{
// Handle case when user does not exist.
}
}
}
// Check if userFound = false. If so, do something with that exception.
}
Update
If you have the domainName in the session.id, use the following,
string lookup = userSession.Id;
using (var pc = new PrincipalContext(ContextType.Domain, lookup.Split('\\')[0]))
{
...
I am struggling to find the data type of AD attributes that are not having a value already.
Thus far a resulting DirecoryEntry only contains a property for attributes that already have a value. I can't find a method to obtain information about all other attributes.
Adding a value to the 'PropertiesToLoad' doesn't seem to do anything. The returned DirectoryEntry object contains all attributes (with values) regardless of what is added here.
Code used:
public void Test(string ldapPath)
{
Type orgType;
try
{
using (DirectoryEntry searchRoot = GetSearchRoot(ldapPath))
{
using (DirectorySearcher search = new DirectorySearcher(searchRoot))
{
search.Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=coen))";
search.PropertiesToLoad.Add("msExchHomeServerName");
SearchResult searchResult = search.FindOne();
{
using (DirectoryEntry entry = searchResult.GetDirectoryEntry())
{
if (entry != null)
{
if (entry.Properties.Contains("msExchHomeServerName"))
{
orgType = entry.Properties["msExchHomeServerName"].GetType();
}
else // The value is empty and NOT present in the current 'entry.Properties' object.
{
throw new Exception("Unknown type");
}
}
}
}
}
}
}
catch (Exception e)
{
<handle exception>
}
}
Is there a way to find the data type of the 'msExchHomeServerName' (or any of the 'empty' attributes) attribute?
Any help would be highly appreciated!
Coen
Active Directory has a schema. Obvious to say, not obvious to think about since, by default, you cannot explore it.
However, you can Register the Active Directory Schema MMC Snap-In1 and then, in a fresh instance of MMC, add that Snap-In.
This then allows you to explore the Classes and Attributes that make up your current AD schema (and add new classes/attributes if you know what you're doing and choose to do so).
msExchHomeServerName can then be discovered to be a "Unicode String", which means a plain string from C# should be acceptable. Note also that some types (particularly numeric ones) may also specify Minimums and Maximums which should be observed.
You can also explore the schema programatically via the ActiveDirectorySchema class, by e.g. calling ActiveDirectorySchema.GetCurrentSchema(); and then explore from there.
1I cannot remember if you need to have installed the general Domain Admin tools (such as Users and Computers) in order for that DLL to exist on your system.
Thanks to Damien_The_Unbeliever who pointed me in the right direction, I managed to create the following method:
public Dictionary<string, ActiveDirectorySyntax> GetAttributeSyntaxes(List<string> lstAttributeNames)
{
Dictionary<string, ActiveDirectorySyntax> dictRes = new Dictionary<string, ActiveDirectorySyntax>();
if (lstAttributeNames.Count > 0)
{
DirectoryContext directoryContext = new DirectoryContext(DirectoryContextType.DirectoryServer,
m_Server, m_UserName, m_Password);
using (ActiveDirectorySchema currentSchema = ActiveDirectorySchema.GetSchema(directoryContext))
{
using (ActiveDirectorySchemaClass objClass = currentSchema.FindClass("user"))
{
if (objClass != null)
{
ReadOnlyActiveDirectorySchemaPropertyCollection propcol = objClass.GetAllProperties();
foreach (ActiveDirectorySchemaProperty schemaProperty in propcol)
{
foreach (string attrName in lstAttributeNames)
{
if (schemaProperty.Name.Equals(attrName))
{
dictRes.Add(attrName, schemaProperty.Syntax);
break;
}
}
}
}
}
}
}
return dictRes;
}
The returned 'schemaProperty.Syntax' contains sufficient information to determine the actual data type.
Thanks Damien!
This is not so much a question as information for anyone experiencing the same problem.
The following error occurs:
System.DirectoryServices.AccountManagement.PrincipalOperationException: An error (87) occurred while enumerating the groups. The group's SID could not be resolved.
at System.DirectoryServices.AccountManagement.SidList.TranslateSids(String target, IntPtr[] pSids)
at System.DirectoryServices.AccountManagement.SidList.ctor(List`1 sidListByteFormat, String target, NetCred credentials)
at System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.TranslateForeignMembers()
When the following code is run and a group or child group contains a ForeignSecurityPrincipal:
private static void GetUsersFromGroup()
{
var groupDistinguishedName = "CN=IIS_IUSRS,CN=Builtin,DC=Domain,DC=com";
//NB: Exception thrown during iteration of members rather than call to GetMembers.
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "Domain", "Username", "Password"))
{
using (GroupPrincipal groupPrincipal = GroupPrincipal.FindByIdentity(ctx, IdentityType.DistinguishedName, groupDistinguishedName))
{
using (var searchResults = groupPrincipal.GetMembers(true))//Occurs when false also.
{
foreach (UserPrincipal item in searchResults.OfType())
{
Console.WriteLine("Found user: {0}", item.SamAccountName)
}
}
}
}
}
I raised a support call with Microsoft and they have confirmed it as an issue. A bug has been raised internally but it has not been confirmed whether this will be fixed.
Microsoft suggested the following workaround code but it performs poorly on groups with a large number of users because of the repeated calls to UserPrincipal.FindByIdentity.
class Program
{
//"CN=IIS_IUSRS,CN=Builtin,DC=dev-sp-sandbox,DC=local"; //TODO MODIFY THIS LINE ACCORDING TO YOUR DC CONFIGURATION
static void Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("Usage: ListGroupMembers \"group's DistinguishedName\"");
Console.WriteLine("Example: ListGroupMembers \"CN=IIS_IUSRS,CN=Builtin,DC=MyDomain,DC=local\"");
return;
}
string groupDistinguishedName = args[0];
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "dev-sp-dc", "Administrator", "Corp123!");
List<UserPrincipal> users = new List<UserPrincipal>();
listGroupMembers(groupDistinguishedName, ctx, users);
foreach (UserPrincipal u in users)
{
Console.WriteLine(u.DistinguishedName);
}
}
//Recursively list the group's members which are not Foreign Security Principals
private static void listGroupMembers(string groupDistinguishedName, PrincipalContext ctx, List<UserPrincipal> users)
{
DirectoryEntry group = new DirectoryEntry("LDAP://" + groupDistinguishedName);
foreach (string dn in group.Properties["member"])
{
DirectoryEntry gpMemberEntry = new DirectoryEntry("LDAP://" + dn);
System.DirectoryServices.PropertyCollection userProps = gpMemberEntry.Properties;
object[] objCls = (userProps["objectClass"].Value) as object[];
if (objCls.Contains("group"))
listGroupMembers(userProps["distinguishedName"].Value as string, ctx, users);
if (!objCls.Contains("foreignSecurityPrincipal"))
{
UserPrincipal u = UserPrincipal.FindByIdentity(ctx, IdentityType.DistinguishedName, dn);
if(u!=null) // u==null for any other types except users
users.Add(u);
}
}
}
}
The above code could be modified to find foreign security principals causing problems in groups.
Microsoft provided the following information about the foreign security principals:
This is a class of objects in AD which represents a security principal from an external source (so another forest/domain or one of the “special” accounts below).
The class is documented here: http://msdn.microsoft.com/en-us/library/cc221858(v=PROT.10).aspx
And the container is documented here : http://msdn.microsoft.com/en-us/library/cc200915(v=PROT.10).aspx
A FSP is not a real object in AD, but rather a placeholder (pointer) to an object which lives in a different, trusted domain/forest. It can also be one of the “special identities” which are a bunch of well-known accounts who are also classed as FSP’s because their SID’s are different to the domain SID.
For example the anonymous, Authenticated User, batch and several other accounts as documented here:
http://technet.microsoft.com/en-us/library/cc779144(v=WS.10).aspx
Sure this is an old thread, but might help someone. I used the below code block the solve the problem. the Principal class exposes a property called StructuralObjectClass which tells you what is the AD Class of that principal. I used this to decide whether the object is a user. The GetMembers(true) recursively searches all nested-members in the groupPrincipal in question.
Hope this helps someone.
List<UserPrincipal> members = new List<UserPrincipal>();
foreach (var principal in groupPrincipal.GetMembers(true))
{
var type = principal.StructuralObjectClass;
if (type.Contains("user"))
members.Add((UserPrincipal)principal);
}
Thanks,
R
The accountmanagement library has many saddening defects, this is just another of the many...
One thing you can do to make things slightly faster would be to adjust your LDAP query so that it checks both group membership and object type at the same time as part of the query instead of in the loop. Honestly I doubt it will make much difference though.
Most of the inspiration for the query came from How to write LDAP query to test if user is member of a group?.
Query: (&(!objectClass=foreignSecurityPrincipal)(memberof=CN=YourGroup,OU=Users,DC=YourDomain,DC=com))
Note: This is an untested query...
IF there was a way to run an LDAP query in AccountManagement (another gripe of mine) then this would be the end of your troubles as you could run the query and let AccountManagement take it from there, but this option does not exist...
Based on personal experience I don't see any other options if you stick with AccountManagement. What you could do is dump AccountManagement and use just DirectoryServices. Under the hood all AccountManagement does is wrap DirectoryEntry objects anyways, you could write a few helper classes to do similar things.
As an alternative, you can use this code to get the members:
var pth = "LDAP://ex.invalid/CN=grpName,OU=Groups,OU=whatever,DC=ex,DC=invalid";
var dirEntry = new DirectoryEntry(pth);
var members = dirEntry.Invoke("Members"); //COM object
foreach (var member in (IEnumerable)members) {
var userEntry = new DirectoryEntry(member); //member is COM object
var sid = new SecurityIdentifier((byte[]) userEntry.InvokeGet("objectSid"), 0);
var typ = typeof(System.Security.Principal.NTAccount);
var account = (NTAccount)sid.Translate(typ);
Console.WriteLine(account.Value);
}
I have an application that uses ActiveDirecotry authorisation and it has been decided that it needs to support nested AD groups, e.g.:
MAIN_AD_GROUP
|
|-> SUB_GROUP
|
|-> User
So, the user in not directly a member of MAIN_AD_GROUP. I'd like to be able to look for the user recursively, searching the groups nested in MAIN_AD_GROUP.
The main problem is that I'm using .NET 3.5 and there is a bug in System.DirectoryServices.AccountManagement in .NET 3.5 whereby the method UserPrincipal.IsMemberOf() will not work for groups with more than 1500 users. So I can't use UserPrincipal.IsMemberOf() and no, I can't switch to .NET 4 either.
I've worked around this last problem with the following function:
private bool IsMember(Principal userPrincipal, Principal groupPrincipal)
{
using (var groups = userPrincipal.GetGroups())
{
var isMember = groups.Any(g =>
g.DistinguishedName == groupPrincipal.DistinguishedName);
return isMember;
}
}
But userPrincipal.GetGroups() only returns the groups of which the user is a direct member.
How can I get this to work with nested groups?
Workaround #1
This bug is reported here at Microsoft Connect along with the following code that works around this issue by manually iterating through the PrincipalSearchResult<Principal> returned objects, catching this exception, and continuing on:
PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups();
var iterGroup = groups.GetEnumerator();
using (iterGroup)
{
while (iterGroup.MoveNext())
{
try
{
Principal p = iterGroup.Current;
Console.WriteLine(p.Name);
}
catch (NoMatchingPrincipalException pex)
{
continue;
}
}
}
Workaround #2
Another workaround found here avoids the AccountManagement class, and uses the System.DirectoryServices API instead:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices;
namespace GetGroupsForADUser
{
class Program
{
static void Main(string[] args)
{
String username = "Gabriel";
List<string> userNestedMembership = new List<string>();
DirectoryEntry domainConnection = new DirectoryEntry(); // Use this to query the default domain
//DirectoryEntry domainConnection = new DirectoryEntry("LDAP://example.com", "username", "password"); // Use this to query a remote domain
DirectorySearcher samSearcher = new DirectorySearcher();
samSearcher.SearchRoot = domainConnection;
samSearcher.Filter = "(samAccountName=" + username + ")";
samSearcher.PropertiesToLoad.Add("displayName");
SearchResult samResult = samSearcher.FindOne();
if (samResult != null)
{
DirectoryEntry theUser = samResult.GetDirectoryEntry();
theUser.RefreshCache(new string[] { "tokenGroups" });
foreach (byte[] resultBytes in theUser.Properties["tokenGroups"])
{
System.Security.Principal.SecurityIdentifier mySID = new System.Security.Principal.SecurityIdentifier(resultBytes, 0);
DirectorySearcher sidSearcher = new DirectorySearcher();
sidSearcher.SearchRoot = domainConnection;
sidSearcher.Filter = "(objectSid=" + mySID.Value + ")";
sidSearcher.PropertiesToLoad.Add("distinguishedName");
SearchResult sidResult = sidSearcher.FindOne();
if (sidResult != null)
{
userNestedMembership.Add((string)sidResult.Properties["distinguishedName"][0]);
}
}
foreach (string myEntry in userNestedMembership)
{
Console.WriteLine(myEntry);
}
}
else
{
Console.WriteLine("The user doesn't exist");
}
Console.ReadKey();
}
}
}
Use UserPrincipal.GetAuthorizationGroups() instead - from its MSDN docs:
This method searches all groups
recursively and returns the groups in
which the user is a member. The
returned set may also include
additional groups that system would
consider the user a member of for
authorization purposes.
The groups that are returned by this
method may include groups from a
different scope and store than the
principal. For example, if the
principal is an AD DS object that has
a DN of
"CN=SpecialGroups,DC=Fabrikam,DC=com,
the returned set can contain groups
that belong to the
"CN=NormalGroups,DC=Fabrikam,DC=com.
I know this is an old thread, but it's the top result on Google, so in case this helps anyone, here's what I came up with that uses the AccountManagement stuff, but makes this particular query much easier.
public static class AccountManagementExtensions
{
public static bool IsNestedMemberOf(this Principal principal, GroupPrincipal group)
{
// LDAP Query for memberOf Nested
var filter = String.Format("(&(sAMAccountName={0})(memberOf:1.2.840.113556.1.4.1941:={1}))",
principal.SamAccountName,
group.DistinguishedName
);
var searcher = new DirectorySearcher(filter);
var result = searcher.FindOne();
return result != null;
}
}
The efficient way is to do a single AD query by having the right DirectorySearcher filter for e.g.
public bool CheckMemberShip(string userName)
{
bool membership = false;
string connection = "LDAP://"+YOURDOMAIN;
DirectoryEntry entry = new DirectoryEntry(connection);
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=cn=GROUPNAME,OU=Groups,OU=ABC,OU=ABC,OU=IND,DC=ad,DC=COMPANY,DC=com)(|(sAMAccountName=" + userName + ")))";
SearchResult result = mySearcher.FindOne();
// No search result, hence no membership
if (result == null)
{
membership = false;
}
entry.Close();
entry.Dispose();
mySearcher.Dispose();
membership = true;
return membership;
}
You need to replace YOURDOMAIN and GROUPNAME with right values from your AD.
Source : How to Recursively Get the Group Membership of a User in Active Directory using .NET/C# and LDAP (without just 2 hits to Active Directory)
Need to include using System.DirectoryServices;
How To Get User group of user from LDAP active directory in C# .NET for ASP. In my Scenario I want to Pass user name to method which query from LDAP Active directory and tell me my user is Member of This User Groups. Please help me in this
If you're on .NET 3.5 or newer, you can also use the new System.DirectoryServices.AccountManagement (S.DS.AM) namespaces.
With this, you can do something like:
// create context for domain
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find the user
UserPrincipal up = UserPrincipal.FindByIdentity(ctx, "YourUserName");
if(up != null)
{
// get groups for that user
var authGroups = up.GetAuthorizationGroups();
}
Read more about the new S.DS.AM namespace:
Managing Directory Security Principals in the .NET Framework 3.5
Look into using the System.DirectoryServices namespace. You can use a DirectorySearcher to find the user. Once you have the DirectoryEntry object for that user do this:
public List<string> GetMemberOf(DirectoryEntry de)
{
List<string> memberof = new List<string>();
foreach (object oMember in de.Properties["memberOf"])
{
memberof.Add(oMember.ToString());
}
return memberof;
}
This will return a list of strings which are the group names the user is a member of.
Of course you could further refine this to include the DirectorySearcher code so you can just pass the function the samAccountName.
try this...
public override string[] GetRolesForUser(string username)
{
var allRoles = new List<string>();
var root = new DirectoryEntry(WebConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString,
ConnectionUsername,
ConnectionPassword);
var searcher = new DirectorySearcher(root,
string.Format(CultureInfo.InvariantCulture, "(&(objectClass=user)({0}={1}))",
AttributeMapUsername,
username));
searcher.PropertiesToLoad.Add("memberOf");
SearchResult result = searcher.FindOne();
if (result != null && !string.IsNullOrEmpty(result.Path))
{
DirectoryEntry user = result.GetDirectoryEntry();
PropertyValueCollection groups = user.Properties["memberOf"];
foreach (string path in groups)
{
string[] parts = path.Split(',');
if (parts.Length > 0)
{
foreach (string part in parts)
{
string[] p = part.Split('=');
if (p[0].Equals("cn", StringComparison.OrdinalIgnoreCase))
{
allRoles.Add(p[1]);
}
}
}
}
}
return allRoles.ToArray();
}
Use the DirectorySearcher class to preform an ldap query.
For reference:
http://www.codeproject.com/KB/system/QueryADwithDotNet.aspx
I needed a method of authenticating a user and a check to see if they were in a specific user group. I did it by pushing the username and password and loading the "memberOf" property into the 'search' instance. Example below will display all the groups for that specific user name. The 'catch' statement will trap a wrong user name or password.
DirectoryEntry entry = new DirectoryEntry("LDAP://xxxxxxxx/OU=xxxxxxx,DC=xxxxxx,DC=xxxxx,DC=xxxxxx", strLdapUserName, strLdapPassword);
try
{
//the object is needed to fire off the ldap connection
object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + strLdapUserName + ")";
search.PropertiesToLoad.Add("memberOf");
SearchResult result = search.FindOne();
string filterAttribute = (String)result.Properties["cn"][0];
foreach(string groupMemberShipName in result.Properties["memberOf"])
{
Console.WriteLine("Member of - {0}", groupMemberShipName);
}
}
catch (Exception ex)
{
//failed to authenticate
throw new Exception(ex.ToString());
}
Hope this helps. (Remember to reference System.DirectoryServices)
I think most methods listed above should work, but i would suggest adding code to ensure that your code can "detect circular loops in nested group memberships", and if found, break any infinite loops that your script of choice could potentially get into.