FOS REST包上的动态序列化组

时间:2015-03-11 14:51:23

标签: rest symfony fosrestbundle

我目前正在使用FOSRESTBundleJMSSerialize制作RESTFull API(当然)。

我的项目是客户和管理员的外联网。

通过这种方式,我必须禁止客户查看某个字段,仅对管理员可见。

我开始为实体制作此序列化程序配置:

AppBundle\Entity\IncidentComment:
    exclusion_policy: ALL
    properties:
        id:
            expose: true
            groups: [list, details]
        author:
            expose: true
            groups: [list, details]
        addedAt:
            expose: true
            groups: [list, details]
        content:
            expose: true
            groups: [details]
        customerVisible:
            expose: true
            groups: [list_admin, details_admin]

如您所见,customerVisible组后缀为_admin。此字段仅应显示给管理员。

我想动态添加带有_admin后缀的组来设置视图上的组,如果用户有例如ROLE_ADMIN角色或其他条件而没有在每个其他控制器的每个操作上写入它。

我正在考虑使用安全上下文参数创建一个custom view handler来添加组,但我不知道是否是正确的方法。

你认为这是好方法吗?你有什么建议吗?

顺便说一下,如果某个开发者遇到同样的问题,我会很高兴他在这里解决了这个问题! :)

感谢。

4 个答案:

答案 0 :(得分:3)

我刚刚想出了一种在运行时添加SerializerGroups的简单方法:

private function determineRolebasedSerializerGroup($role, $groupToAdd, Request $request) {
    if (!$this->get('security.context')->isGranted($role))
        return;

    $groups = $request->attributes->get('_view')->getSerializerGroups();
    $groups[] = $groupToAdd;
    $x = $request->attributes->get('_view')->setSerializerGroups($groups);
}

我已将此方法添加到我的控制器中。我现在能够这样称呼它:

/**
 * @REST\View(serializerGroups={"company"})
 */
public function getCompanyAction(Company $company, Request $request) {
    $this->determineRolebasedSerializerGroup('ROLE_ADMIN', 'company-admin', $request);

    return $company;
}

如果当前用户具有角色“ROLE_ADMIN”,则会将“company-admin”组添加到序列化程序组。这对我来说非常有用。

答案 1 :(得分:2)

如果您想使用侦听器执行此操作,则可以创建自己的ViewResponseListener并将其订阅到事件kernel.view。 您的侦听器必须在FOSRest侦听器之后触发,因此,您必须设置101优先级。

app.event.listener.extended_view_response:
    class: AppBundle\EventListener\ExtendedViewResponseListener
    arguments: ["@security.authorization_checker"]
    tags:
        - { name: kernel.event_listener, event: kernel.view, method: onKernelView, priority: 101 }

ExtendedViewResponseListener.php

public function onKernelView(GetResponseForControllerResultEvent $event)
{
    if (null !== $viewAttribute = $event->getRequest()->attributes->get('_template')) {
        $groups = [];

        foreach(User::getPossibleRoles() as $role) {
            if ($this->authorizationChecker->isGranted($role)) {
                $groups[] = strtolower(str_replace('ROLE_', '', $role)); // ROLE_USER => user group
            }
        }

        $viewAttribute->setSerializerGroups($groups);
    }

}

而且,请不要忘记在控制器中启用_template属性,我的意思是 控制器注释中的@\FOS\RestBundle\Controller\Annotations\View()。 如果你想弄清楚它是如何工作的 - 请检查FosRestBundle中的ViewResponseListener.php。

第二种方法 - 您的自定义序列化程序tokenstorageaware_serializer

答案 2 :(得分:1)

除了@Vladislav Kopaygorodsky的回复之外,即使您从控制器中的函数中省略@View注释,这些添加也允许它工作:

namespace AppBundle\EventListener;

use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use AppBundle\Security\User;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use FOS\RestBundle\Controller\Annotations\View as ViewAnnotation;

/**
 * Listener to automatically adjust serializer groups based on user roles.
 *
 * If a user is granted the USER_XYZ role, then this function will add the
 * serializer group "has_role_xyz" before the automatic serialization takes
 * place on the data returned from a controller.
 */
class PermissionResponseListener
{
    private $authorizationChecker;

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

    public function onKernelView(GetResponseForControllerResultEvent $event)
    {
        $attr = $event->getRequest()->attributes;
        if (null === $viewAttribute = $attr->get('_template')) {
            // No @Rest\View annotation, create a blank one.
            $viewAttribute = new ViewAnnotation(array());
            $viewAttribute->setPopulateDefaultVars(false);
            $attr->set('_template', $viewAttribute);
        }

        $groups = $viewAttribute->getSerializerGroups();
        // Always include this group, since the default value set in
        // config.yml is no longer used.
        $groups[] = 'Default';

        foreach (User::getPossibleRoles() as $role) {
            if ($this->authorizationChecker->isGranted($role)) {
                $groups[] = 'has_' . strtolower($role); // ROLE_USER => has_role_user
            }
        }

        $viewAttribute->setSerializerGroups($groups);
    }
}

User类有一个只列出所有可用角色的函数:

public static function getPossibleRoles()
{
    return [
        'ROLE_ADMIN',     // system administrators
        'ROLE_OFFICE',    // data entry staff
        'ROLE_USER',      // anyone logged in
    ];
}

和services.yml:

# Set serializer groups based on user roles
AppBundle\EventListener\PermissionResponseListener:
    public: false
    tags:
        - { name: kernel.event_listener, event: kernel.view, method: onKernelView, priority: 101 }

在实体中,您现在可以使用注释:

class ExampleEntity {
    /**
     * @Serializer\Groups({"has_role_admin"})
     */
    protected $adminOnlyValue;

答案 3 :(得分:0)

补充上面 Vladislav Kopaygorodsky 的回答并回答 Malvineous 提出的问题:

根据Symfony (5.2) documentation,事件监听者/订阅者优先级是一个整数。值越高,运行越早。在撰写本文时,FOS Rest 包的 ViewResponseListener 将自身设置为 30。因此,如果自定义侦听器/订阅者的优先级为 101,它将在 FOS 优先级之前运行。