ASP.NET MVC allows users the ability to assign permissions to functionality (i.e. Actions) at Design Time like so.
[Authorize(Roles = "Administrator,ContentEditor")]
public ActionResult Foo()
{
return View();
}
To actually check the permission, one might use the following statement in a (Razor) view:
#if (User.IsInRole("ContentEditor"))
{
<div>This will be visible only to users in the ContentEditor role.</div>
}
The problem with this approach is that all permissions must be set up and assigned as attributes at design time. (Attributes are compiled in with the DLL so I am presently aware of no mechanism to apply attributes (to allow additional permissions) such as [Authorize(Roles = "Administrator,ContentEditor")] at runtime.
In our use case, the client needs to be able to change what users have what permissions after deployment.
For example, the client may wish to allow a user in the ContentEditor role to edit some content of a particular type. Perhaps a user was not allowed to edit lookup table values, but now the client wants to allow this without granting the user all the permissions in the next higher role. Instead, the client simply wants to modify the permissions available to the user's current role.
What options are strategies are available to allow permissions on MVC Controllers/Views/Actions to be defined outside of attributes (as in a database) and evaluated and applied at runtime?
If possible, we would very much like to stick as closely as we can to the ASP.NET Membership and Role Provider functionality so that we can continue to leverage the other benefits it provides.
Thank you in advance for any ideas or insights.
What options are strategies are available to allow permissions on MVC
Controllers/Views/Actions to be defined outside of attributes (as in a
database) and evaluated and applied at runtime?
A custom Authorize attribute is one possibility to achieve this:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
Roles = ... go ahead and fetch those roles dynamically from wherever they are stored
return base.AuthorizeCore(httpContext);
}
}
and then:
[MyAuthorize]
public ActionResult Foo()
{
return View();
}
As I'm lazy I couldn't be bothered rolling my own attribute and used FluentSecurity for this. In addition to the ability to apply rules at run time it allows a custom way to check role membership. In my case I have a configuration file setting for each role, and then I implement something like the following;
// Map application roles to configuration settings
private static readonly Dictionary<ApplicationRole, string>
RoleToConfigurationMapper = new Dictionary<ApplicationRole, string>
{
{ ApplicationRole.ExceptionLogViewer, "ExceptionLogViewerGroups" }
};
the application roles are then applied like so
SecurityConfigurator.Configure(
configuration =>
{
configuration.GetAuthenticationStatusFrom(() =>
HttpContext.Current.User.Identity.IsAuthenticated);
configuration.GetRolesFrom(() =>
GetApplicationRolesForPrincipal(HttpContext.Current.User));
configuration.ForAllControllers().DenyAnonymousAccess();
configuration.For<Areas.Administration.Controllers.LogViewerController>()
.RequireRole(ApplicationRole.ExceptionLogViewer);
});
filters.Add(new HandleSecurityAttribute());
and then the check is performed by
public static object[] GetApplicationRolesForPrincipal(IPrincipal principal)
{
if (principal == null)
{
return new object[0];
}
List<object> roles = new List<object>();
foreach (KeyValuePair<ApplicationRole, string> configurationMap in
RoleToConfigurationMapper)
{
string mappedRoles = (string)Properties.Settings.Default[configurationMap.Value];
if (string.IsNullOrEmpty(mappedRoles))
{
continue;
}
string[] individualRoles = mappedRoles.Split(',');
foreach (string indvidualRole in individualRoles)
{
if (!roles.Contains(configurationMap.Key) && principal.IsInRole(indvidualRole))
{
roles.Add(configurationMap.Key);
if (!roles.Contains(ApplicationRole.AnyAdministrationFunction))
{
roles.Add(ApplicationRole.AnyAdministrationFunction);
}
}
}
}
return roles.ToArray();
}
You could of course pull roles from a database. The nice thing about this is that I can apply different rules during development, plus someone has already done the hard work for me!
You could also consider doing task/activity based security and dynamically assign permission to perform those tasks to different groups
http://lostechies.com/derickbailey/2011/05/24/dont-do-role-based-authorization-checks-do-activity-based-checks/
You would need to mangle the provider a little bit to work with this but it is possible to stay inline with the .net authorisation
http://www.lhotka.net/weblog/PermissionbasedAuthorizationVsRolebasedAuthorization.aspx
If you need to do Method or Controller based authorization (deny access to the whole method or controller) then you can override OnAuthorization in the controller base and do your ouwn authorization. You can then build a table to lookup what permissions are assigned to that controller/method and go from there.
You can also do a custom global filter, which is very similar.
Another option, using your second approach, is to say something like this:
#if (User.IsInRole(Model.MethodRoles))
{
<div>This will be visible only to users in the ContentEditor role.</div>
}
And then in your controller populate MethodRoles with the roles assigned to that method.
Related
I am implementing authorization/authentication in a MVC project. They problem I am facing is that there are many conditions for a login user like roles, positions, permissions and some others. I thought the best way is to override AthorizeAttribute and set all logic in there by passing the data as string arrays in constructor and validate them but I do have more than 100 permissions and more than 40 positions which makes this attribute almost useless because I can not write in every action for example 60 position etc.
I thought I could group them somehow but I can see it is not a good point because all actions are dynamic and I would make tons of groups for user permissions and user positions.
I red in some posts for creating a base controller to make all validations there but I am using ASP.NET Identity and I don`t like going in 'old' fashion way...
If anybody had any situation like that before I would be grateful if you share your solution!
EDIT - Adding code example
public class XAuthorizeAttribute : AuthorizeAttribute
{
public XAuthorizeAttribute() { }
public XAuthorizeAttribute(params string[] roles)
{
Roles = string.Join(",", roles.Select(x => x));
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
bool isAuthenticAttribute =
(filterContext.ActionDescriptor.IsDefined(typeof(XAuthorizeAttribute), true) ||
filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(XAuthorizeAttribute), true)) &&
filterContext.HttpContext.User.Identity.IsAuthenticated;
if (!isAuthenticAttribute) return;
UrlHelper urlHelper = new UrlHelper(filterContext.RequestContext);
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
IdentityManager.SignInManager.AuthenticationManager.SignOut(
DefaultAuthenticationTypes.ApplicationCookie, DefaultAuthenticationTypes.ExternalCookie);
}
filterContext.Result =
new RedirectResult(urlHelper.Action("Index", "Home", new {area = ""}));
base.HandleUnauthorizedRequest(filterContext);
}
}
Here is the action...
[XAuthorize("Administrator", "articlelists", "HOD Engineering", "HOD Interior"....)]
public ActionResult Index()
{
return View("Index");
}
As you can see I only put 4 roles, just imagine putting 60 roles and permissions etc.... This is just a demo to get the idea because in my project I do not have only roles as I do have here.
Instead of hard coding roles into attributes it may be better to create a custom generic [XAuthorizeAction] attribute that would start off the process of checking your database for permission configuration.
Step 1.) Configure database to know permissions for action level calls
Step 2.) Create authorization attribute for controller actions that need this level of permission control
Step 3.) Inside attribute determine the caller and the calling controller and action
Step 4.) Query the database for this controller and action to determine the actions permissions.
Step 5.) Query the database for the users permissions and see if they match
In my controllers, I have code like [Authorize(Roles = "Administrators")] annotated above some actions, and I want to know how AuthorizeAttribute uses the Roles parameter (the implementation of the checking mechanism). My goal is to create an extension of this class, called PrivilegeAttribute for example, so that I can annotate actions like [Privilege(Privileges = "read")]. In this class, I would check if the Role of the user has at least one of the privileges in this custom filter (read in this example). I have already created the association between roles and privileges in the code and in the database, and what I want help with is checking whether the role is associated to the privilege.
I tried seeing if that information is there in HttpContextBase.User.Identity but I couldn't find it.
Thank you.
If you don't need your own custom attribute and could live with using someone else attribute, than I would suggest to use the package Thinktecture.IdentityModel.Owin.ResourceAuthorization.Mvc as described here
Blog Post by Dominick Baier
and here
Git Hub Sample Code for the Package
so it basically works like this:
you put an attribute over your action like this:
[ResourceAuthorize("View", "Customer")]
The first argument is the name of the Action to check, the second one is the name of the attribute.
Then you derive from ResourceAuthorizationManager in your code and override the CheckAccessAssync Method
public class MyAuthorization : ResourceAuthorizationManager
{
public override Task<bool> CheckAccessAsync(ResourceAuthorizationContext context)
{
var resource = context.Resource.First().Value;
var action = context.Action.First().Value;
// getting the roles that are connected to that resource and action
// from the db. Context could of course be injected into the
// constructor of the class. In my code I assume that the table
// thank links roles, resources and actions is called Roles ToActions
using(var db = MyContext())
var roles = db.RolesToActions // Use your table name here
.Where(r => r.Resource == resource && r.Action == action).ToList();
foreach(var role in roles)
{
if(context.Principal.IsInRole(role.Name)
{
return Ok();
}
}
return Nok();
}
}
}
So I hope this helps. If you prefer to implement your own attribute however, than the source code from the ResourceAuthorization GitHub Repository should be a good starting point
I'm using the Forms Auth and ASP Universal Membership Provider in an MVC 3 Site. We're persisting the cookie for user convenience.
FormsAuthentication.SetAuthCookie(model.UserName, true)
When we disable a user in the Membership provider like this:
memUser.IsApproved = model.IsActive;
provider.UpdateUser(memUser);
if they have the cookie they can still get in to the site. This is similar to what is described in this post:http://stackoverflow.com/questions/5825273/how-do-you-cancel-someones-persistent-cookie-if-their-membership-is-no-longer-v
We use Authorize attributes on our controllers, and I know that that is technically more Authorize than Authentication. But the certainly overloap so I'm trying to figure out what is the best MVC way to do a check that the user is not actually disabled? Custom AuthorizeAttribute that checks the user against the membership database? An obvious setting/method I'm missing with Forms auth to invalidate the ticket?
Update:
Here’s basically what I'm going with – we use a custom permission denied page which we we use to better inform user that they don’t have rights vs. they’re not logged in. And I added the IsApproved check. AuthorizeCore gets called when you put the attribute on a Controller or Action and if it returns false HandleUnauthorizedRequest is called.
public class CustomAuthorization : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated || !Membership.GetUser(filterContext.HttpContext.User.Identity.Name).IsApproved)
{
filterContext.Result = new HttpUnauthorizedResult();
// in the case that the user was authenticated, meaning he has a ticket,
// but is no longer approved we need to sign him out
FormsAuthentication.SignOut();
}
else
{
var permDeniedRouteVals = new System.Web.Routing.RouteValueDictionary() { { "controller", "MyErrorController" }, { "action", "MyPermissionDeniedAction" }, { "area", null } };
filterContext.Result = new RedirectToRouteResult(permDeniedRouteVals);
}
}
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
// since persisting ticket,
// adding this check for the case that the user is not active in the database any more
return base.AuthorizeCore(httpContext) && Membership.GetUser(httpContext.User.Identity.Name).IsApproved;
}
}
Usage:
[CustomAuthorization()]
public class MyController
Well, you're going to have to check the database regardless, the only question is how you want to do that. Yes, you could create a custom authorize attribute, or you could write some code for the OnAuthorize override in ControllerBase, or you could do it in Application_AuthenticateRequest.. lots of ways you could do it, depends on what works best for you.
The best way, of course, would be to not use a persistent ticket if this is an issue for you.
I pretty much always use Roles and a RolesProvider, even if there is just one role named "Login" - in part for this issue. This way, your Authorize attributes might look something like this:
[Authorize(Roles="Login")]
Where Login represents a basic 'Role' that all "active" accounts must have to be able to log in at all; Every protected action is protected by this, at minimum.
That way, simply removing the "Login" role effectively disables the user... because, in my Roles Provider, I am checking the logged-in user's roles against the database or server-local equivalent.
In your case, your "Login" role could simply resolve to a check on the IsApproved field on your user model.
I have a website that has for examples pages A, B, C, D,.... I want that the user can only access page A once he has registered. To access the remaining pages the user needs to complete some joining formalities which include making a payment among others.
Now the simple way would be where I add a check in all my pages currently completed to make sure that the pages B, C, D... requested are displayed only if the user has completed all joining formalities. This will need me to re work all those existing pages all over again. Also, all future pages developed will need to remember to add that check.
To come around this I used a custom authorize attribute class that inherits from AuthorizeAttribute and made a check in the overridden OnAuthorization method there as in the snippet below
else if (!SessionCache.FormalitiesCompleted)
{
//force completion of formalities
if (!( string.Equals(filterContext.ActionDescriptor.ActionName, "Index", StringComparison.InvariantCultureIgnoreCase)
|| string.Equals(filterContext.ActionDescriptor.ActionName, "Options", StringComparison.InvariantCultureIgnoreCase)
|| string.Equals(filterContext.ActionDescriptor.ActionName, "Index_B", StringComparison.InvariantCultureIgnoreCase)
|| string.Equals(filterContext.ActionDescriptor.ActionName, "Index_C", StringComparison.InvariantCultureIgnoreCase)
&& string.Equals(filterContext.ActionDescriptor.ControllerDescriptor.ControllerName, "Formalities", StringComparison.InvariantCultureIgnoreCase)))
{
string _action = filterContext.ActionDescriptor.ActionName;
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Formalities", action = _action, area = "FormalitiesArea" }));
}
}
As you can see there are too many OR conditions in the IF. I suspect this will increase as the complexity of the formality increases.
Is there a better way to enforce the user doesn't visit any other page on the site unless the formalities are completed?
Thanks for your time.
You probably should look at the [Authorize attribute] http://msdn.microsoft.com/en-us/library/system.web.mvc.authorizeattribute.aspx If you decorate an action with that attribute only logged in users can access it and others will be redirected to the login page.
For Paid/Registered users, I would inherit from this and make a PaidUserAuthorize attribute:
public class PaidUserAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if(filterContext.Result is HttpUnauthorizedResult)
{
filterContext.Result = new RedirectResult("/PaymentPage");
}
}
}
Therefore decorating an action with
[PaidUserAuthorize(Roles = "PaidUser", "Super User")]
will redirect unpaid users to the payment page.
After a user pays - add them to the PaidUser role. So when they go to the action again they will be allowed access.
You can create an Actin Filter and apply it to the Actions or Classes that you want to restrict access too. In you actions filter, you'll want to override the OnActionExecuting method and implement your checking logic in there. You can allow or deny access to those views based on whatever you want to do
Check this link out for a sample
What about using the [Authorize] annotation in your Controllers? By creating your own AuthorizeAttributes you should be able to customise the authorisation per action based on whatever MembershipProvider you choose to use or create.
While looking into forms authorizing/authentication, I found that it is possible to do role based authorizing by adding an array of roles to a FormsAuthenticationTicket. That way I can write
User.IsInRole(role from database)
But is there any way to do the same thing with permissions on a role like :
if (User.IsInRole(role from database)) {
if (User.CanRead()) {
//--- Let the user read
}
if (User.CanWrite()) {
//--- Let the user write
}
}
I have read a couple of articles and forum post's where permission is added to the array instead of the roles, resulting in using
User.IsInRole(permission from database)
However that's not the same thing. Hope someone can give some input on this matter, throw a link to an article or better yet, an code sample.
You're better off changing the way you think about a role. Use the term "permission" or "claim" if that helps. Then create all the roles you need and link a given user to all the necessary roles.
One user can belong to multiple roles. This way, the following simple code will work fine and you don't need to build your own unique way of how things work.
if(User.IsInRole(someRole) && User.IsInRole(someOtherRole))
{
// do something
}
You can make some C# extension methods to make this more readable too:
if(User.IsInSomeRoleAndOtherRole())
{
// do something
}
The extension methods can look something like the following. Create a new class with the following code, then include the class namespace in your code, and you can use the extension method as shown above.
using System.Security.Principal;
namespace MyCompany
{
public static class MyExtensions
{
public static bool IsInSomeRoleAndOtherRole(this IPrincipal principal)
{
if (!principal.IsInRole("someRole"))
return false;
if (!principal.IsInRole("someOtherRole"))
return false;
return true; // the user meets the requirements
}
}
}