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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With