Getting members of an AD domain group using Sharepoint API - c#

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.

Related

Get AD users and groups from client

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.

Active Directory groups not immediately available after creation

I am creating Active Directory groups in my app. I make security and distribution groups. The groups will get created just fine, but it takes about 10-15 minutes to show up in the Active Directory Users and Computers.
Is there some kind of forced sync I can perform in C# to make this happen sooner? Or maybe some setting I can change in my directory to change this behavior?
Example code
DirectoryEntry ou1= topLevel.Children.Find("OU=ou1");
DirectoryEntry secGroups = ou1.Children.Find("OU=Security Groups");
DirectoryEntry newGroup = secGroups.Children.Add("CN=" + name + "", "group");
newGroup.CommitChanges();
GroupPrincipal createdGroup = GroupPrincipal.FindByIdentity(this._context, name);
createdGroup.SamAccountName = name;
createdGroup.DisplayName = name;
createdGroup.GroupScope = GroupScope.Universal;
createdGroup.Save();
if (members.Any())
{
foreach (var item in members)
{
createdGroup.Members.Add(this._context, IdentityType.SamAccountName, item);
}
createdGroup.Save();
}
Using ASP.NET MVC, C#, System.DirectoryServices.AccountManagement, System.DirectoryServices.ActiveDirectory.
The most likely answer is that it takes time to propagate to all domain controllers on your network. You may be connected to a different DC via ADUC from the one your application updated.
Something that might help in this situation where you have multiple domain controllers replicating is to target a specific DC for each call you make to the AD server.
So instead of "LDAP://mydomain.com" it becomes something like "LDAP://myDC.mydomain.com"

Login to different active directories programaticly in c#

The company that I work for has two active directories (ad1.com, ad2.com) because it has two different stores that had nothing to do with each other and plenty of users but now the managers of both stores need a page in common.
So I need to create a login where the users could access using just their Active Directory username and password.
In the login page there should be a list to choose the active directory and after that the user Paul(paul#ad1.com) and the user Paul(paul#ad2.com) with their respective password should be able to access to the page.
I have the code needed for the logIn page for one AD and works great but I don't know if it's possible to make the page available for two ADs.
Do I need some extra configuration on the server?
I google this but didn't find anything related.
If you have the code required for one AD, you could very well use the same code with slight modifications to authenticate against another AD.
Based on the domain name, you can identify the settings for AD and perform your authentication.
In cases where you use two AD, only identifying the domain name matters E.g.
ad1\username or username#ad1.com. You will not be able to achieve a login without the user specifying the domain name.
Have you tried using LDAP Authentication and just definining it differently based on the dropdown list?
Something like:
string adPath = string.Empty;
string domainName = string.Empty;
switch (ddlOption.ToString())
{
case "ad1":
adPath = "ad1Path";
domainName = "ad1";
break;
case "ad2":
adPath = "ad2Path";
domainName = "ad2";
break;
}
LdapAuthentication adAuth = new LdapAuthentication(adPath);
if (adAuth.IsAuthenticated(domainName, username, password))
{
//redirect Logic
}

LDAP Path And Permissions To Query Local User Directory?

I am working on a web application, ASP.NET, C#. Users are required to log in using an account local to the machine the app is running on, which I'll call "cyclops" for this example. I want the app to be able to query the local directory of users and groups to determine what groups the user is in. The code looks something like this:
DirectoryEntry entry = new DirectoryEntry("WinNT://cyclops/Users", "SomeServiceAccount",
"SvcAcctP#$$word", AuthenticationTypes.Secure);
entry.RefreshCache();
// Etc.
My two problems are:
That's pretty clearly not the correct path to use, but my research
and experimentation hasn't found the right answer. This MSDN
article talks about local paths, but doesn't fill in the blanks.
Do I use "LDAP://cyclops/Users", "WinNT://localhost/Users",
"WinNT://cyclops/cn=Users"?
As you can see, I'm providing the
credentials of a local service account. That account needs
permission to access the local directory, but I have no idea where
to set those permissions. Is it a specific file somewhere? Does
the account need to be a member of a particular group?
My experimentation has produced many errors: "The group name could not be found.", "The provider does not support searching...", "The server is not operational.", "Unknown error (0x80005004)", etc.
Thank you for your time...
-JW
WinNT requires the following format
WinNT://<domain/server>/<object name>,<object class>
To get groups of a given user, use
using (DirectoryEntry user = new DirectoryEntry("WinNT://./UserAccount,user"))
{
foreach(object group in (IEnumerable)user.Invoke("Groups",null))
{
using(DirectoryEntry g = new DirectoryEntry(group))
{
Response.Write(g.Name);
}
}
}
where
UserAccount is a name of required user.
dot stands for current machine (you can replace it with cyclops or use Environment.MachineName)
user credentials ("SomeServiceAccount", "SvcAcctP#$$word") might be required, depends on setup
To get users in a particular group, use
using (DirectoryEntry entry = new DirectoryEntry("WinNT://./Users,group"))
{
foreach (object member in (IEnumerable)entry.Invoke("Members"))
{
using(DirectoryEntry m = new DirectoryEntry(member))
{
Response.Write(m.Name);
}
}
}
where
Users is a name of group

Authentication, Authorization, User and Role Management and general Security in .NET

I need to know how to go about implementing general security for a C# application. What options do I have in this regard? I would prefer to use an existing framework if it meets my needs - I don't want to re-invent the wheel.
My requirements are as follows:
the usual username/password authentication
managing of users - assign permissions to users
managing of roles - assign users to roles, assign permissions to roles
authorization of users based on their username and role
I am looking for a free / open-source framework/library that has been time-tesed and used by the .Net community.
My application takes a client/server approach, with the server running as a windows service, connecting to a SQL Server database. Communication between client and server will be through WCF.
One other thing that is important is that I need to be able to assign specific users or roles permissions to View/Update/Delete a specific entity, whether it be a Customer, or Product etc. For e.g. Jack can view a certain 3 of 10 customers, but only update the details of customers Microsoft, Yahoo and Google, and can only delete Yahoo.
For coarse-grained security, you might find the inbuilt principal code useful; the user object (and their roles) are controlled in .NET by the "principal", but usefully the runtime itself can enforce this.
The implementation of a principal can be implementation-defined, and you can usually inject your own; for example in WCF.
To see the runtime enforcing coarse access (i.e. which functionality can be accessed, but not limited to which specific data):
static class Roles {
public const string Administrator = "ADMIN";
}
static class Program {
static void Main() {
Thread.CurrentPrincipal = new GenericPrincipal(
new GenericIdentity("Fred"), new string[] { Roles.Administrator });
DeleteDatabase(); // fine
Thread.CurrentPrincipal = new GenericPrincipal(
new GenericIdentity("Barney"), new string[] { });
DeleteDatabase(); // boom
}
[PrincipalPermission(SecurityAction.Demand, Role = Roles.Administrator)]
public static void DeleteDatabase()
{
Console.WriteLine(
Thread.CurrentPrincipal.Identity.Name + " has deleted the database...");
}
}
However, this doesn't help with the fine-grained access (i.e. "Fred can access customer A but not customer B").
Additional; Of course, for fine-grained, you can simply check the required roles at runtime, by checking IsInRole on the principal:
static void EnforceRole(string role)
{
if (string.IsNullOrEmpty(role)) { return; } // assume anon OK
IPrincipal principal = Thread.CurrentPrincipal;
if (principal == null || !principal.IsInRole(role))
{
throw new SecurityException("Access denied to role: " + role);
}
}
public static User GetUser(string id)
{
User user = Repository.GetUser(id);
EnforceRole(user.AccessRole);
return user;
}
You can also write your own principal / identity objects that do lazy tests / caching of the roles, rather than having to know them all up-front:
class CustomPrincipal : IPrincipal, IIdentity
{
private string cn;
public CustomPrincipal(string cn)
{
if (string.IsNullOrEmpty(cn)) throw new ArgumentNullException("cn");
this.cn = cn;
}
// perhaps not ideal, but serves as an example
readonly Dictionary<string, bool> roleCache =
new Dictionary<string, bool>();
public override string ToString() { return cn; }
bool IIdentity.IsAuthenticated { get { return true; } }
string IIdentity.AuthenticationType { get { return "iris scan"; } }
string IIdentity.Name { get { return cn; } }
IIdentity IPrincipal.Identity { get { return this; } }
bool IPrincipal.IsInRole(string role)
{
if (string.IsNullOrEmpty(role)) return true; // assume anon OK
lock (roleCache)
{
bool value;
if (!roleCache.TryGetValue(role, out value)) {
value = RoleHasAccess(cn, role);
roleCache.Add(role, value);
}
return value;
}
}
private static bool RoleHasAccess(string cn, string role)
{
//TODO: talk to your own security store
}
}
my answer is probably dependent upon the answer to this question: Is this an Enterprise application which lives within a network with Active Directory?
IF the answer is yes, then these are the steps I would provide:
1) Create Global Groups for your application, in my case, I had a APPUSER group and an APPADMIN group.
2) Have your SQL Server be able to be accessed in MIXED AUTHENTICATION mode, and then assign your APPUSER group(s) as the SQL SERVER LOGIN to your database with the appropriate CRUD rights to your DB(s), and ensure that you access the SQL SERVER with Trusted Connection = True in your connection string.
At this point, your AD store will be responsible for authentication. Since, you're accessing the application via a TRUSTED CONNECTION, it will pass the identity of whatever account is running the application to the SQL Server.
Now, for AUTHORIZATION (i.e. telling your application what the logged in user is allowed to do) it's a simple matter of querying AD for a list of groups which the logged in user is a member of. Then check for the appropriate group names and build your UI based upon membership this way.
The way my applications work are thus:
Launching the application, credentials are based upon the logged-in user, this is the primary aspect of authentication (i.e. they can log in therefore they exist)
I Get all Groups For the Windows Identity in question
I check for the Standard USER Group -- if this group does not exist for the Windows Identity in question, then that's an authentication FAIL
I check for ADMIN User Group -- With this existing in the user's groups, I modify the UI to allow access to administration components
Display the UI
I then have either a PRINCIPLE object with the determined rights/etc on it, or I utilize GLOBAL variables that I can access to determine the appropriate UI while building my forms (i.e. if my user is not a member of the ADMIN group, then I'd hide all the DELETE buttons).
Why do I suggest this?
It's a matter of deployment.
It has been my experience that most Enterprise Applications are deployed by Network Engineers rather than programmers--therefore, having Authentication/Authorization to be the responsibility of AD makes sense, as that is where the Network guys go when you discuss Authentication/Authorization.
Additionally, during the creation of new users for the network, a Network Engineer (or whoever is responsible for creating new network users) is more apt to remember to perform group assignments while they are IN AD than the fact that they have to go into a dozen applications to parse out assignments of authorization.
Doing this helps with the maze of permissions and rights that new hires need to be granted or those leaving the company need to be denied and it maintains authentication and authorization in the central repository where it belongs (i.e. in AD # the Domain Controller level).
Look into ASP.NET's Membership Providers. I don't think the out of box SQLMembershipProvider will work in your case but it's easy enough to roll your own provider.
I would take a look at something like CSLA.net: Expert C# 2008 Business Objects
It should provide everything you require.
WCF have rich security related functionality provides both authorization and authentication.
In details here:
http://msdn.microsoft.com/en-us/library/ms735093.aspx
I think you are looking at a few separate problems here--it is no accident most security systems separate authentication and authorization.
For authentication, the bigger question is logistical. Or, is there a logical place for these users to live, be it locally to the application, in Active Directory, some other LDAP store or even in some other application. Exactly where is pretty immaterial--we just need to be able to solidly identify users and preferably make that task someone else's problem. End of the day you really just need a unique identifier and the comfort that Bob from Accounting is actually Bob from Accounting.
Authorization is the more interesting part of the problem here. I think, if it is truly fine-grained, you really want to manage this wholly within your application, no matter where the users come from. Marc Gravell really hit on a good way to model at least some of this--use some custom implementation of IPrincipal and PrincipalPermission to manage things is a very clean way to get started. Beyond that you can use techniques like this one to make more complex authorization decisions in a rather clean manner.
I would use the term - 'RBAC' (Role based Access Control system) as the Solution to all your requirements.
I would not go in much detail for explaining 'RBAC' here, rather I would briefly describe it as:
It basically contains 3 features.
1) Authentication - It confirms the user's identity. Usually it is done via user accounts and passwords or credentials.
2) Authorization - It defines what user can do and cannot do in an application. Ex. ‘Modifying order’ is allowed but ‘creating new order’ is not allowed.
3) Auditing of user actions on applications. - It keeps track of user's actions on applications, as well as who has granted which access to which users?
you can check RBAC on wiki here.
https://en.wikipedia.org/wiki/Role-based_access_control
Now, regarding answer to your requirements - one of the possible solution is to extend ASP.NET membership as per your needs.
And regarding, some ready to use framework , I would recommend VisualGuard for which I work, you should check this, It does all the things what you need very easily, and what is most important is, It manages all your users, roles, permissions, and applications via Central Administration Console, and for defining permissions, administrators do not require developer's knowledge, i.e he/she can create restrictions on activities via UI.
you can also check this article to have more understanding on permission and role based system.
http://www.visual-guard.com/EN/net-powerbuilder-application-security-authentication-permission-access-control-rbac-articles/dotnet-security-article-ressources/role-based-access-control-source_soforum.html

Categories