Создание ASP.Net Core IAuthorizationRequirement на основе параметра маршрута - PullRequest
0 голосов
/ 14 марта 2019

У меня есть группы страниц, которые разделяют общие требования авторизации.Рассмотрим следующую структуру:

https://example.com/Company/{companyId}/Billing/Index
https://example.com/Company/{companyId}/Billing/Edit
https://example.com/Company/{companyId}/Billing/(other pages)
https://example.com/Company/{companyId}/Profile/Index
https://example.com/Company/{companyId}/Profile/Edit
https://example.com/Company/{companyId}/Profile/(other pages)

Мне нужно убедиться, что пользователи, попадающие на любые страницы под /Company/{companyId}/Billing, имеют полномочия для управления выставлением счетов внутри компании с помощью id = {companyId}.Аналогично, пользователи, запрашивающие страницы под /Company/{companyId}/Profile, должны иметь полномочия для управления информацией профиля для компании, включенной в маршрут.Это не простая роль ... это роль применительно к компании, конфигурация которой находится в нашей базе данных.

У меня есть решение, которое работает, но оно кажется ... неправильным.Мне нужно найти идентификатор маршрута {companyId} в пределах AuthorizationHandler, и это просто не похоже.Конечно, это должен быть достаточно распространенный сценарий (защита ресурсов не только на основе роли, но и на основе сведений о фактическом ресурсе), что есть более плавный путь.

Я включаю свое текущее решение в качестве ответа ниже,но приветствуем отзывы и рекомендации для лучшей реализации.

1 Ответ

0 голосов
/ 14 марта 2019

Вот мое текущее решение, но не похоже, что это «правильный путь». В частности, мне кажется, что идентификатор компании должен как-то быть частью 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")));
    });
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...