Cakephp 3 - 如何在验证过程中检索 'Table' class 中的当前登录用户?

Cakephp 3 - How to retrieve current logged user in a 'Table' class during validation process?

我正在为我的网站使用 CakePhp3,当我创建或修改实体时,我必须根据当前用户 ID 注入一些自定义验证逻辑。

基本情况是"Is the user allow to change this field to this new value"?如果不是,我想提出验证错误(或未经授权的异常)。

在 cakephp 中,据我所知,大多数应用程序和业务规则必须放在模型或 ORM 的“模型表”上。但是,在此 类 中,AuthComponent 或当前会话不可用。

我不想每次需要检查时都从控制器手动调用实体上的方法。我想使用验证器,例如:

$validator->add('protected_data', 'valid', [
            'rule' => 'canChangeProtectedData',
            'message' => __('You're not able to change this data !'),
            'provider' => 'table',
        ]);

模型表上的方法:

public function canChangeProtectedData($value, array $context)
{
    \Cake\Log\Log::debug("canChangeProtectedData");
    // Find logged user, look at the new value, check if he is authorized to do that, return true/false
    return false;
}

我的 cakephp < 3,AuthComponent 有一个不再可用的静态方法 'AuthComponent::user()'。那么,我如何在 CakePhp 3 中做到这一点?

感谢您的任何回复。

编辑 - 添加更多详细信息

这里有更多详细信息。如果是 REST API。我有实体的编辑功能。 "Article" 实体。

这篇文章的所有者在名为 "user_id" 的列上有一个外键(这里没有什么特别的)。我的用户被组织成组,组中有一个组长。组长可以更改文章的所有者,但 "basics" 用户不能这样做(但他们可以编辑自己的文章)。管理员用户可以编辑所有内容。 因此编辑方法必须对任何经过身份验证的用户可用,但必须允许更改实体的 "user_id" 并根据情况进行检查(如果我是管理员是,如果我是领导者是只有在新 ID是我的小组之一,如果我是基本用户则否)。

我可以在控制器上进行此检查,但如果我希望在修改文章的代码中的任何地方检查此规则(使用不同于 ArticlesController "Edit" 的另一种方法)。所以对我来说模型似乎是放置它的好地方不是吗?

我认为您不需要在 table 中进行验证。刚想了个办法在controller里做。

例如在控制器中的 Users/Add 方法中:

public function add()
{
    $user = $this->Users->newEntity();
    if ($this->request->is('post')) {
        $user = $this->Users->patchEntity($user, $this->request->data);

        //check if user is logged in and is a certain user
        if ($this->request->session()->read('Auth.User.id') === 1) {
            //allow adding/editing role or whatever
            $user->role = $this->request->data('role');
        } else {
            $user->role = 4;//or whatever the correct data is for your problem.
        }
        if ($this->Users->save($user)) {
            $this->Flash->success(__('You have been added.'));
        } else {
            $this->Flash->error(__('You could not be added. Please, try again.'));
        }
    }
    $this->set(compact('user'));
    $this->set('_serialize', ['user']);
}

身份验证与授权

  • Authentication 表示通过凭据识别用户,大多数情况下归结为 "Is a user logged in".
  • Authorisation表示检查是否允许用户执行特定操作

所以不要混淆这两者。

您不需要验证您需要应用程序规则

摘自本书:

Validation vs. Application Rules

The CakePHP ORM is unique in that it uses a two-layered approach to validation.

The first layer is validation. Validation rules are intended to operate in a stateless way. They are best leveraged to ensure that the shape, data types and format of data is correct.

The second layer is application rules. Application rules are best leveraged to check stateful properties of your entities. For example, validation rules could ensure that an email address is valid, while an application rule could ensure that the email address is unique.

您要实现的是复杂的应用程序逻辑,而不仅仅是简单的验证,因此最好的实现方式是作为应用程序规则。

我从我的一篇解释类似案例的文章中摘录了一段代码。我必须检查可以与模型相关联的语言(翻译)的限制。您可以在此处阅读整篇文章 http://florian-kraemer.net/2016/08/complex-application-rules-in-cakephp3/

<?php
namespace App\Model\Rule;

use Cake\Datasource\EntityInterface;
use Cake\ORM\TableRegistry;
use RuntimeException;

class ProfileLanguageLimitRule {

   /**
    * Performs the check
    *
    * @link http://php.net/manual/en/language.oop5.magic.php
    * @param \Cake\Datasource\EntityInterface $entity Entity.
    * @param array $options Options.
    * @return bool
    */
   public function __invoke(EntityInterface $entity, array $options) {
      if (!isset($entity->profile_constraint->amount_of_languages)) {
         if (!isset($entity->profile_constraint_id)) {
            throw new RuntimeException('Profile Constraint ID is missing!');
         }
         $languageLimit = $this->_getConstraintFromDB($entity);
      } else {
         $languageLimit = $entity->profile_constraint->amount_of_languages;
      }

      // Unlimited languages are represented by -1
      if ($languageLimit === -1) {
         return true;
      }

      // -1 Here because the language_id of the profiles table already counts as one language
      // So it's always -1 of the constraint value
      $count = count($entity->languages);
      return $count <= ($languageLimit - 1);
   }

   /**
    * Gets the limitation from the ProfileConstraints Table object.
    *
    * @param \Cake\Datasource\EntityInterface $entity Entity.
    * @return int
    */
   protected function _getConstraintFromDB(EntityInterface $entity) {
      $constraintsTable = TableRegistry::get('ProfileConstraints');
      $constraint = $constraintsTable->find()
         ->where([
            'id' => $entity['profile_constraint_id']
         ])
         ->select([
            'amount_of_languages'
         ])
         ->firstOrFail();

      return $constraint->amount_of_languages;
   }

}

我认为这是不言自明的。确保 "public" 无法访问您的实体 user_id 字段。在保存数据之前,在修补之后添加它:

$entity->set('user_id', $this->Auth->user('id'));

如果您更改上述代码段并将 profile_constraint_id 更改为 user_id 或您那里的任何其他内容,这应该可以为您完成工作。

您真正想要的是基于行/字段级别的授权

我猜你可以为此使用 ACL,但我从来没有需要基于字段的 ACL。所以我不能给你太多的意见,但它是 (Cake2) 并且仍然是 (Cake3) 可能的。对于 Cake3,ACL 内容已移动 to a plugin。从技术上讲,可以检查任何东西,数据库字段,行,任何东西。

您可以编写一个使用 Model.beforeMarshal 事件的行为,并检查 user_id(或角色,或其他)是否存在且不为空,然后 运行 检查所有使用 ACL 的给定用户 ID 或用户角色所需的字段。

您可以使用 this method PermissionsTable::check() 或者您可以编写一个更专用的方法同时检查多个对象(字段)。就像我说的,如果你愿意的话,你会花一些时间来找出使用 ACL 的最佳方法。

UX 和另一种廉价的解决方案

首先,我会 显示所有字段,不允许用户更改或输入作为输入。如果您需要显示它们,很好,禁用表单输入或仅将其显示为文本。然后使用一组常规验证规则,要求该字段为空(或不存在)或清空基于您的用户角色的字段列表。如果您不显示这些字段,用户将不得不调整表单,然后也无法通过 CSRF 检查(如果使用)。