策略

策略是解析给定对象权限的类。您可以为应用程序中您希望应用权限检查的任何类创建策略。

创建策略

您可以在 src/Policy 目录中创建策略。策略类没有预期实现的通用基类或接口。然后,应用程序类将“解析”到匹配的策略类。有关如何解析策略的更多信息,请参阅 策略解析器 部分。

通常,您希望将策略放在 src/Policy 中并使用 Policy 类后缀。现在,我们将为应用程序中的 Article 实体创建一个策略类。在 src/Policy/ArticlePolicy.php 中放入以下内容

<?php
namespace App\Policy;

use App\Model\Entity\Article;
use Authorization\IdentityInterface;

class ArticlePolicy
{
}

除了实体外,表对象和查询也可以解析策略。查询对象将调用其 repository() 方法,并将根据表名生成策略类。表类 App\Model\Table\ArticlesTable 将映射到 App\Policy\ArticlesTablePolicy

您可以使用 bake 为 ORM 对象生成空的策略类

# Create an entity policy
bin/cake bake policy --type entity Article

# Create a table policy
bin/cake bake policy --type table Articles

编写策略方法

我们刚刚创建的策略类现在没有做太多事情。让我们定义一个方法,让我们检查用户是否可以更新文章

public function canUpdate(IdentityInterface $user, Article $article)
{
    return $user->id == $article->user_id;
}

策略方法必须返回 trueResult 对象以表示成功。所有其他值将被解释为失败。

策略方法将在处理未经身份验证的用户时为 $user 参数接收 null。如果您希望自动使匿名用户的策略方法失败,可以使用 IdentityInterface 类型提示。

策略结果对象

除了布尔值外,策略方法还可以返回 Result 对象。 Result 对象允许提供更多有关策略通过/失败原因的上下文

use Authorization\Policy\Result;

public function canUpdate(IdentityInterface $user, Article $article)
{
    if ($user->id == $article->user_id) {
        return new Result(true);
    }
    // Results let you define a 'reason' for the failure.
    return new Result(false, 'not-owner');
}

任何不是 trueResultInterface 对象的返回值都将被视为失败。

策略范围

除了策略能够定义通过/失败授权检查外,它们还可以定义“范围”。范围方法允许您修改应用授权条件的另一个对象。这在将列表视图限制为当前用户时非常有用

namespace App\Policy;

class ArticlesTablePolicy
{
    public function scopeIndex($user, $query)
    {
        return $query->where(['Articles.user_id' => $user->getIdentifier()]);
    }
}

策略前置条件

在某些策略中,您可能希望对策略中的所有操作应用通用检查。当您需要拒绝对提供资源的所有操作时,这很有用。要使用前置条件,您需要在策略中实现 BeforePolicyInterface

namespace App\Policy;

use Authorization\IdentityInterface;
use Authorization\Policy\BeforePolicyInterface;
use Authorization\Policy\ResultInterface;

class ArticlesPolicy implements BeforePolicyInterface
{
    public function before(?IdentityInterface $identity, mixed $resource, string $action): ResultInterface|bool|null
    {
        if ($identity->getOriginalData()->is_admin) {
            return true;
        }
        // fall through
    }
}

预期之前钩子将返回以下三个值之一

  • true 用户被允许继续执行该操作。

  • false 用户不被允许继续执行该操作。

  • null 之前钩子没有做出决定,将调用授权方法。

范围前置条件

与策略一样,范围也可以定义前置条件。当您想要对策略中的所有范围应用通用条件时,这些条件很有用。要在范围内使用前置条件,您需要在范围策略中实现 BeforeScopeInterface

namespace App\Policy;

use Authorization\Policy\BeforeScopeInterface;

class ArticlesTablePolicy implements BeforeScopeInterface
{
    public function beforeScope($user, $query, $action)
    {
        if ($user->getOriginalData()->is_trial_user) {
            return $query->where(['Articles.is_paid_only' => false]);
        }
        // fall through
    }
}

预期之前范围钩子将返回修改后的资源对象,或者如果返回 null,则将按正常方式调用范围方法。

应用策略

请参阅 应用策略范围,了解如何在控制器操作中应用策略。