Вот мое текущее решение, но не похоже, что это «правильный путь». В частности, мне кажется, что идентификатор компании должен как-то быть частью CompanyAuthorizationRequirement
, а не что-то, что можно перехватить в обработчике авторизации.
Создайте IAuthorizationRequirement
, чтобы пометить ресурсы, которые должны быть защищены:
public class CompanyAuthorizationRequirement : IAuthorizationRequirement
{
public CompanyAuthorizationRequirement(string permission)
{
this.Permission = permission;
}
public string Permission { get; private set; }
}
Создайте AuthorizationHandler
для обработки требования:
public class CompanyAuthorizationHandler : AuthorizationHandler<CompanyAuthorizationRequirement>
{
private readonly IActionContextAccessor actionContextAccessor;
private readonly IAuthorizationRepository authorizationRepository;
public CompanyAuthorizationHandler(IActionContextAccessor actionContextAccessor, IAuthorizationRepository authorizationRepository)
{
this.actionContextAccessor = actionContextAccessor;
this.authorizationRepository = authorizationRepository;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, CompanyAuthorizationRequirement requirement)
{
if (context.User.IsInRole(RoleNames.SystemAdministrator))
{
context.Succeed(requirement);
return;
}
// This is the part that smells wrong to me...
object companyIdRouteData = this.actionContextAccessor.ActionContext.RouteData.Values["companyId"];
if (companyIdRouteData == null)
{
companyIdRouteData = this.actionContextAccessor.ActionContext.HttpContext.Request.Query["companyId"];
}
if (companyIdRouteData == null || !int.TryParse(companyIdRouteData.ToString(), out int companyId))
{
context.Fail();
return;
}
string userId = context.User.Claims.First(c => c.Type == "sub").Value;
bool authorized = false;
switch (requirement.Permission)
{
case "billing":
authorized = await this.authorizationRepository.CanManageCompanyBilling(userId, companyId);
break;
case "profile":
authorized = await this.authorizationRepository.CanManageCompanyProfile(userId, companyId);
break;
}
if (authorized)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
}
}
Добавьте соглашения и политики авторизации папок в Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/");
options.Conventions.AuthorizeFolder("/Company/Billing", "CanManageCompanyBilling");
options.Conventions.AuthorizeFolder("/Company/Profile", "CanManageCompanyProfile");
// Change the routing on all pages underneath the Company folder to include the company id in the route.
options.Conventions.AddFolderRouteModelConvention("/Company", model =>
{
Regex templatePattern = new Regex("^Company(/|$)");
foreach (var selector in model.Selectors)
{
selector.AttributeRouteModel.Template = templatePattern.Replace(selector.AttributeRouteModel.Template, "Company/{company:int}$1");
}
});
});
services.AddAuthorization(options =>
{
options.AddPolicy("CanManageCompanyBilling", builder => builder.AddRequirements(new CompanyAuthorizationRequirement("billing")));
options.AddPolicy("CanManageCompanyProfile", builder => builder.AddRequirements(new CompanyAuthorizationRequirement("profile")));
});
}