Get members of group nested in another group - c#

I'm developing an intranet which shows specific links only if a user is member of certain groups in Active Directory.
I'm using this code to effectively check if a user is member of a group. Works fine if the group contains users only, but it doesn't work if there's a group nested in it. What I need is to make it works even if a particular user is in the nested group!
public bool isMember(string user, string group)
{
string value = "";
bool isMember = false;
try
{
DirectoryEntry entry = new DirectoryEntry(domain);
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = "sAMAccountname=" + user;
SearchResult mySearchResult;
mySearchResult = mySearcher.FindOne();
PropertyValueCollection prop = mySearchResult.GetDirectoryEntry().Properties["memberOf"];
for (int i = 0; i < prop.Count; i++)
{
value = prop[i].ToString();
string[] groups = value.Split(',');
foreach (string property in groups)
{
if (groups[1] == group)
{
isMember = true;
break;
}
}
}
entry.Close();
return isMember;
}
catch (COMException)
{
return false;
}
}
How can I accomplish this?
EDIT :
I've found this code, which allows me to find a user, even if a nested group. The problem is that it works only if my organizational unit contains ONE group, doesn't work if there are more than one...do you think I can edit it a bit to make it work properly??
public bool isMember(string user, string group)
{
bool found = false;
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "domain");
GroupPrincipal p = GroupPrincipal.FindByIdentity(ctx, IdentityType.Name, group);
UserPrincipal u = UserPrincipal.FindByIdentity(ctx, user);
found = p.GetMembers(true).Contains(u);
p.Dispose();
u.Dispose();
return found;
}
Got this code from ->here<-

You might consider asking AD for their token instead with tokenGroups. Have a look at http://dunnry.com/blog/EnumeratingTokenGroupsTokenGroupsInNET.aspx for a sample.

There are two ways to do this.
Recursively check memberOf property for each group found. Note that there can be circular references so you'll have to keep a track of groups visited to avoid infinite recursion.
Use the LDAP_MATCHING_RULE_IN_CHAIN rule to search for all groups a user belongs to, directly or indirectly. To do this you need to first get the user's DN, then search for groups that contain that userDN as a direct or indirect member. The filter looks something like:
String.Format("(member:1.2.840.113556.1.4.1941:={0})", userDN)
IIRC option 1 is generally faster, but of course needs more code.

Related

How to get LDAP nested groups from attribute

I can search the user and find only the groups that the user belongs to. And now i want to return all the groups/roles and assign a user to a specific group/role.
DirectoryEntry and PrincipalContext doesn't work in my case and i have tried that for days.
This is my working code for searching user group/roles which is working fine.
How can i get all the groups/roles?
And Add user to a group/role
Container = “ou=containername,ou=xx,ou=xx,O=xxxxx”
Domain = “mydomain.com”
group = ou=groups,ou=containername,ou=xx,ou=xx,O=xxxx
List<string> roles = new List<string>();
SearchRequest request = new SearchRequest("", "(&(objectClass=person)(mail=myusername))", System.DirectoryServices.Protocols.SearchScope.Subtree);
SearchResponse response = (SearchResponse)con.SendRequest(request);
if (response.Entries.Count == 0)
{
return null;
}
else
{
foreach (SearchResultEntry entry in response.Entries)
{
if (entry.Attributes["member"] != null)
{
roles = (entry.Attributes["member"].GetValues(typeof(string))).ToArray().Select(r => r.ToString()
.Substring(r.ToString().IndexOf("cn=") + 3,
r.ToString().IndexOf(",") - 3))
.ToList();
}
}
}
return roles;
I don't think you're using Active Directory. What tipped me off is that you're getting data from the member attribute of a user. That's not how it works with Active Directory (it would be memberOf).
I'm not entirely sure what you're asking for. Your title mentioned "nested groups", which means when one group is a member of another group. So I assume that would mean that you want to find every group the user is a member of and all groups that those groups are members of, etc. If that's the case, you will really have to find out what type of server you're connecting to before anyone can give you a good answer on that.
But in your question you say "How can i get all the groups/roles?" So does that mean you just want to find every group that exists? To do that, you can just do a new search and use this as the filter:
(objectClass=group)
For adding a user to a group, I think it would be something like this (where userDn is the distinguishedName of the user you want to add, and groupDn is that of the group):
var mod = new DirectoryAttributeModification {
Name = "member",
Operation = DirectoryAttributeOperation.Add
}
mod.Add(userDn);
var response = (ModifyResponse) connectionObject.SendRequest(
new ModifyRequest {
DistinguishedName = groupDn,
Modifications = { mod }
}
);
But I've never actually used LdapConnection, so you might need to tweak it.
By default, the ADLDS or AD MemberOf (User object) Member (Group object) attribute is not retrieved.
Example Solution for User
SearchRequest request = new SearchRequest("", "(&(objectClass=user)(mail=myusername))", System.DirectoryServices.Protocols.SearchScope.Subtree);
request.Attributes.Add("memberOf");
or Group
SearchRequest request = new SearchRequest("", "(&(objectClass=group))", System.DirectoryServices.Protocols.SearchScope.Subtree);
request.Attributes.Add("member");
Default LDAP Filters and Attributes for Users, Groups and Containers

How do display security groups and users in that security group using List View?

I need to show all my security groups and the users that are members of the security groups in a listView.
At the moment I can display all the security groups but not sure how to display the users in the security group.
Here is my code I am currently using:
private void Security_group_btn_Click(object sender, EventArgs e)
{
DirectorySearcher searcher = new DirectorySearcher(DomainName);
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, DomainName);
UserPrincipal userPrin = new UserPrincipal(ctx);
userPrin.Name = "*";
var search = new System.DirectoryServices.AccountManagement.PrincipalSearcher();
search.QueryFilter = userPrin;
var results = searcher.FindAll();
ListView lvwListView = this.security_listView;
lvwListView.Clear();
lvwListView.Columns.Add("Security Group", 175, HorizontalAlignment.Left);
lvwListView.Columns.Add("Users", 175, HorizontalAlignment.Left);
searcher.Filter = "(&(objectClass=group))";
searcher.SearchScope = SearchScope.Subtree;
searcher.PropertiesToLoad.Add("sAMAccountName");
SearchResultCollection result = searcher.FindAll();
foreach (SearchResult entry in result)
{
lvwListView.Items.Add(entry.GetDirectoryEntry().Properties["sAMAccountName"].Value.ToString());
}
result.Dispose();
searcher.Dispose();
}
}
}
So basically I would like to display something like this in my ListView:
Security Group Name
Users User1
User2
User3
Administrators User1
User2
User3
User4
Thanks
First, be careful of this:
lvwListView.Items.Add(entry.GetDirectoryEntry().Properties["sAMAccountName"].Value.ToString());
Particularly, using GetDirectoryEntry() just to get a value. When you get a value using DirectoryEntry.Properties, it checks to see if it already has the value you're asking for in the cache. If not, it asks AD for every attribute that has a value, which will usually be a whole lot more data than you actually need. Since you're looping over a bunch of accounts, that can significantly slow you down.
You are already asking for the sAMAccountName in the search results, since you used searcher.PropertiesToLoad.Add("sAMAccountName"), so you can pull the value from the SearchResult object directly:
lvwListView.Items.Add((string) entry.Properties["sAMAccountName"][0]);
I talk more about that in an article I wrote about better performance when programming with Active Directory.
Now to actually answer your question:
Getting the members is a different story. You can ask for the member attribute in the search, but you get problems when a group has more than 1500 members. You have to ask for the members in pages. But also, you have to decide how you are going to handle nested groups: when a group is a member of a group. Do you want to just list that group name? Or do you want to look inside that group and get those members too?
I wrote a whole article about this too: Find all the members of a group
But it's more than likely you can just use one of the sample methods I show in that article. This will take a DirectoryEntry object of a group, and give you a list of strings containing the DOMAIN\username of each object inside the group (you can change that if you want). You can use the recursive parameter to decide what you want to do with nested groups.
public static IEnumerable<string> GetGroupMemberList(DirectoryEntry group, bool recursive = false) {
var members = new List<string>();
group.RefreshCache(new[] { "member" });
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 (recursive && memberDe.Properties["objectClass"].Contains("group")) {
members.AddRange(GetGroupMemberList(memberDe, true));
} 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;
}
You would call it from your code like this:
var members = GetGroupMemberList(entry.GetDirectoryEntry());
Then you can display them however you want.
This will work fine if you are only working in a single AD forest. But if your domain trusts other domains outside your forest and you can expect to see users from those domains in the groups you are looking at, then you need to account for that. But I cover that in that article. I also cover primary groups there too, which is rare that you need to care about, but you might.
Update: To add two columns in a ListViewItem, you have to create it separately. You can use the constructor that takes a string array of the "subitems", as they call it.
You could put something like this inside your loop where you loop through the members, where groupName is the name of the group and member is the name of the group member.
lvwListView.Items.Add(new ListViewItem(new [] {
groupName,
member
});
If you only want the groupName on the first one, then you can just put an empty string ("") instead of groupName for the others.

How can I filter users by group membership across multiple OU's?

I have a user query that can't be done at an OU level. I am trying to only return users where they are a member of a group (I need to filter by the group string value, not the actual group object). Here is what it currently looks like:
using (var entry = new DirectoryEntry("LDAP://foo.net/DC=appName,DC=domainName,DC=com", Username, Password)) {
using (var searcher = new DirectorySearcher()) {
searcher.Filter = "(&(objectClass=user))";
searcher.SearchRoot = entry;
searcher.PropertiesToLoad.Add("memberOf");
foreach (SearchResult sr in searcher.FindAll()) {
var member = GetSearchResultProperty(sr, "memberOf");
}
}
}
This query goes across multiple OU's where there are different user and group containers.
Is there a way I can filter on the memberOf property when I execute my query (without specifying any sort of OU)?
Just add another term to the filter:
searcher.Filter = "(&(objectClass=user)(memberOf=myGroup))";
It looks like what I am trying to do isn't possible at the time of query execution because the memberOf property needs to be a full path to a group. In my case, I don't actually care about the group object, but, rather the group name (each OU will have different groups, but, could have the same group name). I had to take a different approach to solve this which could have performance implications later on. Just in case someone else finds this question looking for an answer, here is how I am solving it as of now:
using (var entry = new DirectoryEntry("LDAP://foo.net/DC=appName,DC=domainName,DC=com", Username, Password)) {
using (var searcher = new DirectorySearcher()) {
searcher.Filter = "(&(objectClass=user))";
searcher.SearchRoot = entry;
searcher.PageSize = 5000;
searcher.PropertiesToLoad.Add(DirectoryConstants.AD_PROPERTY_MEMBER_OF);
searcher.PropertiesToLoad.Add(DirectoryConstants.AD_PROPERTY_DISPLAY_NAME);
foreach (SearchResult sr in searcher.FindAll()) {
//The memberOf property will be a string array if the user is in multiple groups.
string[] memberOf = GetSearchResultProperties(sr, DirectoryConstants.AD_PROPERTY_MEMBER_OF);
//Check if the user is in the group by analyzing the string.
var group = memberOf.FirstOrDefault(m => m.StartsWith(string.Format("CN={0},", groupName)));
if (group != null) {
//The user is in the group!
}
}
}
}
You need to include the full distinguished name of the group in your search filter, as the memberOf property will contain that value.
searcher.Filter = "(&(objectClass=user)(memberOf=CN=myGroup,CN=groups,OU=theOU,DC=appName,DC=domainName,DC=com))";
EDIT :
My apologies, as I misread the question. Without including an OU, the only other way of doing this is taking the opposite approach, and locating the group object first :
searcher.Filter = "(&(objectClass=group)(name=Your Group Name)"
Then iterate through the DirectoryEntry using it's properties :
foreach(object dn in group.Properties["member"] )
{
string DistinguishedName = dn.ToString();
}
If you're retrieving more than 1,000+ users though, you'll need to take a more segmented approach, which is detailed in this article.

How to get a collection of strings of what Groups a user belongs to?

if (DomainHelpers.DomainExists(ConnectionString))
{
using(var baseDirectory = new DirectoryEntry(ConnectionString))
{
baseDirectory.Username = Username;
baseDirectory.Password = Password;
using (DirectorySearcher searcher = new DirectorySearcher())
{
searcher.SearchRoot = baseDirectory;
searcher.Filter = "(objectCategory=user)";
searcher.SearchScope = SearchScope.Subtree;
var userResults = searcher.FindAll();
foreach (SearchResult user in userResults)
{
var newUser = new User();
newUser.Name = user.Properties["name"][0].ToString();
newUser.Path = user.Path;
//.Groups is just a List<string>.
newUser.Groups = user.Properties?????
_users.Add(newUser);
}
}
}
}
How do I retrieve a collection of groups the user belongs to?
Thank you! :)
user.Properties["memberOf"]
don't forget to add searcher.PropertiesToLoad.Add("memberOf"); before ...searcher.FindAll()
To populate your property:
//.Groups is just a List<string>.
foreach(object group in user.Properties["memberOf"])
newUser.Groups.Add((string)group);
You should use System.DirectoryServices.AccountManagement. It's much easier. Here is a nice code project article giving you an overview on all the classes in this DLL.
It's really hard to get it right using DirectoryEntry. First of all, getting memberOf attribute doesn't give you primary group. Also, if the user has a domain local group from another domain, it won't show up in memberOf attribute. You can check here for details. Here is how the code looks like if you switch to use System.DirectoryServices.AccountManagement. The following code can find the immediate groups this user assigned to, which includes the primary group.
UserPrincipal user = UserPrincipal.FindByIdentity(new PrincipalContext (ContextType.Domain, "mydomain.com"), IdentityType.SamAccountName, "username");
foreach (GroupPrincipal group in user.GetGroups())
{
Console.Out.WriteLine(group);
}

Active Directory - Find a computer in a group

I am trying to do a very simple AD query to see if a computer is in a group. The following code seems intuitive enough but does not work. The LDAPString is a fully distinguised name for the group that the computer referenced by NetBIOSName is a memberOf.
public bool IsComputerInADGroup(String LDAPString, String NetBIOSName)
{
using (DirectoryEntry entry = new DirectoryEntry(String.Format(#"LDAP://{0}", LDAPString)))
using (DirectorySearcher computerSearch = new DirectorySearcher(entry))
{
ComputerSearch.Filter = String.Format("(&(objectCategory=computer)(CN={0}))", NetBIOSName);
SearchResult match = ComputerSearch.FindOne();
if (match != null)
{
return true;
}
}
return false;
}
Can someone please explain why this is incorrect and what the correct/fastest way to to perform this search is.
Thanks
P
Your basic assumption is wrong - a computer (or user) cannot be in a group implying "containment" inside a group; a user or computer is only inside an OU.
A user or computer can be member of any number of groups - but you need to check this against the member property of the group (or the memberOf attribute of the element that is a member of that group).
So the easiest way, really, is to
bind to the object in question
refresh its property cache to get the latest entries in memberOf
enumerate of its memberOf entries and see if the group you're looking for is present
Something like:
public static bool IsAccountMemberOfGroup(string account, string group)
{
bool found = false;
using (DirectoryEntry entry = new DirectoryEntry(account))
{
entry.RefreshCache(new string[] { "memberOf" });
foreach (string memberOf in entry.Properties["memberOf"])
{
if (string.Compare(memberOf, group, true) == 0)
{
found = true;
break;
}
}
}
return found;
}
Call this like so:
bool isMemberOf =
IsAccountMemberOfGroup("LDAP://cn=YourComputer,dc=Corp,dc=com",
"CN=yourGroupInQuestion,OU=SomeOU,dc=corp,dc=com");
and you should be fine.
Update: if you're on .NET 3.5, you could also use the new System.DirectoryServices.AccountManagement namespace and LINQ to make things even easier:
public static bool IsAccountMemberOfGroup2(PrincipalContext ctx, string account, string groupName)
{
bool found = false;
GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, groupName);
if (group != null)
{
found = group.GetMembers()
.Any(m => string.Compare(m.DistinguishedName, account, true) == 0);
}
return found;
}
and call this:
// establish default domain context
PrincipalContext domain = new PrincipalContext(ContextType.Domain);
// call your function
bool isMemberOf =
IsAccountMemberOfGroup2(domain,
"cn=YourComputer,dc=Corp,dc=com",
"CN=yourGroupInQuestion,OU=SomeOU,dc=corp,dc=com");
when you say it doesn't work, you mean that you can't find the computer? If so, check first if the computer is in the group, there is a nice tool out there named Active directory exporer wich can help you: http://technet.microsoft.com/en-us/sysinternals/bb963907.aspx
If it is in the group what you can try is to eliminate the filter for the computer name on the filter and iterate over the resultset in order to find out if your element is there:
ComputerSearch.Filter = ("(&(objectCategory=computer))";
SearchResult match = ComputerSearch.FindAll();
Here's some infos on how to query AD : http://www.codeproject.com/KB/system/everythingInAD.aspx

Categories

Resources