CakePHP 4.1 用户实体作为授权身份关联字段

CakePHP 4.1 User entity as authorization identity associated fields

我刚刚在 CakePHP 4.1 中创建了一个非常小的项目,主要是模仿 CMS 教程,并且想要实现一个相当简单的逻辑。 使用 Authorization 模块,我想让用户 A 能够 view 用户 B 如果 1) 他们实际上是同一个用户 (A = B ) 或 2) 如果 A 是管理员。

有两个数据库表 - usersuser_typesusers 有一个外键 user_type_iduser_types

这种关系在代码中体现为:

##### in UsersTable.php #####

class UsersTable extends Table {
    public function initialize(array $config): void
    {
        parent::initialize($config);

        $this->setTable('users');
        $this->setDisplayField('name');
        $this->setPrimaryKey('id');
        $this->belongsTo('UserTypes');

        $this->addBehavior('Timestamp');
    }

    //...
}



##### in UserTypesTable.php #####

class UserTypesTable extends Table {
    public function initialize(array $config): void
    {
        parent::initialize($config);

        $this->setTable('user_types');
        $this->setDisplayField('name');
        $this->setPrimaryKey('id');
        $this->hasMany('Users');
    }

    //...
}

UsersController.php 我有:

    public function view($id = null)
    {
        $user = $this->Users->get($id, [
            'contain' => ['UserTypes'],
        ]);

        $this->Authorization->authorize($user);

        $this->set(compact('user'));
    }

并且在 UserPolicy.php 中:

use App\Model\Entity\User;

class UserPolicy
{
    public function canView(User $user, User $resource)
    {
        // TODO: allow view if $user and $resource are the same User or if $user is an admin
        //
        // My problem is that here $user->user_type is NULL
        // while $resource->user_type is populated correctly
    }
}

以上摘录中的代码注释显示了我的问题所在。 我不知道如何让 $user 填充其 user_type 字段以检查他们是否是管理员。

作为我努力的一部分,我已将用户 class 设置为授权身份,遵循这篇文章:https://book.cakephp.org/authorization/2/en/middleware.html#using-your-user-class-as-the-identity。 代码方面看起来像:

##### relevant part of Application.php #####

$middlewareQueue
            ->add(new AuthenticationMiddleware($this))
            ->add(new AuthorizationMiddleware($this, [
                'identityDecorator' => function(\Authorization\AuthorizationServiceInterface $auth, \Authentication\IdentityInterface $user) {
                    return $user->getOriginalData()->setAuthorization($auth);
                }
            ]));





##### User.php #####

namespace App\Model\Entity;

use Authentication\PasswordHasher\DefaultPasswordHasher;
use Authorization\AuthorizationServiceInterface;
use Authorization\Policy\ResultInterface;
use Cake\ORM\Entity;

/**
 * User Entity
 *
 * @property int $id
 * @property string $email
 * @property string $password
 * @property string|null $name
 * @property \App\Model\Entity\UserType $user_type
 * @property \Cake\I18n\FrozenTime|null $created
 * @property \Cake\I18n\FrozenTime|null $modified
 * @property \Authorization\AuthorizationServiceInterface $authorization
 */
class User extends Entity implements \Authorization\IdentityInterface, \Authentication\IdentityInterface
{
    protected $_accessible = [
        'email' => true,
        'password' => true,
        'name' => true,
        'created' => true,
        'modified' => true,
    ];

    /**

    protected $_hidden = [
        'password',
    ];

    protected function _setPassword(string $password) : ?string
    {
        if (strlen($password) > 0) {
            return (new DefaultPasswordHasher())->hash($password);
        }
    }

    /**
     * @inheritDoc
     */
    public function can(string $action, $resource): bool
    {
        return $this->authorization->can($this, $action, $resource);
    }

    /**
     * @inheritDoc
     */
    public function canResult(string $action, $resource): ResultInterface
    {
        return $this->authorization->canResult($this, $action, $resource);
    }

    /**
     * @inheritDoc
     */
    public function applyScope(string $action, $resource)
    {
        return $this->authorization->applyScope($this, $action, $resource);
    }

    /**
     * @inheritDoc
     */
    public function getOriginalData()
    {
        return $this;
    }

    /**
     * Setter to be used by the middleware.
     * @param AuthorizationServiceInterface $service
     * @return User
     */
    public function setAuthorization(AuthorizationServiceInterface $service)
    {
        $this->authorization = $service;

        return $this;
    }

    /**
     * @inheritDoc
     */
    public function getIdentifier()
    {
        return $this->id;
    }
}

但是,我无法在 UserPolicy.php 文件中获取标识 User 以填充 user_type 字段。 当我从控制器调用 $this->Authorization->authorize() 时,似乎发生了一些幕后魔法,我在控制器中显式地将资源与其用户类型一起传递(因为我已经使用 'contain' => ['UserTypes'] 构建了它,但身份用户已填充由 Authorization 模块自动生成。 有人可以帮我找到一种方法将关联表数据带入授权策略的身份用户吗?

注意: 我篡改了代码以使其像这样工作:

##### in UserPolicy.php #####


use App\Model\Entity\User;

class UserPolicy
{
    public function canView(User $user, User $resource)
    {
        $user = \Cake\Datasource\FactoryLocator::get('Table')->get('Users')->get($user->id, ['contain' => ['UserTypes']]);

        // Now both $user->user_type and $resource->user_type are correctly populated
    }
}

然而,这感觉非常“hacky”,而不是它应该的样子,所以我原来的问题仍然存在。

相关标识符的解析器正在获取身份。在 CMS 教程中,Password 标识符默认使用 ORM 解析器。

如果您需要控制获取用户的查询,ORM 解析器可以配置为使用自定义查找器,这就是您应该为 UserTypes 关联添加包含的地方。

在您的 UsersTable 中添加这样的查找器:

public function findForAuthentication(\Cake\ORM\Query $query, array $options): \Cake\ORM\Query
{
    return $query->contain('UserTypes');
}

并配置标识符的解析器以像这样使用该查找器:

$service->loadIdentifier('Authentication.Password', [
    'resolver' => [
        'className' => 'Authentication.Orm',
        'finder' => 'forAuthentication',
    ],
    'fields' => [
        'username' => 'email',
        'password' => 'password',
    ]
]);

覆盖 resolver 选项时,您还需要指定解析器 class 名称,因为默认情况下它只是一个字符串,而不是将与新配置合并的数组!

另见