Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asp.net Core code based policy needs access to Authorize attribute

In my multitenant application user permissions (read Roles if you are more comfortable with that) are set per tenant so we are adding claims to each user with value TenantName:Permission.

We are using policy based authorization with custom code using the following code

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class PermissionAuthorizeAttribute : AuthorizeAttribute
{
    public Permission[] AcceptedPermissions { get; set; }

    public PermissionAuthorizeAttribute()
    {

    }

    public PermissionAuthorizeAttribute(params Permission[] acceptedPermissions)
    {
        AcceptedPermissions = acceptedPermissions;
        Policy = "RequirePermission";
    }
}

public enum Permission
{
    Login = 1,
    AddUser = 2,
    EditOtherUser = 3,
    EditBaseData = 6,
    EditSettings = 7,
}

With the above code we decorate Controller actions

[PermissionAuthorize(Permission.EditSettings)]
public IActionResult Index()

In startup.cs we have

services.AddAuthorization(options =>
    {
        options.AddPolicy("RequirePermission", policy => policy.Requirements.Add(new PermissionRequirement()));
    });

The AuthorizationHandler would in this case need access to the PermissionAuthorizeAttribute so we can check what permissions were specified on the action. At the moment we can get the attribute with the below code, but I think there must be an easier way, since there is a lot of casting an iterating there.

public class PermissionRequirement : AuthorizationHandler<PermissionRequirement>, IAuthorizationRequirement
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
    {

        var filters = ((FilterContext)context.Resource).Filters;

        PermissionAuthorizeAttribute permissionRequirement = null;
        foreach (var filter in filters)
        {
            var authorizeFilter = filter as AuthorizeFilter;
            if (authorizeFilter == null || authorizeFilter.AuthorizeData == null)
                continue;

            foreach (var item in authorizeFilter.AuthorizeData)
            {
                permissionRequirement = item as PermissionAuthorizeAttribute;
                if (permissionRequirement != null)
                    break;
            }

            if (permissionRequirement != null)
                break;
        }

        //TODO Check that the user has the required claims

        context.Succeed(requirement);
        return Task.CompletedTask;
    }
}

All the examples I have found are like this, where some hardwired policy is specified in startup.cs. services.AddAuthorization(options => { options.AddPolicy("Over21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21))); }

Here you decorate you controller or action with

[Authorize(Policy="Over21")]
public class AlcoholPurchaseRequirementsController : Controller

I think the above example would be better if you could specify the age in the controller/action, like this

[Authorize(Policy="OverAge", Age=21)]
public class AlcoholPurchaseRequirementsController : Controller

Now you need to add a different policy for every minimum age.

Any ideas on how to make this efficient?

like image 259
Mathias Rönnlund Avatar asked Jun 12 '26 18:06

Mathias Rönnlund


1 Answers

Although i would use Resource Based Authorization as i commented, there is a way to achieve your goal:

First create a custom attribute:

public class AgeAuthorizeAttribute : Attribute
{
    public int Age{ get; set; }
    public AgeAuthorizeAttribute(int age)
    {
        Age = age;
    }
}

Then write a filter provider:

public class CustomFilterProvider : IFilterProvider
{
    public int Order
    {
        get
        {
            return 0;
        }
    }

    public void OnProvidersExecuted(FilterProviderContext context)
    {
    }

    public void OnProvidersExecuting(FilterProviderContext context)
    {
        var ctrl = context.ActionContext.ActionDescriptor as ControllerActionDescriptor;
        var ageAttr = ctrl.MethodInfo.GetCustomAttribute<AgeAuthorizeAttribute>();
        if (ageAttr == null)
        {
             ageAttr = ctrl.ControllerTypeInfo.GetCustomAttribute<AgeAuthorizeAttribute>();
        }
        if (ageAttr != null)
        {
            var policy = new AuthorizationPolicyBuilder()
                   .AddRequirements(new MinimumAgeRequirement(ageAttr.Age))
                   .Build();

            var filter = new AuthorizeFilter(policy);
            context.Results.Add(new FilterItem(new FilterDescriptor(filter, FilterScope.Action), filter));
        }
    }
}

Finally register filter provider:

services.TryAddEnumerable(ServiceDescriptor.Singleton<IFilterProvider, CustomFilterProvider>());

And use it

[AgeAuthorize(21)]
public IActionResult SomeAction()

... or
[AgeAuthorize(21)]
public class AlcoholPurchaseRequirementsController : Controller

ps: not tested

like image 196
adem caglin Avatar answered Jun 15 '26 06:06

adem caglin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!