I managed to get this to work last night. My solution is below. The attribute is pretty standard and I cut off the actual parts of the authorization. Interesting stuff happens at HasAssignedAcccessActionInvoker .
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public class RequiresAssignedAccess : AuthorizeAttribute { public int AccessType { get; private set; } public int IdType { get; private set; } public int IdValue { get; private set; } public int Level { get; private set; } public RequiresAssignedAccess(int accessType, int idType, int idValue, int level) { ... } protected override bool AuthorizeCore(HttpContextBase httpContext) { if (!base.AuthorizeCore(httpContext)) return false; bool retval = ... return retval; } }
HasAssignedAcccessActionInvoker inherits from the standard action invoker, but I redefined the InvokeAuthorizationFilters method to add the authorization logic that we need. The standard invoker simply rotates through the authorization filters, and if any of them returns a result, it breaks the loop.
public class HasAssignedAcccessActionInvoker : ControllerActionInvoker { protected override AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor) { AuthorizationContext authCtx = new AuthorizationContext(controllerContext, actionDescriptor); bool hasAccess = !filters.Any(f => f is RequiresAssignedAccess); foreach (IAuthorizationFilter current in filters) { current.OnAuthorization(authCtx); if (current is RequiresAssignedAccess) { if (authCtx.Result == null) { hasAccess = true; } else if (authCtx.Result is HttpUnauthorizedResult) { authCtx.Result = null; } continue; } if (authCtx.Result != null) break; } if (!hasAccess && authCtx.Result == null) authCtx.Result = new HttpUnauthorizedResult(); return authCtx; } }
I had to take a look at the insides of MVC using ILSpy to figure this out. For reference, this is an overridden version of this method:
protected virtual AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor) { AuthorizationContext authorizationContext = new AuthorizationContext(controllerContext, actionDescriptor); foreach (IAuthorizationFilter current in filters) { current.OnAuthorization(authorizationContext); if (authorizationContext.Result != null) { break; } } return authorizationContext; }
Finally, in order to connect this and do everything possible, our controllers inherit from BaseController , which now returns a new invoker.
public class BaseController : Controller { protected override IActionInvoker CreateActionInvoker() { return new HasAssignedAcccessActionInvoker(); } }