Reputation: 1016
I want to create a claim based authorization for my ASP.NET Core app:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});
}
The problem is that I have a non trivial method to resolve the employee numbers (1 to 5) and I want to use a DI service:
public interface IEmployeeProvider {
string[] GetAuthorizedEmployeeIds();
}
I would like to inject this service and use it in AddPolicy, something like:
services.AddAuthorization(options =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", *employeeProvider.GetAuthorizedEmployeeIds()));
});
Note
I know that I can write my own AuthorizationHandler where I can easily inject IEmployeeProvider
but I'm against this pattern because:
So I'm looking for a way to inject services when the policy is being built
Upvotes: 8
Views: 4132
Reputation: 87
I resolved in this way, options above show me error about service root, but i try and finally make it
Program.cs
builder.Services.AddAuthorization(options =>
{
new RegisterPoliciesService(builder.Services).Register(options);
});
builder.Services.AddTransient<IAuthorizationHandler, PermissionHandler>();
RegisterPolicies.cs
public class RegisterPoliciesService {
private IServiceCollection _serviceProvider;
public RegisterPoliciesService(IServiceCollection serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Register(AuthorizationOptions _autorizationOptions)
{
using (var scope = _serviceProvider.BuildServiceProvider().CreateScope())
{
var repositoryPermissions = scope.ServiceProvider.GetRequiredService<IPermissionRepository>();
foreach (var permission in repositoryPermissions.GetChildPermissions())
{
_autorizationOptions.AddPolicy(permission.Rule!, policy =>
policy.Requirements.Add(new PermissionRequirement(permission.Rule!)));
}
}
}
}
Note
You must dispose createscope, this is why i use "using" clause to dispose createscope automatically whem the using finish.I do an automatic policies based on all data in permission table whose permission doesn't have a paprent permission, that means this permission contain the rule and it belongs to a parent permission to be able to group
Upvotes: 0
Reputation: 247088
To supplement the provided answer by @MichaelShterenberg, the configuration delegate can use a IServiceProvider
to allow for additional dependencies
public static IServiceCollection AddAuthorization(this IServiceCollection services,
Action<AuthorizationOptions, IServiceProvider> configure) {
services.AddOptions<AuthorizationOptions>().Configure<IServiceProvider>(configure);
return services.AddAuthorization();
}
Which, based on the original example, can be used
public void ConfigureServices(IServiceCollection services) {
//...
service.AddScoped<IEmployeeProvider, EmployeeProvider>();
services.AddAuthorization((options, sp) => {
IEmployeeProvider employeeProvider = sp.GetRequiredService<IEmployeeProvider>();
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", employeeProvider.GetAuthorizedEmployeeIds())
);
});
//...
}
If there were other dependencies needed, they could be resolved from the service provider.
Upvotes: 12
Reputation: 1016
Thanks to Nkosi for the tip!
Since AddAuthorization is basically configuring AuthorizationOptions
behind the scenes, I followed the same pattern only I used OptionsBuilder
to configure options with dependencies
I created my own AddAuthorization method that accepts dependencies:
public static IServiceCollection AddAuthorization<TDep>(
this IServiceCollection services,
Action<AuthorizationOptions, TDep> configure) where TDep : class
{
services.AddOptions<AuthorizationOptions>().Configure<TDep>(configure);
return services.AddAuthorization();
}
And now I can use it to properly configure the requirement:
services.AddAuthorization<IEmployeeProvider>((options, employeeProvider> =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", employeeProvider.GetAuthorizedEmployeeIds())
);
});
You can follow the same technique if you need more dependencies (OptionsBuilder.Configure
supports up to 5 dependencies)
Obviously, this solution requires extra validation when upgrading to newer ASP versions, as the underlying implementation of AddAuhtorization
may change
Upvotes: 10
Reputation: 20116
You can build a service provider using the BuildServiceProvider() method on the IServiceCollection:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IEmployeeProvider, EmployeeProvider>();
var sp = services.BuildServiceProvider();
var employeeProvider = sp.GetService<IEmployeeProvider>();
string[] values = employeeProvider.GetAuthorizedEmployeeIds();
services.AddAuthorization(options =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", employeeProvider.GetAuthorizedEmployeeIds()));
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
interface and Class
public interface IEmployeeProvider
{
string[] GetAuthorizedEmployeeIds();
}
public class EmployeeProvider : IEmployeeProvider
{
public string[] GetAuthorizedEmployeeIds()
{
var data = new string[] { "1", "2", "3", "4", "5" };
return data;
}
}
Upvotes: -1