NetSqlAzMan vs AzMan vs (?????) - c#

I've been trying to "read between the lines" about the original (and/or current) motivation for the NetSqlAzMan project.
Was this written for?
An adapter for Windows Authorization Manager (AzMan). Where the methods in the NetSqlAzMan just passes calls to (Windows Authorization Manager (AzMan)), but perhaps with nicer/cleaner methods?
A replacement for (Windows Authorization Manager (AzMan)). Where (most or all of) the features available in (Windows Authorization Manager (AzMan)) are recreated in NetSqlAzMan, but the code was developed independently.
(Perhaps to provide DotNet 4.0 support???) (Perhaps to remove any COM dependencies)
To provide more features than (Windows Authorization Manager (AzMan)) provided. Aka, a "smarter"/"better" version of (Windows Authorization Manager (AzMan)).
To rewrite but also keep a semi-dead project alive through open-source. (As in, perhaps (Windows Authorization Manager (AzMan))) is a dead or abandoned project by Microsoft).
Other?
................
I like the object model of NetSqlAzMan. But I need to defend any decision to use it to my project manager(s) and other developers.
The object model seems "just right" (think goldilocks and the middle bed) as far as what I desire for security.
I do NOT want to do role based security. I want right(or task or permission) based security.
(See:
http://lostechies.com/derickbailey/2011/05/24/dont-do-role-based-authorization-checks-do-activity-based-checks/
and
http://granadacoder.wordpress.com/2010/12/01/rant-hard-coded-security-roles/
)
And basically the question that came up is: "What is the advantage of using NetSqlAzMan instead of (Windows Authorization Manager (AzMan))?"
And the sub question is "Is Windows Authorization Manager (AzMan) dead?". (And something along the lines of Long Live NetSqlAzMan!).
..................
My in-general requirements are:
Non Active-Directory users. (Down the road Active Directory and/or LDAP support would be nice, but not a requirement).
Passwords not stored as plain text.
Be able to handle RIGHTS for security checks.
Group the rights under any role.
Assign roles to users. (But again, the code will check for the right, not the role when performing an action.)
Allow (on occasion) rights to be assigned to users. With a Deny override. (Aka, a single user who does on stupid thing (like "Delete Employee") can have that right revoked.)
Roles and Rights can be maintained for multiple applications.
So other ideas are welcome. But Windows Identity Foundation seems like a little overkill.
Thanks.

I finally found a "compare" article last night.
http://www.c-sharpcorner.com/uploadfile/a.ferendeles/netsqlazman12122006123316pm/netsqlazman.aspx
I am going to paste the relevant portion here (below). (Just in case that website ceases to exist in the future. Small chance, I know, but I hate "The answer is here" links, and when you hit the link, it is a dead one.)
From what I can tell.
NetSqlAzMan provides a (table) user-defined-function that you can overload to provide a list of users (to be assigned to roles/tasks).
NetSqlAzMan provides not only "Yeah you can" mappings (Grant), but also Deny and Grant-With-Delegate as well.
NetSqlAzMan and Azman allows users(groups) to role mappings. Only NetSqlAzMan allows users to Task mappings.
After looking at a few samples ... the object model of NetSqlAzMan is very clean.
=======================================================
Ms Authorization Manager (AzMan) vs .NET Sql Authorization Manager
(NetSqlAzMan)
As pointed out before, an analogous Microsoft product already exists
and is called Authorization Manager (AzMan); AzMan is present, by
default, in Windows Server 2003 and, through the Admin Pack setup, in
Windows XP.
The important difference between AzMan and NetSqlAzMan is that the
first is Role-based, that is, based on the belonging - Role concept
and the operations container in each role, while the second is
Item-based (or if you prefer Operation-based), that is users or users
group or group of groups that can or cannot belong to Roles or execute
such Task and/or Operations (Items).
Here the most important features and differences between the two
products:
Ms AzMan:
* It's COM.
* It's equipped by a MMC 2.0 (COM) console.
* Its storage can be an XML file or ADAM (Active Directory Application Mode - e un LDAP).
* It's role-based.
* It supports static/dynamic applicative groups, members/not-members.
* Structure based on Roles -> Tasks -> Operations. (Hierarchical Roles and Tasks , none Operations).
* Authorizations can be added only to Roles.
* It doesn't implement the "delegate" concept.
* It doesn't manage authorizations "in the time".
* It doesn't trigger events.
* The only type of authorization is "Allow".
(to "deny" it needs to remove the user/group from his Role).
* It supports Scripting / Biz rules.
* It supports Active Directory users/groups and ADAM users.
NetSqlAzMan:
* It's .NET 2.0.
* It's equipped by a MMC 3.0 (.NET) console.
* Its storage is a Sql Server database(2000/MSDE/2005/Express).
* It's based on Tdo - Typed Data Object technology.
* It's Item-based.
* Structure based on Roles -> Tasks -> Operations. (all hierarchical ones).
* Authorizations can be added to Roles, Task and Operations.
* It supports static/dynamic applicative groups, members/not-members.
* LDAP query testing directly from console.
* It's time-dependant.
* It's delegate-compliant.
* It triggers events (ENS).
* It supports 4 authorization types:
o Allow with delegation (authorized and authorized to delegate).
o Allow (authorized).
o Deny (not authorized).
o Neutral (neutral permission, it depends on higher level Item permission).
* Hierarchical authorizations.
* It supports Scripting / Biz rules (compiled in .NET - C# - VB - and not interpreted)
* It supports Active Directory users/groups and custom users defined in SQL Server Database.
Here's another gotcha.
Azman sample code:
http://channel9.msdn.com/forums/sandbox/252978-AzMan-in-the-Enterprise-Sample-Code
http://channel9.msdn.com/forums/sandbox/252973-Programming-AzMan-Sample-Code
using System;
using System.Security.Principal;
using System.Runtime.InteropServices;
using AZROLESLib;
namespace TreyResearch {
public class AzManHelper : IDisposable {
AzAuthorizationStore store;
IAzApplication app;
string appName;
public AzManHelper(string connectionString, string appName) {
this.appName = appName;
try {
// load and initialize the AzMan runtime
store = new AzAuthorizationStore();
store.Initialize(0, connectionString, null);
// drill down to our application
app = store.OpenApplication(appName, null);
}
catch (COMException x) {
throw new AzManException("Failed to initizlize AzManHelper", x);
}
catch (System.IO.FileNotFoundException x) {
throw new AzManException(string.Format("Failed to load AzMan policy from {0} - make sure your connection string is correct.", connectionString), x);
}
}
public void Dispose() {
if (null == app) return;
Marshal.ReleaseComObject(app);
Marshal.ReleaseComObject(store);
app = null;
store = null;
}
public bool AccessCheck(string audit, Operations op,
WindowsIdentity clientIdentity) {
try {
// first step is to create an AzMan context for the client
// this looks at the security identifiers (SIDs) in the user's
// access token and maps them onto AzMan roles, tasks, and operations
IAzClientContext ctx = app.InitializeClientContextFromToken(
(ulong)clientIdentity.Token.ToInt64(), null);
// next step is to see if this user is authorized for
// the requested operation. Note that AccessCheck allows
// you to check multiple operations at once if you desire
object[] scopes = { "" };
object[] operations = { (int)op };
object[] results = (object[])ctx.AccessCheck(audit, scopes, operations,
null, null, null, null, null);
int result = (int)results[0];
return 0 == result;
}
catch (COMException x) {
throw new AzManException("AccessCheck failed", x);
}
}
public bool AccessCheckWithArg(string audit, Operations op,
WindowsIdentity clientIdentity,
string argName, object argValue) {
try {
// first step is to create an AzMan context for the client
// this looks at the security identifiers (SIDs) in the user's
// access token and maps them onto AzMan roles, tasks, and operations
IAzClientContext ctx = app.InitializeClientContextFromToken(
(ulong)clientIdentity.Token.ToInt64(), null);
// next step is to see if this user is authorized for
// the requested operation. Note that AccessCheck allows
// you to check multiple operations at once if you desire
object[] scopes = { "" };
object[] operations = { (int)op };
object[] argNames = { argName };
object[] argValues = { argValue };
object[] results = (object[])ctx.AccessCheck(audit, scopes, operations,
argNames, argValues,
null, null, null);
int result = (int)results[0];
return 0 == result;
}
catch (COMException x) {
throw new AzManException("AccessCheckWithArg failed", x);
}
}
// use this to update a running app
// after you change the AzMan policy
public void UpdateCache() {
try {
store.UpdateCache(null);
Marshal.ReleaseComObject(app);
app = store.OpenApplication(appName, null);
}
catch (COMException x) {
throw new AzManException("UpdateCache failed", x);
}
}
}
public class AzManException : Exception {
public AzManException(string message, Exception innerException)
: base(message, innerException)
{}
}
}
That is Azman helper code. That is ugly COM/Interopish stuff. :<
Now check the NetSqlAzMan code samples:
http://netsqlazman.codeplex.com/wikipage?title=Samples
/// <summary>
/// Create a Full Storage through .NET code
/// </summary>
private void CreateFullStorage()
{
// USER MUST BE A MEMBER OF SQL DATABASE ROLE: NetSqlAzMan_Administrators
//Sql Storage connection string
string sqlConnectionString = "data source=(local);initial catalog=NetSqlAzManStorage;user id=netsqlazmanuser;password=password";
//Create an instance of SqlAzManStorage class
IAzManStorage storage = new SqlAzManStorage(sqlConnectionString);
//Open Storage Connection
storage.OpenConnection();
//Begin a new Transaction
storage.BeginTransaction(AzManIsolationLevel.ReadUncommitted);
//Create a new Store
IAzManStore newStore = storage.CreateStore("My Store", "Store description");
//Create a new Basic StoreGroup
IAzManStoreGroup newStoreGroup = newStore.CreateStoreGroup(SqlAzManSID.NewSqlAzManSid(), "My Store Group", "Store Group Description", String.Empty, GroupType.Basic);
//Retrieve current user SID
IAzManSid mySid = new SqlAzManSID(WindowsIdentity.GetCurrent().User);
//Add myself as sid of "My Store Group"
IAzManStoreGroupMember storeGroupMember = newStoreGroup.CreateStoreGroupMember(mySid, WhereDefined.Local, true);
//Create a new Application
IAzManApplication newApp = newStore.CreateApplication("New Application", "Application description");
//Create a new Role
IAzManItem newRole = newApp.CreateItem("New Role", "Role description", ItemType.Role);
//Create a new Task
IAzManItem newTask = newApp.CreateItem("New Task", "Task description", ItemType.Task);
//Create a new Operation
IAzManItem newOp = newApp.CreateItem("New Operation", "Operation description", ItemType.Operation);
//Add "New Operation" as a sid of "New Task"
newTask.AddMember(newOp);
//Add "New Task" as a sid of "New Role"
newRole.AddMember(newTask);
//Create an authorization for myself on "New Role"
IAzManAuthorization auth = newRole.CreateAuthorization(mySid, WhereDefined.Local, mySid, WhereDefined.Local, AuthorizationType.AllowWithDelegation, null, null);
//Create a custom attribute
IAzManAttribute<IAzManAuthorization> attr = auth.CreateAttribute("New Key", "New Value");
//Create an authorization for DB User "Andrea" on "New Role"
IAzManAuthorization auth2 = newRole.CreateAuthorization(mySid, WhereDefined.Local, storage.GetDBUser("Andrea").CustomSid, WhereDefined.Local, AuthorizationType.AllowWithDelegation, null, null);
//Commit transaction
storage.CommitTransaction();
//Close connection
storage.CloseConnection();
}
That tells a story in and of itself.

I think the reason for the lack of updates from Microsoft on their blogs and in their SDKs has something to do with them already moving all their tools and designs towards a social network/federation friendly "claims model":
http://msdn.microsoft.com/en-us/magazine/ee335707.aspx
In comparison to any of the AzMan variants, at the low AzMan operation level (what the code demands to decouple it from the rest) we only have a permission type claim. This new style of operation is simply a URN string/action name issued from any trusted claims provider/service validated by signatures you (or later re-configuration) define. They are then just a flat list of roles in the user identity so easy to check with common IsInRole methods.
The justification for this is clear. Modern internet solutions (and perhaps some future corporate intranet applications once the privacy laws are improved) demand multi-domain authentication and authorization, e.g. this StackOverflow.com user account and the connected Facebook account or any OpenID account you may have linked.
So for authorization you can now CODE rules which map between external claims and internal "permission claims" (analogous to AzMan operations). However there is no standard format, hierarchy or administration tool.
Perhaps a hybrid solution of Claims Service (Authentication) + AzMan XML/SQL (Roles to Claims mapping) + Claims Permission demand is the way forwards. All the samples I found so far just have code in the middle. I want to see something with recursive group memberships from Active Directory resolved to Roles to Tasks to claims (operations) like we already have with AzMan.
More investigation is necessary to achieve the "good old" but still essential "role based security" pattern with the newer technology...
If you're looking to start, head towards the Microsoft Windows Identity Foundation (WIF) which first appeared in .NET 3.5.1 but has since been integrated into the .NET 4.5 framework.
http://msdn.microsoft.com/en-us/library/hh377151(v=vs.110).aspx

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.

Authentication against local AD in the Angular application

I've been developing an Angular app with .NET Core backend (services). The task is to enable an integrated authentication, i.e. make it work with the local user seamlessly, so I login to my (connected to a local AD) machine once and the web application lets me in without the necessity to login a second time. We've been working with Identity Server 4 and intended to implement this scenario using it.
There is a little documentation on the official website concerning the Windows Authentication (e.g. against Active directory): http://docs.identityserver.io/en/latest/topics/windows.html but it doesn't explain much. As per my info, to make this scenario work the browser utilizes either Kerberos or NTLM. Neither of them is mentioned in the IS4 docs. I'm lacking the understanding of how the local credentials are getting picked up and how IS4 'knows' the user belongs to AD? How I can make sure only the users from a specific domain have access to my app?
I found some working stuff here https://github.com/damienbod/AspNetCoreWindowsAuth but questions remain the same. Even though I was able to get to the app with my local account I don't understand the flow.
I expect the user utilizing the app in the local network to log-in to the app without entering the login/password (once he's already logged in to the Windows). Is this something achievable?
Identity Server is intended to serve as an Identity Provider, if you need to talk with your AD you should see the Federation Gateway architecture they propose using the IAuthenticationSchemeProvider. Where Identity Server acts as an endpoint and talks with your AD.
This is the link:
http://docs.identityserver.io/en/latest/topics/federation_gateway.html
You have the control to programmatically reach your AD and pass the correct credentials to get the authentication. That step should be done in your Identity Server. After you get authenticated you should get redirected to your application again.
About your last question, the answer is yes, if you have your website hosted on an intranet and you have the access to your AD, you don't need to capture your credentials as user input, you can programmatically reach the AD as I said.
Bellow is the code I use to connect with my active directory
On the ExternalController class, you get when you use IdentityServer, you have this:(I don't remember at the top of my head how much I changed from the original code, but you should get the idea)
/// <summary>
/// initiate roundtrip to external authentication provider
/// </summary>
[HttpGet]
public async Task<IActionResult> Challenge(string provider, string returnUrl)
{
if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/";
// validate returnUrl - either it is a valid OIDC URL or back to a local page
if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false)
{
// user might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
}
if (AccountOptions.WindowsAuthenticationSchemeName == provider)
{
// windows authentication needs special handling
return await ProcessWindowsLoginAsync(returnUrl);
}
else
{
// start challenge and roundtrip the return URL and scheme
var props = new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(Callback)),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", provider },
}
};
return Challenge(props, provider);
}
}
private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl)
{
// see if windows auth has already been requested and succeeded
var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName);
if (result?.Principal is WindowsPrincipal wp)
{
// we will issue the external cookie and then redirect the
// user back to the external callback, in essence, testing windows
// auth the same as any other external authentication mechanism
var props = new AuthenticationProperties()
{
RedirectUri = Url.Action("Callback"),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", AccountOptions.WindowsAuthenticationSchemeName },
}
};
var id = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName);
id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
// add the groups as claims -- be careful if the number of groups is too large
if (AccountOptions.IncludeWindowsGroups)
{
var wi = wp.Identity as WindowsIdentity;
var groups = wi.Groups.Translate(typeof(NTAccount));
var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
id.AddClaims(roles);
}
await HttpContext.SignInAsync(
IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme,
new ClaimsPrincipal(id),
props);
return Redirect(props.RedirectUri);
}
else
{
// trigger windows auth
// since windows auth don't support the redirect uri,
// this URL is re-triggered when we call challenge
return Challenge(AccountOptions.WindowsAuthenticationSchemeName);
}
}
If you want to use Azure AD, I would recommend you to read this article:
https://damienbod.com/2019/05/17/updating-microsoft-account-logins-in-asp-net-core-with-openid-connect-and-azure-active-directory/
Not sure if it's what you want, but I would use the Active Directory Federation Services to configure an OAuth2 endpoint and obtain the user token in the .Net Core Web App.
Isn't NTLM authentication support limited on non Microsoft browsers?
OAuth2 have the advantage of using only standard technologies.
One way to do it is to have 2 instances of the app deployed.
The first one is configured to use Windows Authentication and the other one uses IS4.
ex:
yoursite.internal.com
yoursite.com
Your local DNS should redirect traffic internally from yoursite.com to yoursite.internal.com
yoursite.internal.com will be the one configured to use AD authentication. You should have a flag in your appsettings.json to indicate if this instance is a AD auth or IS4 auth.
The downside of this solution is that you have to deploy 2 instances

BOX: Add Collaboration to a folder using AppUser user token

I am building an integration between my organization back-end systems and BOX.
One of the scenarios is that when certain event is happening inside my organization there is a need to create a folder in BOX and add collaboration objects to that folder (connect groups to the folder).
I have no problem to create the folder but when trying to create the collaboration I am getting the following error:
Box.V2.Exceptions.BoxException: Bearer realm="Service", error="insufficient_scope", error_description="The request requires higher privileges than provided by the access token."
I am using BOX SDK for .Net to interact with BOX.
The application I created in BOX is assigned to use AppUser User Type and I provided all the scopes that BOX allows me (All scopes except "Manage enterprise" which is disabled).
The code that fails is (C#):
var privateKey = File.ReadAllText(Settings.JwtPrivateKeyFile);
var boxConfig = new BoxConfig(Settings.ClientID, Settings.ClientSecret, Settings.EnterpriseID, privateKey, Settings.JwtPrivateKeyPassword, Settings.JwtPublicKeyID);
var jwt = BoxJWTAuth(boxConfig);
var token = jwt.AdminToken();
var client = jwt.AdminClient(token);
var addRequest = new BoxCollaborationRequest(){
Item = new BoxRequestEntity() {
Id = folderId,
Type = BoxType.folder
},
AccessibleBy = new BoxCollaborationUserRequest(){
Type = BoxType.#group,
Id = groupId
},
Role = "viewer"
};
var api = client.CollaborationsManager;
var task = api.AddCollaborationAsync(addRequest);
task.Wait();
When running this code but replacing the Admin Token with Developer Token generated from the Box Applicaiton Edit Page it works.
Any help is appreciated
OK, I had long discussion with BOX Technical team and here is the conclusion: Using AppUser is not the right choice for my scenario because it is limited only to the folders it creates. There is no way to bypass it.
The solution is:
1. Configure the Application to use standard user
2. Create User with administrative rights that will be used by the API to do activities in BOX. I named this user "API User"
3. Follow the oAuth 2 tutorial to generate access token and refresh token that the API .Net application can use instead of generating token for the AppUser. the oAuth 2 tutorial can be found at https://www.box.com/blog/get-box-access-tokens-in-2-quick-steps/
If the app user is a member of the group(s) you want to be able to access the folder then you shouldn't need to set up a collaboration, the users should just have access.

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.

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