Reputation: 1345
I am developing an .NET Core web app and I can't get into work custom Authorize attributes handler.
My goal is to make Custom Authorize Handler working with settings file (business requirement).
So, what is the problem? I'll provide as little code as possible to explain.
Let's start with Controller
[Authorize(Roles = Roles.Full)]
[Authorize(Roles = Roles.Read)]
public class ConfigurationController : BaseController
{
...
[Authorize(Roles = Roles.Full)]
public async Task<ActionResult> Edit(ServiceConfigurationDto item)
{ .. }
}
The goal is that user with only full access can save data ...
(problem) Now in my handler I receive 3 roles when user click save, instead of 1.
This is my handler
public class RoleRequirementHandler : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
var succeed = false;
var pendingRequirements = context.PendingRequirements.ToList();
var requiredRoles = new List<RoleRequirement>();
foreach (var x in pendingRequirements)
{
requiredRoles.AddRange(((RolesAuthorizationRequirement)x).AllowedRoles.Select(x => new RoleRequirement(x)).ToList());
}
foreach (var x in requiredRoles)
{
if (x is RoleRequirement)
{
var prefix = "APPT"; // future from setting file
if (context.User.IsInRole(prefix + ((RoleRequirement)x).Role))
{
succeed = true;
}
}
};
if (succeed)
{
pendingRequirements.ForEach(x => context.Succeed(x));
}
return Task.CompletedTask;
}
}
It works fine with only one role but in case with previous Edit method, Handler gets 3 roles and it's not possible to process throught that.
...................
Adding policy looks like:
options.AddPolicy(Roles.Full,
policy => policy.Requirements.Add(new RoleRequirement(Roles.Full)));
for every role,
also I am using Cookie Auth Scheme
I would be thankful for any suggestions :)
Upvotes: 0
Views: 1048
Reputation: 63347
Actually you lack a logic to prioritize the requirements. E.g: a user has role of Full
can just skip the other requirements whereas a user has role of Read
can only skip the other requirements having less prioritized (which of course excluding the most prioritized role of Full
).
I know your code is messy, that's possibly due to your implementation of IAuthorizationHandler
which does not inherit from AuthorizationHandler<TRequirement>
. Here I've just adjusted the most relevant code to make it work (not to make the whole handler code cleaner):
var mostPrioritizedRequirement = requiredRoles.Of<RoleRequirement>()
.OrderByDescending(e => e.Role == Roles.Full).FirstOrDefault();
if(mostPrioritizedRequirement != null){
var prefix = "APPT"; // future from setting file
succeed = context.User.IsInRole(prefix + mostPrioritizedRequirement.Role);
}
The above code is pretty equivalent to your code which seems to verify the most prioritized requirement only (in your code, of course the first succeed = true
does not break the loop).
UPDATE: from your question's title, it seems that you want to verify just the closest requirement (applied on the action method). So instead of prioritizing on the Role
, you need to find the applied requirement on the current action method (if any) to determine the value of succeed
. The code above is still usable, but before running it, we can find the applied requirement on the current action method first and verify it instead, like this:
var methodRequirements = Enumerable.Empty<RoleRequirement>();
ActionDescriptor currentActionDescriptor = null;
if(context.Resource is Endpoint ep){
currentActionDescriptor = ep.Metadata.GetMetadata<ActionDescriptor>();
} else if(context.Resource is ActionContext ac){
currentActionDescriptor = ac.ActionDescriptor;
}
if(currentActionDescriptor != null){
methodRequirements = currentActionDescriptor.FilterDescriptors
.Select(e => e.Filter)
.Of<AuthorizeFilter>()
.SelectMany(e => e.AuthorizeData ?? Enumerable.Empty<IAuthorizeData>())
.SelectMany(e => e.Roles.Select(o => new RoleRequirement(o))).ToList();
}
var mostPrioritizedRequirement = (methodRequirements.Any() ?
methodRequirements : requiredRoles.Of<RoleRequirement>())
.OrderByDescending(e => e.Role == Roles.Full).FirstOrDefault();
if(mostPrioritizedRequirement != null){
var prefix = "APPT"; // future from setting file
succeed = context.User.IsInRole(prefix + mostPrioritizedRequirement.Role);
}
Upvotes: 0
Reputation: 331
This will use "and" condition in roles
[Authorize(Roles = Roles.Full)]
[Authorize(Roles = Roles.Read)]
and this will use "OR"
[Authorize(Roles = "Full,Read")]
on Controller you try to specify or condition like this
[Authorize(Roles = "Full,Read")]
Upvotes: 1