23

I used the resource-based authorization pattern in .NET Core 2.1 as described here. The only problem that I have is I have no idea how to test my AuthorizationHandler cleanly.

Anyone here done something like that already?

AuthorizationHandler sample (from the above link):

public class DocumentAuthorizationHandler : 
    AuthorizationHandler<SameAuthorRequirement, Document>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   SameAuthorRequirement requirement,
                                                   Document resource)
    {
        if (context.User.Identity?.Name == resource.Author)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

public class SameAuthorRequirement : IAuthorizationRequirement { }
Niroshan K
  • 88
  • 10
Patrice Cote
  • 3,572
  • 12
  • 43
  • 72

2 Answers2

40

All the required dependencies are available for an isolated unit test.

the desired method under test HandleRequirementAsync is accessible via the Task HandleAsync(AuthorizationHandlerContext context)

/// <summary>
/// Makes a decision if authorization is allowed.
/// </summary>
/// <param name="context">The authorization context.</param>
public virtual async Task HandleAsync(AuthorizationHandlerContext context)
{
    if (context.Resource is TResource)
    {
        foreach (var req in context.Requirements.OfType<TRequirement>())
        {
            await HandleRequirementAsync(context, req, (TResource)context.Resource);
        }
    }
}

And that member is only dependent on AuthorizationHandlerContext which has a constructor as follows

public AuthorizationHandlerContext(
    IEnumerable<IAuthorizationRequirement> requirements,
    ClaimsPrincipal user,
    object resource) {

    //... omitted for brevity
}

Source

Simple isolated unit test that verifies the expected behavior of DocumentAuthorizationHandler.

public async Task DocumentAuthorizationHandler_Should_Succeed() {
    //Arrange    
    var requirements = new [] { new SameAuthorRequirement()};
    var author = "author";
    var user = new ClaimsPrincipal(
                new ClaimsIdentity(
                    new Claim[] {
                        new Claim(ClaimsIdentity.DefaultNameClaimType, author),
                    },
                    "Basic")
                );
    var resource = new Document {
        Author = author
    };
    var context = new AuthorizationHandlerContext(requirements, user, resource);
    var subject = new DocumentAuthorizationHandler();

    //Act
    await subject.HandleAsync(context);

    //Assert
    context.HasSucceeded.Should().BeTrue(); //FluentAssertions
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
0

I do not know the cleanliness of this but I combined https://stackoverflow.com/a/49945492/11305428 and https://stackoverflow.com/a/53885374 to test resource handlers with own DI.

Rough code to pass my fake services and InMemoryDatabase to DI. Might be helpful for someone as it's used in same structure as in the sample code (AuthorizeAsync method).

[Theory]
[InlineData("8d8312c6-36e4-4702-b88a-ff136b80fe0e", false)]
[InlineData("17d42380-ca72-438c-a553-48f0cd80054b", false)]
[InlineData("e036c64d-4e95-4eef-98a6-f7495db3088b", true)]
public async void Folder_Create(Guid folderId, bool expectedAuthorizationResult)
{
    // Arrange
    var folderService = new FolderService(
        this.dbContext,
        this.reportingContext);

    var folder = folderService.GetFolder(folderId);

    var authorizationService = BuildAuthorizationService(services =>
    {
        services.AddTransient<IAuthorizationHandler>(x =>
            ActivatorUtilities.CreateInstance<FolderAuthorizationCrudHandler>(x,
                new object[] { folderService, this.reportingContext })); // constructor
    });

    //Act
    var authorizationResult = await authorizationService.AuthorizeAsync(this.User, folder, CrudOperations.Create);

    //Assert
    Assert.Equal(expectedAuthorizationResult, authorizationResult.Succeeded);
}
private static IAuthorizationService BuildAuthorizationService(
    Action<IServiceCollection> setupServices = null)
{
    var services = new ServiceCollection();
    services.AddAuthorization();
    services.AddOptions();
    services.AddLogging();
    setupServices?.Invoke(services);
    return services.BuildServiceProvider().GetRequiredService<IAuthorizationService>();
}
sw45neutr
  • 71
  • 1
  • 9