Azure Active Directory search in hebrew - c#

Using the Graph API I am attempting to load a list of Azure Active Directory Users according to their department. I do it like this:
public List<User> GetUsersByDepartment(string dept)
{
QueryOperationResponse<User> response;
var users = DirectoryService.users;
users = (DataServiceQuery<User>)(users.Where(user => user.department.Equals(dept)));
response = users.Execute() as QueryOperationResponse<User>;
List<User> deptUsers = response.ToList();
return deptUsers;
}
If dept is in English, I get corresponding results. If dept is in Hebrew, I get no results at all... The following works but requires a full load of the Users list:
public List<User> GetUsersByDepartment(string dept)
{
var users = DirectoryService.users.ToList();
List<User> deptUsers = users.Where(user => user.department.Equals(dept)).ToList();
return deptUsers;
}
Does the Azure Active Directory support searches in Hebrew? If so, what am I missing in my first example? Could there be a setting in the Azure Active Directory itself?

This was an issue in the service side that has now been fixed.
You should be able to verify this easily at https://graphexplorer.cloudapp.net. Just sign in to a directory with users with Unicode characters in the DisplayName (or department), and try the REST query:
https://graph.windows.net/<your domain name>/users()?$filter=startswith(displayName,'%C3%80%C3%A0%C3%88')`
(Changing the encoded Unicode characters to whatever you're looking for.)

Related

Is there a way to fetch data based on user assigned permissions on Cosmos DB?

I have a container in CosmosDB that contains information about some
countries.
I have a Storage table that contains the user access permissions
on specific countries.
I.E the user testmail#test.com can access the USA, UK only countries.
In my azure function, after successfully authentication i got the
user email.
I perform a search on the user permission storage table to
fetch the specific user countries access.
Manipulate the query string with the fetched user countries string
to make the final query.
Is this a proper way to fetch resources from CosmosDB based on user assigned permissions?
If it's not:
How to assign custom permissions on azure active directory to fetch data based on that permissions?
The code below works.
Here is the body of the function:
//**GET THE USER INFO**
var user = req.Authenticate();
//**GET THE USER PERMISSIONS**
var userPermissions = await new CalculateUserPermissions().GetPermissions(user.UserId);
//**GET THE USER COUNTRIES ['USA', 'UK']**
List<string> countries = userPermissions.Countries;
StringBuilder query = new StringBuilder(#"
SELECT
c.country,
c.code,
c.info,
FROM c");
if (countries == null)
{
query.Append($" GROUP BY c.country, c.code) as root");
}
else
{
query.Append($" WHERE");
foreach (string country in countries)
{
query.Append($" c.code = '{country}' OR");
}
string builtQuery = query.ToString();
// TRIM THE LAST 'OR'
builtQuery = builtQuery.Substring(0, builtQuery.Length - 3);
query = new StringBuilder(builtQuery);
query.Append($" GROUP BY c.country, c.code");
}
}
...call the database with the query string...
Cosmos DB does not currently support Azure AD authentication or authorization. It does have it's own user and permissions model that you can map between your AAD users and Cosmos but you can only assign permissions down to the partition key level. There is no support for row-level permissions in Cosmos DB.
If the number of countries is not too large, I think it would be easier to simply create a container of users and embed country codes as an array within each user.

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

Getting email addresses from AD Group MVC

I have the name of my AD Group and I need to return a list of every single email address of the users found inside. I've been looking around trying to find the answer to this but all of the results I've found are aimed at checking an AD Group for a single users email address. Often by using their windows log on username.
However, what I'm wanting to do is to grab every email address inside my target active directory group. As eventually this will be turned into an automated email that needs to be sent to only those individuals within the active directory group.
The project I'm building is making use of MVC 5.
Could someone please point me in the right direction for this?
You need to split this into two steps:
Find the group (I assume you just have the name of the group)
Get all the members.
Finding the group
You can use GroupPrincipal.FindByIdentity, which is arguably easier, but I find that's quite slow (slower the bigger your group is). I prefer to use DirectorySearcher/DirectoryEntry (which is what GroupPrincipal uses behind the scenes anyway).
To find the group, your code would look something like this:
var groupName = "MyGroup";
var search = new DirectorySearcher() {
Filter = $"(&(objectClass=group)(cn={groupName}))"
};
search.PropertiesToLoad.Add("cn"); //this is just to prevent it from returning every attribute
//This will throw an exception if the group is not found
var group = ((SearchResult)search.FindOne()).GetDirectoryEntry();
If you already have the distinguishedName of the group, you can actually skip that and just make a DirectoryEntry:
var group = new DirectoryEntry($"LDAP://{disginguishedName}");
Getting the members
I wrote an article on how to do this, with example C# code: Find all the members of a group
The example code in that article returns the DOMAIN\username, so here is the same method, modified to return a list of email addresses for all the members that have one.
Pass this method the DirectoryEntry object we found for the group. Set recursive to true if you want it to look inside 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", "mail" });
if (recursive && memberDe.Properties["objectClass"].Contains("group")) {
members.AddRange(GetGroupMemberList(memberDe, true));
} else {
var email = memberDe.Properties["mail"].Value.ToString();
if (!string.IsNullOrEmpty(email)) {
members.Add(email);
}
}
}
}
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;
}
In my article, I also talk about primary groups and Foreign Security Principals, but you don't need to worry about that here since it's not applicable to distribution lists.

When searching Global Address List, is there a way to do a partial search and not just a "startsWith"

I have the following code to search the global address book by a certain string:
"CONF"
var esb = new ExchangeServiceBinding();
esb.Url = #"https://myurl.com/EWS/Exchange.asmx";
esb.Credentials = new NetworkCredential(_user,_pwd, _domain);
var rnType = new ResolveNamesType {ReturnFullContactData = true, UnresolvedEntry = "CONF"};
ResolveNamesResponseType response = esb.ResolveNames(rnType);
ArrayOfResponseMessagesType responses = resolveNamesResponse.ResponseMessages;
var responseMessage = responses.Items[0] as ResolveNamesResponseMessageType;
ResolutionType[] resolutions = responseMessage.ResolutionSet.Resolution;
the issue is that it seems to be doing a "starts with" search so I have a name called:
"CONF-123" it will show up but if i have a name "JOE-CONF" then it will not.
How can I do a partial string search on this line
var rnType = new ResolveNamesType {ReturnFullContactData = true, UnresolvedEntry = "CONF-"};
i was hoping there was something like:
var rnType = new ResolveNamesType {ReturnFullContactData = true, UnresolvedEntry = "%CONF-%"};
but that doesn't seem to work.
EDIT: Jan 4,2016 - Added sample code for searching AD.
What won't work
Searching the GAL via ResolveNames always uses prefix-string match for Ambiguous Name Resolution (ARN). Although EWS documentation does not say this explicitly, the Exchange ActiveSync documentation does. EWS and Exchange ActiveSync are just protocols; they both rely on ARN underneath, so you are stuck with prefix match, whether you use ActiveSync protocol or EWS.
Here is the relevant quote from Exchange ActiveSync documentation (https://msdn.microsoft.com/en-us/library/ee159743%28v=exchg.80%29.aspx)
The text query string that is provided to the Search command is used
in a prefix-string match
.
What will work
The best thing to do depends on your use case, but here are some ideas:
Search Active Directory in your client program (the program that contains the code you showed in your question)
Set up your own service to search the GAL. Your client program would connect both to Exchange and to your service. Or your service could proxy EWS, so that the client program needs to connect only to your service.
How would you service get the GAL data? One way would be to use EWS ResolveNames repeatedly, to get the GAL data, 100 entries at a time and cache this data in your service. First, retrieve all the "a"s, then all the "b"s, etc. Of course, there can be more than 100 "a"s in the GAL, so just getting all the "a"s could take multiple searches - you would construct your next search string, based on the last entry returned from each search. This can be slow and painful. You would probably want to cache this data in a database and refresh it periodically.
You can also get to GAL through MAPI. You can use MAPI directly (https://msdn.microsoft.com/en-us/library/cc765775%28v=office.12%29.aspx) or through a helper library like Redemption (http://www.dimastr.com/redemption/home.htm). Whether you use MAPI directly or through Redemption, you will need to install Outlook (or Exchange) on the computer where your code is running. Because of this restriction, it may be best to not use MAPI in your client program, but to stick it in a service running on some server and have your client program connect to that service.
AD Code Sample
Another answer provided sample code to search Active Directory. I am adding a code sample that may be better suited for generic use by people who may find this question through search. Compared to the other sample, the code below has the following improvements:
If the search string contains any special characters (like parenthesis), they are escaped, so that the constructed filter string is valid.
Searching by just samaccountname many not be sufficient. If "David Smith" has account name "dsmith", searching for "David" by samaccountname would not find him. My sample shows how to search by more fields and gives some of the fields one may want to search.
Starting at a root like "GC:" is more robust than trying to construct an LDAP entry from Domain.GetComputerDomain().
All IDisposables must be disposed of (usually by using them in a using construct).
// Search Active Directory users.
public static IEnumerable<DirectoryEntry> SearchADUsers(string search) {
// Escape special characters in the search string.
string escapedSearch = search.Replace("*", "\\2a").Replace("(", "\\28")
.Replace(")", "\\29").Replace("/", "\\2f").Replace("\\", "\\5c");
// Find entries where search string appears in ANY of the following fields
// (you can add or remove fields to suit your needs).
// The '|' characters near the start of the expression means "any".
string searchPropertiesExpression = string.Format(
"(|(sn=*{0}*)(givenName=*{0}*)(cn=*{0}*)(dn=*{0}*)(samaccountname=*{0}*))",
escapedSearch);
// Only want users
string filter = "(&(objectCategory=Person)(" + searchPropertiesExpression + "))";
using (DirectoryEntry gc = new DirectoryEntry("GC:")) {
foreach (DirectoryEntry root in gc.Children) {
try {
using (DirectorySearcher s = new DirectorySearcher(root, filter)) {
s.ReferralChasing = ReferralChasingOption.All;
SearchResultCollection results = s.FindAll();
foreach (SearchResult result in results) {
using (DirectoryEntry de = result.GetDirectoryEntry()) {
yield return de;
}
}
}
} finally {
root.Dispose();
}
}
}
}
Though wildcard search isn't possible in EWS, it is possible in AD search. AD Queries support wildcards. i.e., *CONF* can be searched in AD, which will return all results which contain "CONF". Based on the results, query EWS for an corresponding Exchange Object. You need to find a parameter with which you can find the corresponding EWS entry. I guess email address (username) should be sufficient to find the corresponding exchange object.
AD Search code snippet...
private SearchResultCollection SearchByName(string username, string password, string searchKeyword)
{
DirectorySearcher ds = new DirectorySearcher(new DirectoryEntry("LDAP://" + Domain.GetComputerDomain().ToString().ToLower(), username, password));
ds.Filter = "(&((&(objectCategory=Person)(objectClass=User)))(samaccountname=*" + searchKeyword + "*))";
ds.SearchScope = SearchScope.Subtree;
ds.ServerTimeLimit = TimeSpan.FromSeconds(90);
return ds.FindAll();
}
AD Query Example here.
An ambiguous search on an indexed text field can only be done with prefix (or suffix...). That's why Exchange probably implements the query as LIKE 'CONF%'.
I've looked over the documentation and there isn't any way you can bypass it - full table scan (which would have to be the case for %CONF%) does not seem to make sense.
I have also been trying to search the GAL from PHP using php-ews. On a web page the user starts to enter the name to be searched, once 2 chars have been entered a call to read_contacts.php is made passing the 2 chars entered. read_contacts.php uses EWS ResolveNames request to retrieve a limited list of contacts. The code then filters on some criteria and also checks that the displayName starts with the chars entered. This then returns the filtered list:
<?php
$staffName = $_GET['q'];
require_once './php-ews/EWSType.php';
require_once './php-ews/ExchangeWebServices.php';
require_once 'php-ews/EWSType/RestrictionType.php';
require_once 'php-ews/EWSType/ContainsExpressionType.php';
require_once 'php-ews/EWSType/PathToUnindexedFieldType.php';
require_once 'php-ews/EWSType/ConstantValueType.php';
require_once 'php-ews/EWSType/ContainmentModeType.php';
require_once 'php-ews/EWSType/ResolveNamesType.php';
require_once 'php-ews/EWSType/ResolveNamesSearchScopeType.php';
require_once 'php-ews/NTLMSoapClient.php';
require_once 'php-ews/NTLMSoapClient/Exchange.php';
$host = '[exchange server]';
$user = '[exchange user]';
$password = '[exchange password]';
$ews = new ExchangeWebServices($host, $user, $password);
$request = new EWSType_ResolveNamesType();
$request->ReturnFullContactData = true;
$request->UnresolvedEntry = $staffName;
$displayName = '';
$i = 0;
$staff_members = false;
$response = $ews->ResolveNames($request);
if ($response->ResponseMessages->ResolveNamesResponseMessage->ResponseClass == 'Error' && $response->ResponseMessages->ResolveNamesResponseMessage->MessageText == 'No results were found.') {
}
else {
$numNamesFound = $response->ResponseMessages->ResolveNamesResponseMessage->ResolutionSet->TotalItemsInView;
$i2=0;
for ($i=0;$i<$numNamesFound;$i++) {
if ($numNamesFound == 1) {
$displayName = $response->ResponseMessages->ResolveNamesResponseMessage->ResolutionSet->Resolution->Contact->DisplayName;
}
else {
$displayName = $response->ResponseMessages->ResolveNamesResponseMessage->ResolutionSet->Resolution[$i]->Contact->DisplayName;
}
echo "DisplayName: " . $displayName . "\n";
if (stripos($displayName, 'External') == true) {
}
else {
$searchLen = strlen($staffName);
if (strcasecmp(substr($displayName, 0, $searchLen), $staffName) == 0) {
if ($numNamesFound == 1) {
$emailAddress = $response->ResponseMessages->ResolveNamesResponseMessage->ResolutionSet->Resolution->Mailbox->EmailAddress;
}
else {
$emailAddress = $response->ResponseMessages->ResolveNamesResponseMessage->ResolutionSet->Resolution[$i]->Mailbox->EmailAddress;
}
$staff_members[$i2] = array( 'name' => $displayName,'email' => $emailAddress );
$i2++;
}
}
}
$staffJson = json_encode($staff_members);
echo $staffJson;
}
Most of the time this seems to work, ie: 'mi', 'jo' returns Mike, Michael, or Joe, John, except when I sent 'Si' or 'si', for Simon, then the ResolveNames call returns the first 100 entries from the GAL.
For the time being I have increased the minimum number of characters to be entered to 3, ie: 'sim' and this works. The problem will be when we get staff with only 2 character first names.
I just thought I would share the code to see if it helps and to see if anyone knows why my 'si' does not work properly.
I am accessing Exchange 2010

Find Recursive Group Membership (Active Directory) using C#

I am looking to get a list of all of the groups that a user is a member of in Active Directory, both explicitly listed in the memberOf property list as well as implicitly through nested group membership. For example, if I examine UserA and UserA is a part of GroupA and GroupB, I also want to list GroupC if GroupB is a member of GroupC.
To give you a bit more insight into my application, I will be doing this on a limited basis. Basically, I want a security check occasionally that will list these additional memberships. I will want to differentiate the two but that shouldn't be hard.
My problem is that I have not found an efficient way to make this query work. The standard text on Active Directory (This CodeProject Article) shows a way to do this that is basically a recursive lookup. That seems terribly inefficient. Even in my small domain, a user might have 30+ group memberships. That means 30+ calls to Active Directory for one user.
I've looked into the following LDAP code to get all of the memberOf entries at once:
(memberOf:1.2.840.113556.1.4.1941:={0})
where {0} would be my LDAP path (ex: CN=UserA,OU=Users,DC=foo,DC=org). However, it does not return any records. The downside of this method, even if it worked, would be that I wouldn't know which group was explicit and which was implicit.
That is what I have so far. I would like to know if there is a better way than the CodeProject article and, if so, how that could be accomplished (actual code would be wonderful). I am using .NET 4.0 and C#. My Active Directory is at a Windows 2008 functional level (it isn't R2 yet).
Thirst thanks for this an interesting question.
Next, just a correction, you say :
I've looked into the following LDAP code to get all of the memberOf entries at once:
(memberOf:1.2.840.113556.1.4.1941:={0})
You don't make it work. I remember I make it work when I learnt about its existence, but it was in an LDIFDE.EXE filter. So I apply it to ADSI in C# and it's still working. There were too much parenthesis in the sample I took from Microsoft, but it was working (source in AD Search Filter Syntax).
According to your remark concerning the fact that we don't know if a user explicitly belongs to the group I add one more request. I know this is not very good, but it's the best I'am abable to do.
static void Main(string[] args)
{
/* Connection to Active Directory
*/
DirectoryEntry deBase = new DirectoryEntry("LDAP://WM2008R2ENT:389/dc=dom,dc=fr");
/* To find all the groups that "user1" is a member of :
* Set the base to the groups container DN; for example root DN (dc=dom,dc=fr)
* Set the scope to subtree
* Use the following filter :
* (member:1.2.840.113556.1.4.1941:=cn=user1,cn=users,DC=x)
*/
DirectorySearcher dsLookFor = new DirectorySearcher(deBase);
dsLookFor.Filter = "(member:1.2.840.113556.1.4.1941:=CN=user1 Users,OU=MonOu,DC=dom,DC=fr)";
dsLookFor.SearchScope = SearchScope.Subtree;
dsLookFor.PropertiesToLoad.Add("cn");
SearchResultCollection srcGroups = dsLookFor.FindAll();
/* Just to know if user is explicitly in group
*/
foreach (SearchResult srcGroup in srcGroups)
{
Console.WriteLine("{0}", srcGroup.Path);
foreach (string property in srcGroup.Properties.PropertyNames)
{
Console.WriteLine("\t{0} : {1} ", property, srcGroup.Properties[property][0]);
}
DirectoryEntry aGroup = new DirectoryEntry(srcGroup.Path);
DirectorySearcher dsLookForAMermber = new DirectorySearcher(aGroup);
dsLookForAMermber.Filter = "(member=CN=user1 Users,OU=MonOu,DC=dom,DC=fr)";
dsLookForAMermber.SearchScope = SearchScope.Base;
dsLookForAMermber.PropertiesToLoad.Add("cn");
SearchResultCollection memberInGroup = dsLookForAMermber.FindAll();
Console.WriteLine("Find the user {0}", memberInGroup.Count);
}
Console.ReadLine();
}
In my test tree this give :
LDAP://WM2008R2ENT:389/CN=MonGrpSec,OU=MonOu,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpSec,OU=MonOu,DC=dom,DC=fr
cn : MonGrpSec
Find the user 1
LDAP://WM2008R2ENT:389/CN=MonGrpDis,OU=ForUser1,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpDis,OU=ForUser1,DC=dom,DC=fr
cn : MonGrpDis
Find the user 1
LDAP://WM2008R2ENT:389/CN=MonGrpPlusSec,OU=ForUser1,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpPlusSec,OU=ForUser1,DC=dom,DC=fr
cn : MonGrpPlusSec
Find the user 0
LDAP://WM2008R2ENT:389/CN=MonGrpPlusSecUniv,OU=ForUser1,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpPlusSecUniv,OU=ForUser1,DC=dom,DC=fr
cn : MonGrpPlusSecUniv
Find the user 0
(edited)
'1.2.840.113556.1.4.1941' is not working in W2K3 SP1, it begins to work with SP2. I presume it's the same with W2K3 R2. It's supposed to work on W2K8. I test here with W2K8R2. I'll soon be able to test this on W2K8.
If there is no way other than recursive calls (and I don't believe there is) then at least you can let the framework do the work for you: see the UserPrincipal.GetAuthorizationGroups method (in the System.DirectoryServices.AccountManagement namespace and introduced in .Net 3.5)
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.
Compare with the results of GetGroups ("Returns a collection of group objects that specify the groups of which the current principal is a member") to see whether the membership is explicit or implicit.
Use the ldap filter recursively but query for all groups returned after each query to reduce the number of round trips.
Ex:
Get all groups where user is a member
Get all groups where Step 1 Groups are members
Get all groups where Step 2 Groups are members
...
In my experience there are rarely more then 5 but should definitiely be much less then 30.
Also:
Make sure to only pull the properties
you are going to need back.
Caching results can significantly aid
performance but made my code much
more complicated.
Make sure to utilize connection pooling.
Primary group has to be handled seperately
you can utilize the tokenGroups and tokenGroupsGlobalAndUniversal properties if you are on Exchange server.
tokenGroups will give you all the security groups this user belongs to, including nested groups and domain users, users, etc
tokenGroupsGlobalAndUniversal will include everything from tokenGroups AND distribution groups
private void DoWorkWithUserGroups(string domain, string user)
{
var groupType = "tokenGroupsGlobalAndUniversal"; // use tokenGroups for only security groups
using (var userContext = new PrincipalContext(ContextType.Domain, domain))
{
using (var identity = UserPrincipal.FindByIdentity(userContext, IdentityType.SamAccountName, user))
{
if (identity == null)
return;
var userEntry = identity.GetUnderlyingObject() as DirectoryEntry;
userEntry.RefreshCache(new[] { groupType });
var sids = from byte[] sid in userEntry.Properties[groupType]
select new SecurityIdentifier(sid, 0);
foreach (var sid in sids)
{
using(var groupIdentity = GroupPrincipal.FindByIdentity(userContext, IdentityType.Sid, sid.ToString()))
{
if(groupIdentity == null)
continue; // this group is not in the domain, probably from sidhistory
// extract the info you want from the group
}
}
}
}
}
If you are using .NET 3.5 or higher you can use the System.DirectoryServices.AccountManagement namespace which really makes this easy.
See the related answer here: Active Directory nested groups
static List<SearchResult> ad_find_all_members(string a_sSearchRoot, string a_sGroupDN, string[] a_asPropsToLoad)
{
using (DirectoryEntry de = new DirectoryEntry(a_sSearchRoot))
return ad_find_all_members(de, a_sGroupDN, a_asPropsToLoad);
}
static List<SearchResult> ad_find_all_members(DirectoryEntry a_SearchRoot, string a_sGroupDN, string[] a_asPropsToLoad)
{
string sDN = "distinguishedName";
string sOC = "objectClass";
string sOC_GROUP = "group";
string[] asPropsToLoad = a_asPropsToLoad;
Array.Sort<string>(asPropsToLoad);
if (Array.BinarySearch<string>(asPropsToLoad, sDN) < 0)
{
Array.Resize<string>(ref asPropsToLoad, asPropsToLoad.Length+1);
asPropsToLoad[asPropsToLoad.Length-1] = sDN;
}
if (Array.BinarySearch<string>(asPropsToLoad, sOC) < 0)
{
Array.Resize<string>(ref asPropsToLoad, asPropsToLoad.Length+1);
asPropsToLoad[asPropsToLoad.Length-1] = sOC;
}
List<SearchResult> lsr = new List<SearchResult>();
using (DirectorySearcher ds = new DirectorySearcher(a_SearchRoot))
{
ds.Filter = "(&(|(objectClass=group)(objectClass=user))(memberOf=" + a_sGroupDN + "))";
ds.PropertiesToLoad.Clear();
ds.PropertiesToLoad.AddRange(asPropsToLoad);
ds.PageSize = 1000;
ds.SizeLimit = 0;
foreach (SearchResult sr in ds.FindAll())
lsr.Add(sr);
}
for(int i=0;i<lsr.Count;i++)
if (lsr[i].Properties.Contains(sOC) && lsr[i].Properties[sOC].Contains(sOC_GROUP))
lsr.AddRange(ad_find_all_members(a_SearchRoot, (string)lsr[i].Properties[sDN][0], asPropsToLoad));
return lsr;
}
static void Main(string[] args)
{
foreach (var sr in ad_find_all_members("LDAP://DC=your-domain,DC=com", "CN=your-group-name,OU=your-group-ou,DC=your-domain,DC=com", new string[] { "sAMAccountName" }))
Console.WriteLine((string)sr.Properties["distinguishedName"][0] + " : " + (string)sr.Properties["sAMAccountName"][0]);
}

Categories

Resources