6

I am trying to create a new Authorization requirement, but it must use one of the services that I declare in ConfigureServices, and I have no idea how to pass that service to the new requirement in the same method as declaring the service.

public class NewRequirement: AuthorizationHandler<NewRequirement>, IAuthorizationRequirement
{
    private IRepository _repository;

    public NewRequirement(IRepository repository)
    {
        _repository = repository;
    }

    protected override void Handle(AuthorizationContext context, NewRequirement requirement)
    {
        //code that uses _repository here
    }
}

This works fine, but when I try to add the policy in Startup.cs like this:

    public void ConfigureServices(IServiceCollection services)
    {
        ...

        services.AddSingleton<RepositoryContext>();
        services.AddScoped<IRepository, Repository>();

        services.Configure<AuthorizationOptions>(options =>
        {
            options.AddPolicy("NewRequirement", policy => policy.Requirements.Add(new NewRequirement()));
        });
    }

I get this error

Error CS7036
There is no argument given that corresponds to the required formal parameter 'repository' of 'ExtraProjectRequirements.NewRequirement(IRepository)'
Reception.DNX 4.5.1

I think I need to pass the IRepository to the new policy but I have no idea how to do it in ConfigureServices.

Matthew
  • 256
  • 1
  • 3
  • 13

2 Answers2

12

You are passing the handler to the requirement, which is wrong. IAuthorizationRequirement and AuthorizationHandler<NewRequirement> need to be two distinct classes. Also IAuthorizationRequirement is only a marker interface w/o any mandatory properties or methods, just there to accidentally adding arbitrary classes to the Requirements collection ;)

The IAuthorizationRequirement will contain pure data (reads: No services, no dependencies that need to be injected) required for your requirement, the handler will validate it. See @blowdart example of an Over18Requirement and it's handler as well as the official documentation.

Handlers are allowed to have dependencies injected.

Examples from the documentation for future readers (in case link becomes unavailable).

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public MinimumAgeRequirement(int age)
    {
        MinimumAge = age;
    }

    protected int MinimumAge { get; set; }
}

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override void Handle(AuthorizationContext context, MinimumAgeRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
                                   c.Issuer == "http://contoso.com"))
        {
            return;
        }

        var dateOfBirth = Convert.ToDateTime(context.User.FindFirst(
            c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "http://contoso.com").Value);

        int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }
    }
}
Community
  • 1
  • 1
Tseng
  • 52,202
  • 10
  • 166
  • 183
  • I actually got my code from that example from @blowdart, it doesn't seem like he made 2 distinct classes? I will test this tomorrow and will return to you then, thank you for answering so quickly! – Matthew Jun 01 '16 at 21:57
  • He doesn't have dependencies. The requirement class must be instantiated before the IoC container is built (which happens before `Configure` method is called, but you need to register the requirements in the `ConfigureServices` method. – Tseng Jun 02 '16 at 06:10
  • I'm trying to find the 'context.User' in a list but this list could change at any time. I managed to do this by making the requirement empty and injecting the repository dependency into the handler and comparing it straight to the context.User, is this good practice? Would it be better to entirely remove the requirement? @Tseng – Matthew Jun 02 '16 at 08:15
  • Doing this makes unauthorized users fail as expected: `Microsoft.AspNet.Authorization.DefaultAuthorizationService: Information: Authorization failed for user: ******. Microsoft.AspNet.Mvc.Controllers.ControllerActionInvoker: Warning: Authorization failed for the request at filter 'Microsoft.AspNet.Mvc.Filters.AuthorizeFilter'.` But if this happens it will just redirect me to the same login page and retry, making this a loop trying to log me in that repeats every second. I didn't find a solution anywhere since nobody seems to mention this. – Matthew Jun 02 '16 at 08:44
  • I accepted your answer since it removed the error, I will create a new question asking why I keep getting redirected to the login.onmicrosoft every second. – Matthew Jun 02 '16 at 21:06
0

Not sure if this is really what you are looking for, but just try to get the IRepository instance first, and then pass it to the NewRequirement class

var repo = services
                .BuildServiceProvider()
                .GetRequiredService<IRepository>();

    services.Configure<AuthorizationOptions>(options =>
    {
        options.AddPolicy("NewRequirement", 
                policy => policy.Requirements.Add(new NewRequirement(repo)));
    });
Bellash
  • 5,719
  • 3
  • 39
  • 72