Reputation: 16011
How do I re-use AuthorizationHandlers
to compose a composite requirement of the two handlers?
RequirementA
with one Handler IsAllowedAccessToA : AuthorizationHandler<RequirementA>
RequirementB
with one Handler IsAllowedAccessToB : AuthorizationHandler<RequirementB>
RequirementA_OR_B
where if it meets IsAllowedAccessToA
or IsAllowedAccessToB
it succeedsI have resources that are only accessible to RequirementA
and the same for RequirementB
. I also have resources that are available to A or B.
I can't figure out how to do this without duplicating IsAllowedAccessToA
and IsAllowedAccessToB
handlers
This article helps but is not exactly the same use case.
Upvotes: 6
Views: 2355
Reputation: 163
try this:
you're requirement class is like this:
public class PermissionRequirement : IAuthorizationRequirement
{
public PermissionRequirement(string permission)
{
this.Permission = permission;
}
public string Permission { get; }
}
and the handler should be like this:
public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IPermissionProvider _permissionProvider;
private readonly IUserProvider _userProvider;
public PermissionAuthorizationHandler(IPermissionProvider permissionProvider, IUserProvider userProvider)
{
// permissionProvider is a class that has a function called hasClaim, with bool return value that takes user id and claim as input arguments and realize weather the user id has access to the controller or not
this._permissionProvider = permissionProvider;
// userProvider, return the id of current user
this._userProvider = userProvider;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
var hasClaim= await this._permissionProvider.HasClaim(this._userProvider.GetUserId(), requirement.Permission)
.ConfigureAwait(false);
if (hasClaim) context.Succeed(requirement);
else
context.Fail();
}
}
Upvotes: 4
Reputation: 2137
Actually, the article you point to gives all the answers. The only thing for you to do is defining the handlers in such a way that they can be used for different requirements, i.e., make them generic. Later, you will just register them as services using different type parameters for every requirement.
So your handlers will look something like:
public class IsAllowedAccessToA<T>
: AuthorizationHandler<T> where T : IAuthorizationRequirement
{
}
And you will register them to use for the composite requirement like:
services.AddSingleton<IAuthorizationHandler, IsAllowedAccessToA<RequirementA_OR_B>>();
services.AddSingleton<IAuthorizationHandler, IsAllowedAccessToB<RequirementA_OR_B>>();
This should do what you need.
Upvotes: 1
Reputation: 28200
There isn't a super clean way to express a policy as either of two other policies.
But you could write this imperatively as a helper method that authorizes against both policies, you wouldn't be able to do this via Authorize, but you could just call this where needed:
async Task<bool> IsAllowedAccessToAOrB(ClaimsPrincipal user, IAuthorizationService auth, object resource) {
return await auth.AuthorizeAsync(user, resource, "PolicyA") || await auth.AuthorizeAsync(user, resource, "PolicyB")
}
Upvotes: 2
Reputation: 14482
Let's assume your RequirementA_OR_B
is made of both requirements, such as:
public class RequirementA_OR_B : IAuthorizationRequirement
{
public RequirementA RequirementA { get; set; }
public RequirementB RequirementB { get; set; }
}
Then you can create your combined handler as follow:
public class RequirementA_OR_BHandler : AuthorizationHandler<RequirementA_OR_B>
{
private RequirementAHandler _requirementAHandler;
private RequirementBHandler _requirementBHandler;
public RequirementA_OR_BHandler(/* Whatever is needed by either handlers*/)
{
// note: the dependency injection framework might directly inject both handlers, but I didn't check
_requirementAHandler = new RequirementAHandler();
_requirementBHandler = new RequirementBHandler();
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RequirementA_OR_B requirement)
{
// create a dummy context with both requirements
var requirements = new IAuthorizationRequirement[]
{
requirement.RequirementA,
requirement.RequirementB,
};
var dummyContext = new AuthorizationHandlerContext(requirements, context.User, null);
await _requirementAHandler.HandleAsync(dummyContext);
await _requirementBHandler.HandleAsync(dummyContext);
// if either A or B succeeds, the number of pending requirements will decrease
if (dummyContext.PendingRequirements.Count() < 2)
context.Succeed(requirement);
await Task.FromResult(true);
}
}
Upvotes: 3