Grzegorz G.
Grzegorz G.

Reputation: 1345

Multiple Role Authorization Handler - Get only Method Roles

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

Answers (2)

King King
King King

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

NAS
NAS

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

Related Questions