使用Api平台,在创建授权用户时会自动将授权用户添加为资源所有者吗?

问题描述 投票:2回答:3

如何在创建时自动将当前授权的用户添加到资源(POST。]

我正在使用JWT身份验证,并且/ api /路由受到保护,防止未经授权的用户使用。我想进行设置,以便在通过身份验证的用户创建新资源时(即,通过向POST发送/api/articles请求),新创建的Article资源与经过身份验证的用户有关。

我目前正在使用每种资源类型的自定义EventSubscriber从令牌存储中添加用户。

这是订户基本类的要点:https://gist.github.com/dsuurlant/5988f90e757b41454ce52050fd502273

以及扩展它的实体订户:https://gist.github.com/dsuurlant/a8af7e6922679f45b818ec4ddad36286

但是,例如,如果实体构造函数要求用户作为参数,则此方法不起作用。

例如

class Book {

    public User $owner;
    public string $name;

    public class __construct(User $user, string $name) {
        $this->owner = $user;
        $this->name  = $name;
    }
}

如何在创建实体时自动注入授权用户?

php symfony symfony4 api-platform.com
3个回答
0
投票

暂时,我正在使用DTOs and data transformers

主要缺点是必须为每个需要这种行为的资源创建一个新的DTO。

作为一个简单的例子,我正在做这样的事情:

class BootDtoTransformer implements DataTransformerInterface
{

    private Security $security;

    public function __construct(Security $security)
    {
        $this->security = $security;
    }

    public function transform($data, string $to, array $context = [])
    {

        $owner = $this->security->getUser();

        return $new Book($owner, $data->name);;
    }

    public function supportsTransformation($data, string $to, array $context = []): bool
    {

        if ($data instanceof Book) {
            return false;
        }

        return Book::class === $to && null !== ($context['input']['class'] ?? null);
    }

}

这在逻辑上仅适用于单个资源。为了拥有用于多种资源的通用转换器,我最终使用一些接口来划分“拥有的”资源,并进行一些实例化以实例化每个类。

我本以为在非规范化阶段这是可行的,但我无法使它正常工作。


0
投票

我的看法:没有Gedmo,没有魔法。正确的OOP代码。虽然这本身并不能回答您的问题,但确实可以解决您的问题。

[使用Factory Pattern通过工厂抽象对象的创建。

<?php

declare(strict_types=1);

namespace App\Books\Domain\Factory;

use App\Books\Domain\Model\Book;

interface BookFactory
{
    public function create(string $name): Book;
}

随后是实现:

<?php

declare(strict_types=1);

namespace App\Books\Domain\Factory;

use App\Books\Domain\Model\Book;
use Symfony\Component\Security\Core\Security;

final class SymfonySecurityBookFactory implements BookFactory
{
    private $security;

    public function __construct(Security $security)
    {
        $this->security = $security;
    }

    public function create(string $name): Book
    {
        return new Book($this->security->getUser(), $name);
    }
}

示例单元测试:

final class SymfonySecurityBookFactoryTest extends TestCase
{
    public function testItCreatesABook(): void
    {
        $user = new DomainUser();
        $security = $this->prophesize(Security::class);
        $security->getUser()->willReturn($user);
        $factory = new SymfonySecurityBookFactory($security->reveal());

        $book = $factory->create('Test');

        $this->assertSame($user, $book->getUser());
        $this->assertSame('Test', $book->getName());
    }
}

示例用法形式:

public function buildForm(FormBuilderInterface $builder): void
{
    $builder->setData(function (FormInterface $form): Book {
        return $this->bookFactory->create($form->get('name')->getData());
    });

    // … individual fields
}

-1
投票

正如@ n​​ealio82和@lavb所说,您应该查看Gedmo \ Blameable,它可以帮助您将属性存储为createdByupdatedBy,您可以在其中存储创建资源的User

BlameableStofDoctrineExtensionsBundle

然后处理访问,请看一下Voter,它对处理安全性和不同的访问非常有用。

Official Symfony documentation about Voters


例如

图书实体

...
use Gedmo\Mapping\Annotation as Gedmo;

class Book {

    ...

    /**
     * @var string $createdBy
     *
     * @Gedmo\Blameable(on="create")
     * @ORM\Column
     */
    public User $owner;

    public function getOwner() {
        return $this->owner;
    }

    public function setOwner(User $owner) {
        $this->owner = $owner
    }
}

src /安全性/投票/ BookVoter

namespace App\Security;

use App\Entity\Book;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;

class BookVoter extends Voter
{
    const VIEW = 'view';
    const EDIT = 'edit';

    protected function supports(string $attribute, $subject)
    {
        // if the attribute isn't one we support, return false
        if (!in_array($attribute, [self::VIEW, self::EDIT])) {
            return false;
        }

        // only vote on `Book` objects
        if (!$subject instanceof Book) {
            return false;
        }

        return true;
    }

    protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token) {
        $user = $token->getUser();

        if (!$user instanceof User) {
            // the user must be logged in; if not, deny access
            return false;
        }

        /** @var Book $book */
        $book = $subject;

        switch ($attribute) {
            case self::VIEW:
                return $this->canView($book, $user);
            case self::EDIT:
                return $this->canEdit($book, $user);
        }

        throw new \LogicException('This code should not be reached!');
    }

    private function canEdit(Book $book, User $user) {
        // ONLY OWNER CAN EDIT BOOK
        return $user === $book->getOwner();
    }

    private function canView(Book $book, User $user) {
        // DIFFERENT LOGIC ?
        return $user === $book->getOwner();
    }

    ...

}
© www.soinside.com 2019 - 2024. All rights reserved.