Applying non-MVC related attributes to MVC actions - c#

Our application has the notion of a PermissionAttribute. This attribute is defined in a base layer of our application and our commands and queries are decorated with that attribute. Since this attribute is defined in base layer we can't (and don't want to) to let it inherit from FilterAttribute or implement System.Web.Mvc.IActionFilter on it.
Still we would like to apply this attribute to controller actions as follows:
[Permission(Permissions.Administration.ManageUsers)]
public ActionResult Index()
{
return this.View();
}
Based on this attribute the proper security checks should be applied. I've been browsing through the MVC code base to find the proper hooks to customize MVCs behavior to allow adding these security checks based on this custom attribute. I though about creating a custom ControllerActionInvoker that returned a custom ReflectedControllerDescriptor from its GetControllerDescriptor method, which would return FilterAttribute that would be created based on the existence of the PermissionAttribute, but it feels like a lot of work, and I'm not sure this is the correct path to walk.
What would be am efficient and pleasant way to customize the MVC pipeline so that we can handle this non-MVC related attribute?

I would do it this way. First create your own implementation of the AuthorizeAttribtues like this:
public class PermissionAuthoriseAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//Leaving the implementation of this to you, but you check if your
//PermissionAttribute is assigned and call it's methods.
if(...)
return true;
//You may want to check this first, depending on your requirements
return base.AuthorizeCore(httpContext);
}
}
Then apply this across your project by adding this line to the FilterConfig.cs file:
filters.Add(new PermissionAuthoriseAttribute());

I ended up doing the following:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
filters.Add(new PermissionAuthorizationFilter(
() => Global.Container.GetInstance<IUserPermissionChecker>()), 0);
filters.Add(new HandleErrorAttribute());
}
}
public sealed class PermissionAuthorizationFilter : IAuthorizationFilter
{
private readonly Func<IUserPermissionChecker> userPermissionCheckerFactory;
public PermissionAuthorizationFilter(
Func<IUserPermissionChecker> userPermissionCheckerFactory) {
this.userPermissionCheckerFactory = userPermissionCheckerFactory;
}
public void OnAuthorization(AuthorizationContext filterContext) {
var attribute = filterContext.ActionDescriptor
.GetCustomAttributes(typeof(PermissionAttribute), true)
.OfType<PermissionAttribute>()
.SingleOrDefault();
if (attribute != null) {
VerifyPermission(filterContext, attribute.PermissionId);
}
}
private static void VerifyPermission(AuthorizationContext filterContext,
Guid permissionId) {
var permissionChecker = userPermissionCheckerFactory.Invoke();
if (!permissionChecker.HasPermission(permissionId))
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}

Related

Custom Attribute settings in .net Core

I am writing a very simple custom attribute to be used with my methods for ASP.net Core. The attribute is to handle feature flags which indicate an endpoint method is "switched on or off" as follows:
1) If a feature is turned ON, allow the code to pass through to the method and execute it as normal.
2) If the feature is turned OFF, just return from the attribute and dont execute the method within
I was thinking something along the lines of this:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class FeatureFlagAttribute : ActionFilterAttribute
{
private Dictionary<string, bool> myFeaturesList;
private readonly string selectedFeature;
public FeatureFlagAttribute(string featureName)
{
selectedFeature = featureName;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var found = myFeaturesList.TryGetValue(selectedFeature, out var result);
if (!found || !result)
{
// dont continue
context.HttpContext.Response.StatusCode = 403;
}
}
}
I need the myFeaturesList populated for this to work BUT I dont want to pass it into the constructor every time this is being used. Whats the best way to configure this? I was thinking of setting a static property in the attribute but thought this was a bit of a lame approach and that there must be a better way. Thanks in advance!
Alternatively, you could extract the creation of featureNames into an injectable service (registered to DI) and use your attribute as a type filter or with IFilterFactory.
Using type filters, you would create your attribute as:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class FeatureFlagAttribute : ActionFilterAttribute
{
private readonly string _featureName;
private readonly IFeatureService _featureService;
public FeatureFlagAttribute(string featureName, IFeatureService featureService)
{
_featureName = featureName;
_featureService = featureService;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var features = _featureService.GetMyFeatures();
var found = features.TryGetValue(_featureName, out var result);
if (!found || !result)
{
// don't continue
context.HttpContext.Response.StatusCode = 403;
}
}
}
In the constructor parameters, featureName stays the same, and needs to be defined to the attribute, while featureService will get resolved from the DI, so you need to register an implementation for this in your startup's ConfigureServices().
The attribute usage changes a bit with type filters, for example:
[TypeFilter(typeof(FeatureFlagAttribute), Arguments = new object[] { "feature-A" })]
public IActionResult Index()
{
return View();
}
You can read more options of injecting dependencies into filters in the docs:
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection
A different approach, but maybe move that out of the attribute, perhaps using a static event as the API hook? then you can put the dictionary where-ever you want?
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class FeatureFlagAttribute : ActionFilterAttribute
{
public FeatureFlagAttribute(string featureName)
{
selectedFeature = featureName;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (IsActive?.Invoke(selectedFeature) == false)
{
// dont continue
context.HttpContext.Response.StatusCode = 403;
}
}
public static event Func<string, bool> IsActive;
}
(note that you need to be careful with static events not to cause memory leaks)
Alternatively, keep what you have, but make the dictionary static (and thread-protected, etc); then add some kind of API like:
public static void SetFeatureEnabled(string featureName, bool enabled);
that tweaks the static dictionary.

Custom authorization attribute not working in WebAPI

public class CustomAuthorizeAttribute : AuthorizationFilterAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return true;// if my current user is authorised
}
}
Above is my CustomAuthorizeAttribute Class
and
[CustomAuthorize] // both [CustomAuthorize] and [CustomAuthorizeAttribute ] I tried
public class ProfileController : ApiController
{
//My Code..
}
When I'm calling
http://localhost:1142/api/Profile
It is not firing CustomAuthorizeAttribute
More over My FilterConfig class is look like below
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomAuthorizeAttribute());
}
}
Please help if I miss something.
Looks like you are using an MVC filter instead of a Web API filter. It can be detected in the sample because it uses HttpContextBase. Instead use the filter from the System.Web.Http.Filters namespace.
You need to override OnAuthorization or OnAuthorizationAsync on the Web API filter.
You don't need to register a global filter and decorate your controller with it. Registering it will make it run for all controllers.
Web API filter code:
https://github.com/aspnetwebstack/aspnetwebstack/blob/master/src/System.Web.Http/Filters/AuthorizationFilterAttribute.cs
YOur custom attribute should inherit from System.Web.Http.Filters.AuthorizationFilterAttribute
and it should look like this
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
public class CustomAuthorizeAttribute : System.Web.Http.Filters.AuthorizationFilterAttribute
{
public override bool AllowMultiple
{
get { return false; }
}
public override void OnAuthorization(HttpActionContext actionContext)
{
//Perform your logic here
base.OnAuthorization(actionContext);
}
}
Try with this.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
{
return true;
}
}
To add onto the other answers that have you inherit from System.Web.Http.Filters.AuthorizationFilterAttribute, I put this into my OnAuthorization method to make sure the user was logged in:
if (!actionContext.RequestContext.Principal.Identity.IsAuthenticated)
{
// or whatever sort you want to do to end the execution of the request
throw new HttpException(403, "Forbidden");
}

Should I be using an IAuthorizationFilter if I wish to create an ApiKey restricted resource with ASP.NET MVC4?

I have a few simple routes which I wish to restrict via a simple querystring param. If the key is incorrect or not provided, then I wish to throw a NotAuthorizedException.
Please don't suggest I use WebApi or the equiv - I can't just yet in this scenario.
So i'm not sure if I should be implementing an IAuthorizationFilter or implementing an IActionFilter or even something else.
My code logic?
Check querystring for key.
Check my RavenDb (repository) for a user with that key/value.
If they fail any of those checks, then throw the NotAuthorizedException.
I'm assuming I would then decorate a my action method with this filter. I'm also assuming i would need to pass in my repository into this action method also?
Any suggestions please?
So i'm not sure if I should be implementing an IAuthorizationFilter or
implementing an IActionFilter or even something else.
You should be implementing an IAuthorizationFilter:
public class MyAuthorizeAttribute: FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var key = filterContext.HttpContext.Request.QueryString["param_name"];
if (!IsValid(key))
{
// Unauthorized!
filterContext.Result = new HttpUnauthorizedResult();
}
}
private bool IsValid(string key)
{
// You know what to do here => go hit your RavenDb
// and perform the necessary checks
throw new NotImplementedException();
}
}
And if you wanted to use dependency injection into your custom action filter you could take a look at the following article in which you could implement a custom filter provider (IFilterProvider). You could have a marked attribute which you may use on controller actions and then have this custom filter provider simply look whether the action is decorated with this marker attribute and apply the custom authorization filter.
For example:
public class MyAuthorizeAttribute: Attribute
{
}
and your authorization filter will only implement the IAuthorizationFilter, it won't be a FilterAttribute:
public class MyAuthorizationFilter: IAuthorizationFilter
{
private readonly ISomeRepository repository;
public class MyAuthorizationFilter(ISomeRepository repository)
{
this.repository = repository;
}
public void OnAuthorization(AuthorizationContext filterContext)
{
var key = filterContext.HttpContext.Request.QueryString["param_name"];
if (!IsValid(key))
{
// Unauthorized!
filterContext.Result = new HttpUnauthorizedResult();
}
}
private bool IsValid(string key)
{
// You know what to do here => go hit your RavenDb
// and perform the necessary checks
throw new NotImplementedException();
}
}
and then you will have the custom filter provider:
public class MyFilterProvider : IFilterProvider
{
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
if (actionDescriptor.GetCustomAttributes(typeof(MyAuthorizeAttribute), true).Any())
{
var filter = DependencyResolver.Current.GetService<MyAuthorizationFilter>();
yield return new Filter(filter, FilterScope.Global);
}
yield break;
}
}
that will be registered in your Application_Start:
FilterProviders.Providers.Add(new MyFilterProvider());

Override a Global Filter in MVC for One Method

In my filterConfig, I have registered a global attribute filter, which requires authorization on each of my methods.
However, I have one particular method where I want to apply a different authorization filter attribute. How does one accomplish this, if at all possible?
Note: I do not want to use the [AllowAnonymous] attribute (which works seamlessly and completely ignores my filter), since I want the request to be authorized, just through a different set of authorization logic on the method.
You can alter your filter to allow multiple by setting AllowMultiple = true in the AttributeUsage attribute on your attribute class, and add a check so that if the filter is present multiple times, the globally-applied one doesn't execute. The ActionExecutingContext that gets passed into OnActionExecuting() lets you get the filters applied via filterContext.ActionDescriptor.GetCustomAttributes(), so you can use that here.
Then, alter the constructor so that you can pass in a parameter (probably an enum) that it can use to decide which authorisation method to use - the normal one, or this other one. Give that parameter a default value that makes it select the normal auth method. Then, on that one method that needs a different auth method, you can apply the filter with the other value of the parameter. So it might look like this:
public class CustomAuthAttribute : AuthorizeAttribute
{
public CustomAuthAttribute(AuthMethod method = AuthMethod.StandardAuth)
{
//stuff
}
}
[CustomAuth(AuthMethod.WeirdAuth)]
public ActionResult MethodThatNeedsDifferentAuth()
{
//stuff
}
you can write your own version of the authorize attribute and pass specific parameter to depending on what action would you like your attribute to do for example
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public string currentAction { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (currentAction != "notallowed")
{
HandleUnauthorizedRequest(filterContext);
}
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext context)
{
context.Result = new RedirectResult("/home/login");
}
and then apply it to your class or action
[CustomAuthorize(currentAction = "notallowed")]
public class HomeController : Controller

Make a Role that always is authorized even when not listed

In MVC3 is there a way to make a role (SuperAdmin) that is ALWAYS authorized even if not explicitly listed in the Roles list?
For example with this markup...
[Authorize(Roles="Accounting")]
Even though I'm not in the Accounting role, as a SuperAdmin is there a way to be Authorized for this Action?
I'd highly recommend reading Securing your ASP.NET MVC 3 Application.
First, create your AnonymousAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
AllowMultiple = false,
Inherited = true)]
public sealed class AllowAnonymousAttribute : Attribute
{
}
Second, create your GlobalAuthorize attribute:
public sealed class GlobalAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
bool bypassAuthorization =
filterContext.ActionDescriptor
.IsDefined(typeof(AllowAnonymousAttribute),
true)
|| filterContext.ActionDescriptor
.ControllerDescriptor
.IsDefined(typeof(AllowAnonymousAttribute),
true)
|| (filterContext.RequestContext
.HttpContext
.User != null
&& filterContext.RequestContext
.HttpContext
.User
.IsInRole("SuperAdmin"));
if (!bypassAuthorization)
{
base.OnAuthorization(filterContext);
}
}
}
Third, register GlobalAuthorize in your Global Filters (global.asax):
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new GlobalAuthorize());
}
Now all controllers require the user to be logged in to access. Controllers OR controller Methods can be allowed Anonymous access with the AllowAnonymous attribute. Additionally, all methods are allowed by users in the SuperAdmin role.
You can create your customized AuthorizeAttribute where in the AuthorizeCore method you can implement the extra logic.
A simple example without proper error handling:
public class AuthorizeSuperAdminAttribute : AuthorizeAttribute
{
protected virtual bool AuthorizeCore(HttpContextBase httpContext)
{
IPrincipal user = httpContext.User;
if (user.Identity.IsAuthenticated && user.IsInRole("SuperAdmin"))
return true;
return base.AuthorizeCore(httpContext);
}
}
Then you can use it normally on your actions:
[AuthorizeSuperAdmin(Roles="Accounting")]
public ActionResult MyAction()
{
}

Categories