且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

将控制器操作限制为 Yii2 中的帖子创建者

更新时间:2022-11-06 10:59:30

1) 推荐的方式是使用 RBAC 和规则.它在官方文档中有很好的介绍.部分.

检查作者 ID 是否与通过参数传递的当前用户 ID 匹配的规则示例:

命名空间应用\rbac;使用 yii\rbac\Rule;/*** 检查 authorID 是否与通过 params 传递的用户匹配*/类 AuthorRule 扩展规则{公共 $name = 'isAuthor';/*** @param string|integer $user 用户ID.* @param Item $item 此规则关联的角色或权限* @param array $params 参数传递给 ManagerInterface::checkAccess().* @return boolean 一个值,指示规则是否允许与其关联的角色或权限.*/公共函数执行($user,$item,$params){返回 isset($params['post']) ?$params['post']->createdBy == $user : false;}}

然后您需要将其与现有权限绑定(可以在迁移或扩展中完成):

$auth = Yii::$app->authManager;//添加规则$rule = new \app\rbac\AuthorRule;$auth->add($rule);//添加updateOwnPost"权限并将规则与其关联.$updateOwnPost = $auth->createPermission('updateOwnPost');$updateOwnPost->description = '更新自己的帖子';$updateOwnPost->ruleName = $rule->name;$auth->add($updateOwnPost);//updateOwnPost"将从updatePost"中使用$auth->addChild($updateOwnPost, $updatePost);//允许作者"更新他们自己的帖子$auth->addChild($author, $updateOwnPost);

然后您可以检查您的用户是否可以像这样更新帖子:

use yii\web\ForbiddenHttpException;使用 Yii;公共函数 actionUpdate($id){$model = $this->findModel($id);if (!Yii::$app->user->can('updatePost', ['post' => $model])) {throw new ForbiddenHttpException('你不能编辑这篇文章');}...}

另请注意,如果您首先找到模型并且用户无权编辑它,那么从逻辑上讲,***抛出 403 Forbidden 异常而不是 404,因为它已找到,但不允许编辑.

不要忘记在 AccessControl 行为中包含这样的规则:

['允许' =>真的,'动作' =>['更新'],'角色' =>['@'],],

表示这个控制器的update动作只能被授权用户访问,不包括guest.

2)如果由于某种原因你不想使用 RBAC,你可以使用你的方法:

use yii\web\ForbiddenHttpException;公共函数 actionUpdate($id){$model = $this->findModel($id);if ($model->user_id != Yii::$app->user->id ) {throw new ForbiddenHttpException('你不能编辑这篇文章.');}...}

为了改进这一点,您可以通过将此逻辑移至辅助方法来从该检查中抽象出来:

命名空间 app\posts\components;使用 Yii;类 PostPermission{/*** @param $model 帖子* @return 布尔值*/公共静态函数 allowedToUpdate($model){返回 $model->user_id = Yii:$app->user->id;}}

然后这样称呼它:

使用 app\posts\components\PostPermission;使用 yii\web\ForbiddenHttpException;如果 (!PostPermission::allowedToUpdate($model) {throw new ForbiddenHttpException('你不能编辑这篇文章.');}

这只是一个例子,方法不一定是静态的,你可以使用$model来构造实例.

你可以直接在Post模型中创建方法,但是这样的逻辑***不要污染模型.

3) 我可以建议的另一种替代方法是在查找模型时将范围最初限制为当前用户:

use yii\web\NotFoundHttpException;/*** @param 整数 $id* @return 帖子* @throws NotFoundHttpException*/受保护的函数 findModel($id){$model = Post::find(['id'=> $id, 'user_id' => Yii::$app->user->id])->one();如果($模型){返回 $model;} 别的 {throw new NotFoundHttpException('这篇文章不存在.');}}

这可以为站点管理员改进:

use yii\web\NotFoundHttpException;/*** @param 整数 $id* @return 帖子* @throws NotFoundHttpException*/受保护的函数 findModel($id){$query = Post::find()->where(['id' => $id]);if (!Yii::$app->user->is_admin) {//替换为你自己的支票$query->andWhere(['user_id' => Yii::$app->user->id]);}$model = $query->one();如果($模型){返回 $model;} 别的 {throw new NotFoundHttpException('这篇文章不存在.');}}

那你就只写:

公共函数 actionUpdate($id){$model = $this->findModel($id);...}

这样在两种情况下(未找到模型且当前用户不允许编辑),都会引发 404 Not Found 异常.从另一方面来说,这并没有错,因为从技术上讲,对于这个用户来说,这个模型并不存在(因为他不是它的作者).

Is there an easy way to restrict a controller action to the owner/creator of the post without using full blown RBAC?

Right now I'm doing this for every controller:

public function actionUpdate( $id ) {
    $model = $this->findModel( $id );
    if ( $model->user_id != Yii::$app->user->identity->id ) {
        throw new NotFoundHttpException( 'The requested page does not exist.' );
    }
}

But I think there must be a better way to restrict certain controllers to the users who created the $model thats being edited.

1) The recommended way is to use RBAC and rules. It's covered well in official docs in according dedicated section.

Example of rule that checks if author id matches current user id passed via params:

namespace app\rbac;

use yii\rbac\Rule;

/**
 * Checks if authorID matches user passed via params
 */
class AuthorRule extends Rule
{
    public $name = 'isAuthor';

    /**
     * @param string|integer $user the user ID.
     * @param Item $item the role or permission that this rule is associated with
     * @param array $params parameters passed to ManagerInterface::checkAccess().
     * @return boolean a value indicating whether the rule permits the role or permission it is associated with.
     */
    public function execute($user, $item, $params)
    {
        return isset($params['post']) ? $params['post']->createdBy == $user : false;
    }
}

Then you need to tie it with existing permission (can be done in migration or with extensions):

$auth = Yii::$app->authManager;

// add the rule
$rule = new \app\rbac\AuthorRule;
$auth->add($rule);

// add the "updateOwnPost" permission and associate the rule with it.
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = 'Update own post';
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);

// "updateOwnPost" will be used from "updatePost"
$auth->addChild($updateOwnPost, $updatePost);

// allow "author" to update their own posts
$auth->addChild($author, $updateOwnPost);

Then you can check if you user can update post like this:

use yii\web\ForbiddenHttpException;
use Yii;

public function actionUpdate($id)
{
    $model = $this->findModel($id);
    if (!Yii::$app->user->can('updatePost', ['post' => $model])) {
        throw new ForbiddenHttpException('You are not allowed to edit this post');
    }

    ...
}

Also note that in case you found model first and user has no access to edit it, logically it's better to throw 403 Forbidden exception rather than 404, since it's found, but not allowed for editing.

Don't forget to include rule like that in AccessControl behavior:

[
    'allow' => true,
    'actions' => ['update'],
    'roles' => ['@'],
],

It means that update action of this controller can be only accessed by authorized users excluding guests.

2) If for some reason you don't want to use RBAC, you can use your approach:

use yii\web\ForbiddenHttpException;

public function actionUpdate($id)
{
    $model = $this->findModel($id);
    if ($model->user_id != Yii::$app->user->id ) {
        throw new ForbiddenHttpException('You are not allowed to edit this post.');
    }

    ...
}

To improve this you can abstract from this check by moving this logic to helper method:

namespace app\posts\components;

use Yii;

class PostPermission
{
    /**
     * @param $model Post
     * @return boolean
     */
    public static function allowedToUpdate($model)
    {
        return $model->user_id = Yii:$app->user->id;
    }
}

Then call it like that:

use app\posts\components\PostPermission;
use yii\web\ForbiddenHttpException;

if (!PostPermission::allowedToUpdate($model) {
    throw new ForbiddenHttpException('You are not allowed to edit this post.');
}

It's just an example, method doesn't have to be static, you can construct instance using $model.

You can just directly create method in Post model, but it's better to not pollute model with such logic.

3) Another alternative that I can advise is to restrict scope initially to current user when finding model:

use yii\web\NotFoundHttpException;

/**
 * @param integer $id
 * @return Post
 * @throws NotFoundHttpException
 */
protected function findModel($id)
{
    $model = Post::find(['id'=> $id, 'user_id' => Yii::$app->user->id])->one();
    if ($model) {
        return $model;
    } else {
        throw new NotFoundHttpException('This post does not exist.');
    }
}

This can be improved for site administrators:

use yii\web\NotFoundHttpException;

/**
 * @param integer $id
 * @return Post
 * @throws NotFoundHttpException
 */
protected function findModel($id)
{
    $query = Post::find()->where(['id' => $id]);
    if (!Yii::$app->user->is_admin) { // replace with your own check
        $query->andWhere(['user_id' => Yii::$app->user->id]);
    }
    $model = $query->one();
    if ($model) {
        return $model;
    } else {
        throw new NotFoundHttpException('This post does not exist.');
    }
}

Then you only write:

public function actionUpdate($id)
{
    $model = $this->findModel($id);
    ...
}

That way in both cases (model not found and not allowed for editing by current user), 404 Not Found exception will be raised. From other side, nothing is wrong with that, because technically for this user this model does not exist (since he is not author of it).