3

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?

Steven
  • 166,672
  • 24
  • 332
  • 435
  • Could you inherit from `AuthoriseAttribute`, put logic in there to check for your `PermissionAttribtue` and add it across your app globally? – DavidG Sep 17 '14 at 10:12
  • Just create global filter within mvc project and check for Permission attribute on executed action. :) – dariol Sep 17 '14 at 10:15
  • @DavidG but in that case I need to apply two attributes to an action or don't I? That is ugly and error prone. – Steven Sep 17 '14 at 10:15
  • @Steven Indirectly yes, but you only need 1 line in the FilterConfig.cs file to apply it across all controller actions in one go. – DavidG Sep 17 '14 at 10:17

2 Answers2

2

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());
DavidG
  • 113,891
  • 12
  • 217
  • 223
  • 1
    Yes that's a great solution. I ended implementing `IAuthorizationFilter` directly but your answer is close enough. – Steven Sep 17 '14 at 10:37
2

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();
        }
    }
}
Steven
  • 166,672
  • 24
  • 332
  • 435
  • 2
    Nice. Annoying there's no easy way to get round the service location (but like your blog article says, don't do DI in attributes!) – DavidG Sep 17 '14 at 10:51
  • @Steven: You could inject a factory for the IUserPermissionChecker in the constuctor of the PermissionAuthorizatinonFilter instead of resolving through the DependencyResolver. – Ric .Net Sep 26 '14 at 10:23
  • @Ric.Net: Yes, that's much cleaner. Updated my answer. – Steven Sep 26 '14 at 10:28