如何根据用户角色和请求方式设置归一化组

How to set normalization groups based on user's role and request method

我正在创建沙盒应用作为 Api 平台实践,我有以下问题需要解决:
让我们考虑以下用户实体的 REST 端点:
免责声明 在代码示例中有更多的属性,但整个概念适用于

Collection-get(又名。/api/users) - 仅适用于管理员用户(所有属性均可用,也许我们排除了散列密码)

POST - 每个人都应该有权访问以下属性:用户名、电子邮件、明文密码(不会保留以防万一有人问)

PATCH/PUT - 这里变得非常棘手:我希望那些 ROLE_ADMIN 的人能够访问用户名、电子邮件、纯密码字段。而那些所有者只能更改 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-平台文档

中所述
<?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 平台的新手,我不太明白为什么这不起作用,但我猜上下文构建器会有问题。感谢您的帮助,如果我同时想出一些办法,我会及时更新问题

在调查了文档和浏览了 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']

这与问题中的问题完全相同,我们唯一需要配置的是哪些操作需要 'admin' 无论您对博客、图书馆、商店进行编程,都可以根据您的需要进行更改或其他任何东西,并且需要在 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;
    }
}

这很简单,可以根据您的个人需求进行扩展,基本上我们会检查当前用户是否是管理员,并为他提供组 a、b、c 等的属性。这也可以按实体指定(更多信息您可以在 API 平台文档中找到 BookContextBuilder 非常简单

我很确定这是任何人在构建一些简单甚至复杂的 API 时都需要的面包和黄油,其中角色将决定谁可以做什么。如果这个答案对您有帮助,请务必补充我的答案,非常感谢,祝您编码愉快!