Cakephp 3 -如何检索'表'类在验证过程中


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

我使用CakePhp3为我的网站,我必须注入一些基于当前用户Id的自定义验证逻辑,当我创建或修改一个实体。

基本情况是"用户是否允许将这个字段更改为这个新值"?如果不是,我想引发一个验证错误(或一个未经授权的异常)。

在cakephp中,据我所知,大多数应用程序和业务规则必须放在ORM的Models或'ModelsTable'上。但是,在这些类中,AuthComponent或当前会话不可用。

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

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

ModelTable上的方法:

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

I cakephp

感谢您的回复。

EDIT -添加更多细节

这里有更多的细节。对于REST API。我有一个实体的编辑功能。"文章"实体。

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

我可以在控制器上做这个检查,但如果我想要这个规则在我的代码中检查任何地方的文章被修改(在另一个方法比"编辑"的ArticlesController)。所以对我来说,模型似乎是放置它的好地方,不是吗?

认证与授权

  • 身份验证意味着通过凭据识别用户,大多数情况下归结为"是否用户登录"。
  • authorization 表示检查是否允许用户执行特定操作

所以不要把这两者混在一起。

你不想要验证,你想要应用规则

摘自书中:

验证与应用规则

CakePHP ORM的独特之处在于它使用两层方法来验证。

第一层是验证。验证规则的目的是以无状态的方式操作。最好利用它们来确保数据的形状、数据类型和格式是否正确。

第二层是应用程序规则。应用程序规则是最好的用于检查实体的有状态属性。例如,验证规则可以确保电子邮件地址是有效的,而应用程序规则可以确保电子邮件地址是唯一的。

你想要实现的是复杂的应用逻辑,而不仅仅是一个简单的验证,所以实现它的最好方法是作为一个应用规则。

我从我的一篇文章中摘取了一个代码片段,它解释了一个类似的情况。我必须检查与模型相关联的语言(翻译)的限制。你可以在这里阅读全文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;
   }
}

我认为这是不言自明的。确保你的实体user_id字段不能被"public"访问。在保存数据之前,只需在补丁完成后添加数据:

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

如果你修改了上面的代码片段,并将profile_constraint_id改为user_id或其他任何你在那里的东西,这应该为你做的工作。

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

猜你可以使用ACL,但我从来没有需要过基于字段的ACL。所以我不能给你太多的输入,但它曾经是(Cake2)现在仍然是(Cake3)可能的。对于Cake3, ACL的东西被移动到一个插件。从技术上讲,可以检查任何东西,数据库字段,行,任何东西。

你可以写一个行为,使用Model.beforeMarshal事件和检查是否user_id(或角色,或任何)是存在的,而不是空的,然后运行检查所有字段,你想为给定的用户id或用户角色使用ACL。

你可以使用这个方法PermissionsTable::check()或者你可以写一个更专门的方法来同时检查多个对象(字段)。就像我说的,如果你选择使用ACL,你需要花一些时间来找出最好的方法。

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

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

我认为您不需要在表中验证。我刚想到一个在控制器中实现的方法。

在控制器的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']);
}