CakePHP:基于belongsTo关系授权操作


CakePHP: authorizing actions based on belongsTo relationships

让我们保持简单。

一个项目只有两种模式:

  1. 用户(hasMany项目)
  2. 项目(属于用户)

用户只能对自己拥有的项目执行操作。没有别人的。

我知道如何手动检查登录用户是谁,以及他/她是否拥有特定项目,但有更好、更全球化的方法吗?我正在寻找一种更具D.R.Y.的方式,它不需要在多个操作中重复相同的验证。例如,有没有像maybe…这样的配置设置。。。

Configure::write('Enforce_belongs_to', true);

或者可能是Auth组件上的设置/选项。

也许这太疯狂了,但我想我会问的。

在Nunser的回答中,这里有一个行为的一般概念。然后可以将其附加到适用的模型中。

    class StrongBelongBehavior extends ModelBehavior
    {
       public function beforeFind(  Model $Model, $query = array() ) { 
         $query['conditions'] = array_merge( (array)$query['conditions'], array( $Model->alias.'.user_id' => CakeSession::read("Auth.User.id" ) );
         return $query;
       }
       public function beforeSave( Model $Model ) {
        $projectId = Hash::get( $Model->data, 'Poject.id' );
        if( $projectId ) {
           $Model->loadModel('UserProject'); // UserProject is a custom model
           $canEdit =  $Model->UserProject->projectIDExists( $projectId ); // returns true if projectId belongs to the current user
           if ( ! $canEdit  ) {
             return false;
           }
        }
       return true;
       }
     }

我不确定我所回答的是否是最好的最干燥的方法,但这是我能想到的最简单的方法。

在Project模型中,创建一个函数,该函数返回与用户关联的项目ID数组。

class Project extends AppModel {
    public function getByUserId($userId) {
        $projectsArray = array();
        if ($userId != "valid")
           //do all the checks, if it's not null, numeric, if the id exists, etc
        $projects = $this->Project->find('all', array('conditions'=>
                        array('user_id'=>$userId)));
        if (!empty($projects)) {
            foreach($projects as $i => $project)
                $projectsArray[] = $project['Project']['id'];
        }
        return $projectsArray;
    }
}

你在评论中提到了find('first'),但我假设你想要的是与用户相关的所有项目,而不仅仅是第一个。如果不是,这只是对该函数的简单修改。此外,我只是在获取ID,但您可能想要一个$id=>$name_project数组,由您决定。

现在,我不知道你说的"只允许执行操作"是什么意思,这只是受限制的编辑吗?或者,如果项目不是用户的,列表或视图应该受到限制,甚至不向用户显示?

对于第一种情况,限制编辑,修改beforeSave

public function beforeSave($options = array()) {
    if(!$this->id && !isset($this->data[$this->alias][$this->primaryKey])) {
        //INSERT
        //not doing anything
    } else {
        //UPDATE
        //check if project inside allowed projects array
        $allowed = $this->getByUserId(CakeSession::read("Auth.User.id"));
        if (!in_array($this->id, $allowed))
           return false; //or throw error and catch it in the controller
    }
     return true;
}

该代码未经测试,但通常情况下,您会在更新记录之前阻止编辑非"用户"的项目。我想插入新项目对每个人来说都是免费的。根据这篇文章,除了saveAll之外的所有保存函数都会首先通过此过滤器,因此您需要覆盖saveAll函数,并添加类似于beforeSave中的验证(如答案所述)。

对于第二部分,过滤结果,使用户甚至不知道还有其他项目而不是他/她的项目,请更改beforeFind。文档中谈到了根据用户角色限制结果,所以我想我们走在了正确的轨道上。

public function beforeFind($queryData) {
    //force the condition
    $allowed = $this->getByUserId(CakeSession::read("Auth.User.id"));
    $queryData['conditions'][$this->alias.'.user_id'] = $allowed;
    return $queryData;
}

由于$allowed数组只有id值,所以它将像IN子句一样工作,但如果您更改该数组结构,请确保相应地修改这些函数。

就这样。我在考虑这里更基本的案例,编辑、查看、删除。。。Ups,删除。。。还更改了beforeDelete函数,以避免任何想要删除他人属性的恶意用户。逻辑保持不变(检查项目id是否在允许的数组中,如果不在,则返回false或抛出error),所以我不会在这里添加该函数的示例。但这是最基本的东西。如果出于某种原因,您希望在控制器中具有允许的项目,请调用beforeFilter中的getByUserId函数并在那里处理该id数组。您甚至可以将其存储在会话中,但在添加或删除项目时,必须记住维护该会话。

如果您想要一个可以查看和编辑所有内容的超级管理员,只需在getByUserId中添加一个检查用户角色的条件,如果是管理员,则返回所有项目。

此外,请记住:也许Project有很多。。。子项目(没有太多的想象力),因此,与项目相关的用户可以添加子项目,但与以前一样的邪恶用户修改了子项目的隐藏project_id并对其进行编辑。在这种情况下,我建议您也在subproject中添加验证,以避免对与project相关的模型执行非他的操作。如果您已经安装了安全组件,并且表单可以直接执行编辑和删除操作,那么这只是一件小事,因为安全组件使用得当可以避免篡改表单。但考虑一下是否也需要验证"子项目"实例。

正如Ayo Akinyemi所提到的,你可以把这一切作为一种行为。我个人还没有这样做,但它符合要求,这里修改的所有回调都是你在行为中修改的。您必须封装逻辑、列名(需要是可变的,而不是设置硬编码的,如user_id)等,但它可以在您拥有的任何其他蛋糕项目中重复使用。类似StrongBelongBehaviorMoreDRYBehavior的东西。如果你这样做了,请分享:)

我不确定Auth组件是否有某种方式可以做你想要的事情,但我想这将是最好的选择。在一些人启发我之前(我还没有对这个问题进行过多的调查),这是我会使用的解决方案。