根据特定客户端ID过滤对控制器操作的访问的最佳方法

时间:2012-11-06 21:46:17

标签: php cakephp authorization cakephp-2.2

使用CakePHP 2.2,我正在构建一个应用程序,其中每个客户端都拥有自己的“领域”数据,并且其他任何数据都不可见。例如,客户有他的一组用户,课程,承包商和工作。组在客户端之间共享,但它们无法对组执行操作。所有客户端都可以使用组将其分配给用户。因此,管理员(使用ACL)只能管理来自同一客户端ID的数据。

我的所有对象(当然除了组)都有client_id键。

现在,我知道一种方法可以完成这项工作,并且确实让它运转良好,但它似乎有点脏,我想知道是否有更好的方法。在项目的早期和CakePHP的新手,我渴望得到正确的。

这就是我现在正在做的事情:

1-用户登录。他的client_id根据用户表中的数据写入会话。

$user = $this->User->read(null, $this->Auth->user('id'));
$this->Session->write('User.client_id', $user['User']['client_id']);

2-在AppController中,我有一个受保护的函数,它将该会话ID与给定参数进行比较。

protected function clientCheck($client_id) {
    if ($this->Session->read('User.client_id') == $client_id) {
        return true;
    } else {
        $this->Session->setFlash(__('Invalid object or view.'));
        $this->redirect(array('controller' => 'user', 'action' => 'home'));
    }
}

3-我的不同索引操作(每个索引,每个相关控制器),我使用分页条件检查client_id。

public function index() {
    $this->User->recursive = 0;
    $this->paginate = array(
         'conditions' => array('User.client_id' => $this->Session->read('User.client_id'))
    );
    $this->set('users', $this->paginate());
}

4-在其他操作中,我在检查HTTP请求类型之前检查client_id。

$user = $this->User->read(null, $id);
$this->clientCheck($user['User']['client_id']);
$this->set('user', $user);

3 个答案:

答案 0 :(得分:1)

这个概念很好 - 它并不“脏”,而且与我处理这类情况的方式完全一样。

你刚刚获得了几行冗余代码。第一:

$this->Auth->user('id')

该方法实际上可以为登录用户获取任何字段,因此您可以执行以下操作:

$this->Auth->user('client_id')

所以你的两行:

$user = $this->User->read(null, $this->Auth->user('id'));
$this->Session->write('User.client_id', $user['User']['client_id']);

不需要。您无需重新读取用户,也无需向会话写任何内容 - 只需在您需要时直接从Auth获取client_id。

事实上,如果您阅读http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#accessing-the-logged-in-user,它甚至表示您可以从控制器的上下文之外获取它,使用静态方法,如:

AuthComponent::user('client_id')

虽然看起来你不需要那样。

答案 1 :(得分:1)

您还可以通过在模型中的beforeFind函数中放置一些东西,将client_id条件应用于Model的所有查找。

例如,在您的用户模型中,您可以执行以下操作:

function beforeFind( $queryData ) {

    // Automatically filter all finds by client_id of logged in user
    $queryData['conditions'][$this->alias . '.client_id'] = AuthComponent::user('client_id');

    return $queryData;
}

不确定AuthComponent :: user('client_id')是否在模型中有效,但您明白了。这将自动将此条件应用于模型中的每个查找。

您还可以使用模型中的beforeSave在新记录中自动为您设置client_id。

答案 2 :(得分:0)

我的答案可能是数据库引擎,因为我使用PostgreSQL。在我的项目中,我在mysql术语中为每个客户端使用了不同的模式,这些模式对于每个客户端都是独立的数据库。

在公共模式(公共数据库)中,我存储了所有客户端(在您的情况下没有client_id的对象)之间需要共享的所有数据,例如,变量常量,配置文件设置等。

在公司特定的模型中我定义

public $useDbConfig = 'company_data';

Controller/AppController.php beforeFilter()方法中,我使用此代码根据登录用户设置架构。

if ($this->Session->check('User.Company.id')) {
    App::uses('ConnectionManager', 'Model'); 
    $dataSource = ConnectionManager::getDataSource('company_data');
    $dataSource->config['schema'] = 
        'company_'.$this->Session->read('User.Company.id');
}

如您所见,我根据二手公司动态更新dataSource。这确实排除了company_id在任何查询中的任何参与,因为只有公司相关数据存储在该模式(数据库)中。这也增加了扩展项目的能力。

这种方法的缺点是,它会在结构更改中同步所有数据库结构时产生痛苦,但可以使用导出数据,删除所有数据库,使用新布局重新创建数据库并再次导入数据来完成。只需要确保导出包含列名称的完整插入数据。