Как установить группы нормализации на основе роли пользователя и метода запроса - PullRequest
0 голосов
/ 21 февраля 2020

Я создаю приложение-песочницу как практику на платформе Api, и мне нужно решить следующую проблему:
Давайте рассмотрим следующую конечную точку REST для пользовательской сущности:
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ в примерах кода там немного больше атрибутов, но вся концепция применима в отношении этого

Collection-get (aka. / api / users) - доступно только для администраторов (все атрибуты доступны, может быть, мы исключаем хэширование пароль)

POST - каждый должен иметь доступ к следующим атрибутам: имя пользователя, электронная почта, plainPassword (не сохраняется на случай, если кто-то спросит)

PATCH / PUT - здесь все становится довольно сложно: я хочу, чтобы те, у кого ROLE_ADMIN, имели доступ к полям username, email, plainPassword. А те, кто является владельцами, могут изменять только plainPassword

DELETE - только ROLE_ADMIN и владельцы могут удалять

Я начну с конфигурации ресурса

resources:
    App\Entity\User:
        # attributes:
        #     normalization_context:
        #         groups: ['read', 'put', 'patch', 'post', 'get', 'collection:get']
        #     denormalization_context:
        #         groups: ['read', 'put', 'patch', 'post', 'get', 'collection:get']
        collectionOperations:
            get:
                security: 'is_granted("ROLE_ADMIN")'
                normalization_context: { groups: ['collection:get'] }
            post: 
                normalization_context: { groups: ['admin:post', 'post'] }
        itemOperations:
            get:
                normalization_context: { groups: ['admin:get', 'get'] }
                security: 'is_granted("ROLE_ADMIN") or object == user'
            put:
                normalization_context: { groups: ['admin:put', 'put'] }
                security: 'is_granted("ROLE_ADMIN") or object == user'
            patch:
                normalization_context: { groups: ['admin:patch', 'patch'] }
                security: 'is_granted("ROLE_ADMIN") or object == user'
            delete:
                security: 'is_granted("ROLE_ADMIN") or object == user'

Вот конфигурация сериализатора

App\Entity\User:
    attributes:
        username:
            groups: ['post', 'admin:put', 'admin:patch', 'collection:get', 'get']
        email:
            groups: ['post', 'admin:put', 'admin:patch', 'collection:get', 'get']
        firstName:
            groups: ['post', 'admin:put', 'admin:patch', 'collection:get', 'get']
        lastName:
            groups: ['post', 'admin:put', 'admin:patch', 'collection:get', 'get']
        plainPassword:
            groups: ['post', patch]
        createdAt:
            groups: ['get', 'collection:get']
        lastLoginDate:
            groups: ['get', 'collection:get']
        updatedAt:
            groups: ['collection:get']

Вот построитель контекстной группы (Зарегистрирован как сервис, как указано в API-платформе: c

<?php

namespace App\Serializer;

use Symfony\Component\HttpFoundation\Request;
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

final class AdminContextBuilder implements SerializerContextBuilderInterface
{
    private $decorated;
    private $authorizationChecker;

    public function __construct(SerializerContextBuilderInterface $decorated, AuthorizationCheckerInterface $authorizationChecker)
    {
        $this->decorated = $decorated;
        $this->authorizationChecker = $authorizationChecker;
    }

    public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array
    {
        $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);

        if ($this->authorizationChecker->isGranted('ROLE_ADMIN')) {
            switch($request->getMethod()) {
                case 'GET':
                    $context['groups'][] = 'admin:get';
                    break;
                case 'POST':
                    $context['groups'][] = 'admin:post';
                case 'PUT':
                    $context['groups'][] = 'admin:put';
                case 'PATCH':
                    $context['groups'][] = 'admin:patch';
            }
        }

        return $context;
    }
}

проблема в том, что даже если я вошел в систему как пользователь только с ROLE_USER, я все еще могу изменить поле имени пользователя, которое должно быть заблокировано в соответствии с группой нормализации admin: patch. Я довольно новичок в api-платформе и не могу Я вполне понимаю, почему это не работает, но я думаю, что возникнет проблема с конструктором контекста. Спасибо за вашу помощь, я буду держать вопрос в курсе, если я что-нибудь придумаю тем временем

1 Ответ

0 голосов
/ 22 февраля 2020

После изучения документации и просмотра YouTube и, прежде всего, экспериментов с вышеупомянутым пользовательским ресурсом, я нашел решение
Давайте начнем с конфигурации снова:

resources:
    App\Entity\User:
        collectionOperations:
            get:
                security: 'is_granted("ROLE_ADMIN")'
                normalization_context: { groups: ['collection:get'] }
                denormalization_context: { groups: ['collection:get'] }
            post:
                normalization_context: { groups: ['post'] }
                denormalization_context: { groups: ['post'] }
        itemOperations:
            get:
                normalization_context: { groups: ['get'] }
                security: 'is_granted("ROLE_ADMIN") or object == user'
            patch:
                normalization_context: { groups: ['patch'] }
                denormalization_context: { groups: ['patch'] }
                security: 'is_granted("ROLE_ADMIN") or object == user'
            delete:
                security: 'is_granted("ROLE_ADMIN") or object == user'

Основное различие между отправной точкой является то, что действия администратора никогда не должны указываться в группах операций, поскольку они будут добавлены по умолчанию в контекст.

Далее группы свойств, в которых мы определяем все операции, доступные для определенного свойства

App\Entity\User:
    attributes:
        id:
            groups: ['get', 'collection:get']
        username:
            groups: ['post', 'admin:patch', 'get', 'collection:get']
        email:
            groups: ['post', 'admin:patch', 'get', 'collection:get']
        plainPassword:
            groups: ['post', 'patch', 'collection:get']
        firstName:
            groups: ['post', 'patch', 'get', 'collection:get']
        lastName:
            groups: ['post', 'get', 'collection:get']
        createdAt:
            groups: ['get', 'collection:get']
        lastLoginDate:
            groups: ['get', 'collection:get']
        updatedAt:
            groups: ['collection:get']

Это почти то же самое, что и в вопросе, который нам нужно настроить, это только то, какие действия должны быть «администраторами», их можно изменить в соответствии с вашими потребностями, независимо от того, программируете ли вы блог, библиотеку, магазин и т. Д. и вам нужно несколько пользовательских действий для каждой роли в вашем API.

Наконец, это пользовательский конструктор контекста

<?php

namespace App\Serializer;

use Symfony\Component\HttpFoundation\Request;
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

final class AdminContextBuilder implements SerializerContextBuilderInterface
{
    private $decorated;
    private $authorizationChecker;

    public function __construct(SerializerContextBuilderInterface $decorated, AuthorizationCheckerInterface $authorizationChecker)
    {
        $this->decorated = $decorated;
        $this->authorizationChecker = $authorizationChecker;
    }

    public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array
    {
        $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);

        if ($this->authorizationChecker->isGranted('ROLE_ADMIN')) {
            $context['groups'][] = 'admin:patch';
            $context['groups'][] = 'admin:post';
            $context['groups'][] = 'admin:get';
        }

        return $context;
    }
}

Это довольно просто и может быть расширено для ваших личных потребностей, в основном мы проверяем, если текущий пользователь является администратором, дайте ему свойства от группы а, б, c и др. c. Это также может быть указано для каждой сущности (подробнее о том, что вы можете найти в платформе API, выполните c BookContextBuilder довольно просто

Я почти уверен, что это тот хлеб с маслом, который понадобится любому при создании какого-то простого или даже сложного API, где роли будут определять, кто и что может делать. Если этот ответ поможет вам, обязательно уточните мой ответ, большое спасибо и удачного кодирования!

...