首页 > 解决方案 > CakePHP 4.1 用户实体作为授权身份相关字段

问题描述

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

有两个数据库表 -usersuser_types. users有一个外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
    }
}

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

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

##### 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;
    }
}

但是,我无法获得文件中的身份User来填充UserPolicy.php该字段。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”,而不是它应该是的方式,所以我最初的问题仍然存在。

标签: cakephpormentitycakephp-4.x

解决方案


身份由相关标识符的解析器获取。在 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,因为默认情况下它只是一个字符串,而不是与新配置合并的数组!

也可以看看


推荐阅读