Get AD users and groups from client - c#

Is there any way possible to access a clients AD users and groups outsite an intranet setting?
I am not looking for azure solutions.
The tech i am working with is .Net Core web api and Angular as frontend.
Retrieving AD information on my own domain is achievable and i could get UserPrincipals,
but if the web API and AD is not hosted on the same server, how does that work?

I can't tell you exactly how to do it, since I haven't done it with .NET Core yet, but I can tell you what you need to do and you can look up more details about each part.
Use forms authentication. You will need a login page that asks for their username and password.
Validate the credentials. There are several ways to do this. My favourite is in this answer, which uses LdapConnection from System.DirectoryServices.Protocols because it's the least amount of network requests needed to do the job and it will tell your why credentials fail (which would let you take the user to a "change password" page if their password has expired, for example). However, using DirectoryEntry/DirectorySearcher is easier for looking up groups, so you might want to also use that for validating too, by using the user's credentials in the constructor of DirectoryEntry (but you'd lose knowing the reason for failed attempts).
Look up the user's account. I prefer using DirectoryEntry/DirectorySearcher from System.DirectoryServices for this. Eldar's answer shows how to do that.
Find the user's groups. I wrote a whole article about this: Finding all of a user's groups. Assuming you only have one domain in your environment, and you already have a DirectoryEntry object for the user's account, this code will work:
private static IEnumerable<string> GetUserMemberOf(DirectoryEntry de) {
var groups = new List<string>();
//retrieve only the memberOf attribute from the user
de.RefreshCache(new[] {"memberOf"});
while (true) {
var memberOf = de.Properties["memberOf"];
foreach (string group in memberOf) {
var groupDe = new DirectoryEntry($"LDAP://{group.Replace("/", "\\/")}");
groupDe.RefreshCache(new[] {"cn"});
groups.Add(groupDe.Properties["cn"].Value as string);
}
//AD only gives us 1000 or 1500 at a time (depending on the server version)
//so if we've hit that, go see if there are more
if (memberOf.Count != 1500 && memberOf.Count != 1000) break;
try {
de.RefreshCache(new[] {$"memberOf;range={groups.Count}-*"});
} catch (COMException e) {
if (e.ErrorCode == unchecked((int) 0x80072020)) break; //no more results
throw;
}
}
return groups;
}
If you have more than one domain in your environment, then it'll be a bit more complicated.

var entry = new DirectoryEntry("LDAP://DC=DomainController,DC=com","UserName","P4$$w0Rd!???");
// userName password must be valid
var searcher = new DirectorySearcher(entry);
searcher.PropertiesToLoad.Add("sn");
var accName = "accNameToSearch"; // you can also use wildcart
// https://learn.microsoft.com/en-us/windows/win32/adsi/search-filter-syntax
searcher.Filter = $"(&(objectCategory=person)(objectClass=user)(sAMAccountName={accName}))";
var result = searcher.FindOne();
var sn = result.Properties["sn"];
There is no UserPrincipal class for that nuget package yet. But you can still query users and other stuff with ad query syntax like above.

Related

Search in global catalog of AD Forest

I need to do LDAP query across all DCs of Forest. I found similar post, from which is following code:
using (Forest currentForest = Forest.GetCurrentForest())
{
using (GlobalCatalog globalCatalog = currentForest.FindGlobalCatalog())
{
using (DirectorySearcher searcher = globalCatalog.GetDirectorySearcher())
{
searcher.Filter = "filter to verify existence of user account according to supplied sid";
SearchResult result = searcher.FindOne();
return (result != null);
}
}
}
Above code works in my test scenario. Is this the correct way to query information across domains? There are a lot of other posts where they manually enter GC location or search through list of domains. Therefore is there anything I should pay attention to or that could go wrong with this code?
Use the Domains property of System.DirectoryServices.ActiveDirectory.Forest to iterate through the domains and then the DomainControllers property of System.DirectoryServices.ActiveDirectory.Domain to iterate though domain controllers, running whatever query you want on each.

How to tell if SPUser is Active Directory account

I'm working on a project where the client wants to restrict some content to only Active Directory users . Is there any way to identify that a SPUser is an AD user short of parsing the username string for the domain (or something along those lines). Something like SPUser.IsADUser would be awesome.
Edit
This seems to work, but I'm not sure if this is reliable enough? For this use case, identifying that a user is a windows user is enough (there are no local system accounts)
SPUser user = SPContext.Current.Web.CurrentUser;
string userName = user.LoginName.Substring(user.LoginName.IndexOf('|') + 1);
SPPrincipalInfo info = SPUtility.ResolveWindowsPrincipal(SPContext.Current.Site.WebApplication, userName, SPPrincipalType.User, false);
if(info != null){
//THIS IS A WINDOWS ACCOUNT
}
In my experience it is much better to use audiences for this purpose. You then can easily trim any web part using "Audience" property. You can read about audiences here. Of course it will only work if you have user profile synchronization configured.

ASP.NET Active Directory C# field specification

We've got an active directory here. provided the unique user id of the user, I need to access the organization->manager->name attribute related to that userid. Basically this will be used to send an approval form to the manager of the person submitting request.
Any idea how this could be done?
You can use the following code :
/* Retreiving object from SID
*/
string SidLDAPURLForm = "LDAP://WM2008R2ENT:389/<SID={0}>";
System.Security.Principal.SecurityIdentifier sidToFind = new System.Security.Principal.SecurityIdentifier("S-1-5-21-3115856885-816991240-3296679909-1106");
/*
System.Security.Principal.NTAccount user = new System.Security.Principal.NTAccount("SomeUsername");
System.Security.Principal.SecurityIdentifier sidToFind = user.Translate(System.Security.Principal.SecurityIdentifier)
*/
DirectoryEntry userEntry = new DirectoryEntry(string.Format(SidLDAPURLForm, sidToFind.Value));
string managerDn = userEntry.Properties["manager"].Value.ToString();
But you can also find in this post other ways to seach bind to Active-directory.
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
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
if(user != null)
{
// do something here....
}
// find the group in question
GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, "YourGroupNameHere");
// if found....
if (group != null)
{
// iterate over members
foreach (Principal p in group.GetMembers())
{
Console.WriteLine("{0}: {1}", p.StructuralObjectClass, p.DisplayName);
// do whatever you need to do to those members
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
I'm not 100% sure what you want to do in your concrete case... the UserPrincipal has an EmployeeId property - is that what you want to search for?
Use the System.DirectoryServices.DirectoryEntry class to read out the appropriate property of the user object. The constructor of DirectoryEntry requires that you have an LDAP path to the user. Getting the LDAP path can often be tricky though as IIS prefers handing over the SAM account name only. If you provide more details of what the user id you have looks like it is easier to point you in the right direction.
To do this the account which runs the ASP.NET application needs read access to the AD, which probably doesn't have by default. Changing the application pool to run under "NetworkService" is the easiest way if the web server belongs to the AD. The ASP.NET app will then use the MACHINE$ account of the server to access the AD.

Is there a way to enable referral chasing for UserPrincipal.FindByIdentity()?

I have a .NET 3.5 web application that uses the System.DirectoryServices.AccountManagement classes. When I search for some users I get a PrincipalOperationException: A referral was returned from the server. If I did this the old school way with my own LDAP code I could enable chasing of referrals. Do I need to rewrite my code?
My code looks like this:
using (var principalContext = new PrincipalContext(ContextType.Domain, null, adPath))
{
// Find the principal object for which you wish to enumerate group
// membership.
using (var userPrincipal = UserPrincipal.FindByIdentity(principalContext, identity))
{
if (userPrincipal != null)
{
Name = userPrincipal.DisplayName;
DistinguishedName = userPrincipal.DistinguishedName;
EmailAddress = userPrincipal.EmailAddress;
Sid = userPrincipal.Sid.Value;
}
}
}
My adPath can be one of 2 values. One of the values is a domain that was recently joined, and can be accessed using different tools. I believe this is a problem with how this .NET library makes the LDAP calls.
Here is a partial Answer, as it's too long for a comment.
According to this Microsoft documentation, as you even know, Referrals are a hint that the client can chase. But concerning RODC they add "For example, in the case of an LDAP application, if chase referrals is enabled on the LDAP connection between the client and the RODC, the application never knows that the client received a referral from the RODC. The client is automatically redirected to the writable domain controller that is specified in the referral. ".
So I look how to enable LDAP chasing on a connexion in Microsoft site and I found this which means ADSI use. I'am very interested in the answer.
Do you try to query the global catalog like this :
/* Retreiving a principal context
*/
PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, "YourGCServer:3268", "dc=dom,dc=fr", "User", "Password");
It's supposed to contains all the forest domain's datas.
I hope it helps.
Have you tried code of the form(put the domain in as the second argument):
var principalContext = new PrincipalContext(ContextType.Domain, "office.local", "OU=Users, DC=office, DC=local" ))
Also make sure that the adPath is from most specific to least specific.

Getting members of an AD domain group using Sharepoint API

In my Sharepoint code I display a list of all defined users via:
foreach (SPUser user in SPContext.Current.Web.AllUsers)
{
...
}
The great part is, I can add a domain security group to a Sharepoint group (like Visitors) thus adding many users at once (simpler administration). But my code doesn't see those users at least not until they log-in for the first time (if they have sufficient rights). In this case I can only see the domain security group SPUser object instance with its IsDomainGroup set to true.
Is it possible to get domain group members by means of Sharepoint without resorting to Active Directory querying (which is something I would rather avoid because you probably need sufficient rights to do such operations = more administration: Sharepoint rights + AD rights).
You can use the method SPUtility.GetPrincipalsInGroup (MSDN).
All parameters are self-explaining except string input, which is the NT account name of the security group:
bool reachedMaxCount;
SPWeb web = SPContext.Current.Web;
int limit = 100;
string group = "Domain\\SecurityGroup";
SPPrincipalInfo[] users = SPUtility.GetPrincipalsInGroup(web, group, limit, out reachedMaxCount);
Please note that this method does not resolve nested security groups. Further the executing user is required to have browse user info permission (SPBasePermissions.BrowseUserInfo) on the current web.
Update:
private void ResolveGroup(SPWeb w, string name, List<string> users)
{
foreach (SPPrincipalInfo i in SPUtility.GetPrincipalsInGroup(w, name, 100, out b))
{
if (i.PrincipalType == SPPrincipalType.SecurityGroup)
{
ResolveGroup(w, i.LoginName, users);
}
else
{
users.Add(i.LoginName);
}
}
}
List<string> users = new List<string>();
foreach (SPUser user in SPContext.Current.Web.AllUsers)
{
if (user.IsDomainGroup)
{
ResolveGroup(SPContext.Current.Web, user.LoginName, users);
}
else
{
users.Add(user.LoginName);
}
}
Edit:
[...] resorting to Active Directory querying (which is something I would rather avoid because you probably need sufficient rights to do such operations [...]
That's true, of course, but SharePoint has to lookup the AD as well. That's why a application pool service account is required to have read access to the AD.
In other words, you should be safe executing queries against the AD if you run your code reverted to the process account.
I would suggest you just query Active Directory directly. You are spending a lot of effort to try to get SharePoint to make this call to AD for you. Every account that has Domain User access should be able to query the AD groups you have nested in SharePoint. I would just go to the source.
This way you don't have to worry about Browse User Permissions or anything else. In my opinion trying to proxy this through SharePoint is just making your life more difficult.

Categories