Several authorization attributes that know about each other

I have a very simple scenario. I want to decorate my controllers / actions with a special authorization attribute. Authorization should be granted if any of the attributes is valid. For instance,

[MyAuth(1)] [MyAuth(2)] public class MyController : Controller { ... } 

I cannot combine the parameters into a single authorization attribute. The above example is a simplified example.

If any attribute allows the user, I want the user to be authorized. I suggested that an ActionFilterAttribute or AuthorizeAttribute will have the means to see which other filters have been executed and are awaiting execution, but there is no such luck.

How can i do this? Since the attributes do not seem to have any awareness, perhaps an HttpModule ? Custom ControllerActionInvoker ?

+6
source share
3 answers

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); /* * If any of the filters are RequiresAssignedAccess, default this to false. One of them must authorize the user. */ bool hasAccess = !filters.Any(f => f is RequiresAssignedAccess); foreach (IAuthorizationFilter current in filters) { /* * This sets authorizationContext.Result, usually to an instance of HttpUnauthorizedResult */ 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(); } } 
+6
source

As far as I know, you cannot bind the [Authorize] attributes the way you want, because they all must pass (AND), not (OR). However, combining the elements into one does not make it necessary to perform some manipulations with magic strings, regardless of the number of parameters that you need to pass. You can define your own set of options available for the Authorize attribute.

 public class SuperCoolAuthorize : AuthorizationAttribute { public string Parameter1{get;set;} public string Parameter2{get;set;} public int Parameter3{get;set;} public string Parameter4{get;set;} public override void OnAuthorization(AuthorizationContext filterContext) { // your custom behaviour } } 

And on your controller / action method

 [Authorize(Parameter1 = "Foo", Parameter2 = "Bar", Parameter3 = 47, Parameter4 = string.Empty) public ActionResult MyControllerAction(){ ... } 

A great article on some other considerations in the user authorization attributes that I came across while helping to formulate this answer.

0
source
 public class AuthUserAttribute : AuthorizeAttribute { public string[] SecurityGroups; public string Groups { get; set; } protected override bool AuthorizeCore(HttpContextBase httpContext) { bool valid = false; var user = UserInformation.Current; if (user.SecurityGroups.Select(x => x).Intersect(this.SecurityGroups).Any()) { valid = true; } if (user.SecurityGroups.Select(x => x).Intersect(new string[] { "IT Administrators" }).Any()) { valid = true; } return valid; } public override void OnAuthorization(AuthorizationContext filterContext) { if (!this.AuthorizeCore(filterContext.HttpContext)) { if (UserInformation.Current.SecurityGroups.Count == 0) { filterContext.Result = new RedirectResult(string.Format("/oa?ReturnUrl={0}", filterContext.HttpContext.Request.RawUrl)); } else { filterContext.Result = new RedirectResult(string.Format("/oa/user/permissions?ReturnUrl={0}", filterContext.HttpContext.Request.RawUrl)); } } else { base.OnAuthorization(filterContext); } } 

}

then I decorate

 [AuthUser(SecurityGroups = new string[] { "Data1", "Data2" })] public ActionResult ForYourEyesOnly() { } 

We will see if someone catches a link to bonds. Lol

0
source

Source: https://habr.com/ru/post/957382/


All Articles