spottedmahn
spottedmahn

Reputation: 16011

How to Combine/Compose Authorization Handlers in ASP.Net Core?

How do I re-use AuthorizationHandlers to compose a composite requirement of the two handlers?

I 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

Answers (4)

Milad jalali
Milad jalali

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

galenus
galenus

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

Hao Kung
Hao Kung

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

M&#233;toule
M&#233;toule

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

Related Questions