CakePHP ACL 数据库设置:ARO/ACO结构?
-
09-06-2019 - |
解决方案
CakePHP 的内置 ACL 系统确实很强大,但在实际实现细节方面却很少有文档记录。我们在许多基于 CakePHP 的项目中使用的系统取得了一些成功,如下所示。
它是对一些组级访问系统的修改,这些系统已 记录在别处. 。我们系统的目标是建立一个简单的系统,其中用户在组级别上获得授权,但他们可以对他们创建的项目或每个用户拥有特定的附加权限。我们希望避免为每个用户(或者更具体地说,为每个 ARO)创建一个特定的条目 aros_acos
桌子。
我们有一个用户表和一个角色表。
用户
user_id, user_name, role_id
角色
id, role_name
为每个角色创建 ARO 树(我们通常有 4 个角色 - 未经授权的访客 (id 1)、授权用户 (id 2)、站点管理员 (id 3) 和管理员 (id 4)):
cake acl create aro / Role.1
cake acl create aro 1 Role.2 ... etc ...
之后,您必须使用 SQL 或 phpMyAdmin 或类似的工具来为所有这些添加别名,因为 cake 命令行工具不会这样做。我们对所有角色都使用“Role-{id}”和“User-{id}”。
然后我们创建一个 ROOT ACO -
cake acl create aco / 'ROOT'
然后为该 ROOT 下的所有控制器创建 ACO:
cake acl create aco 'ROOT' 'MyController' ... etc ...
到目前为止还算正常。我们在 aros_acos 表中添加一个名为 _editown
我们可以将其用作 ACL 组件的 actionMap 中的附加操作。
CREATE TABLE IF NOT EXISTS `aros_acos` (
`id` int(11) NOT NULL auto_increment,
`aro_id` int(11) default NULL,
`aco_id` int(11) default NULL,
`_create` int(11) NOT NULL default '0',
`_read` int(11) NOT NULL default '0',
`_update` int(11) NOT NULL default '0',
`_delete` int(11) NOT NULL default '0',
`_editown` int(11) NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `acl` (`aro_id`,`aco_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
然后,我们可以设置 Auth 组件以使用“crud”方法,该方法根据 AclComponent::check() 验证请求的控制器/操作。在 app_controller 中,我们有以下内容:
private function setupAuth() {
if(isset($this->Auth)) {
....
$this->Auth->authorize = 'crud';
$this->Auth->actionMap = array( 'index' => 'read',
'add' => 'create',
'edit' => 'update'
'editMine' => 'editown',
'view' => 'read'
... etc ...
);
... etc ...
}
}
同样,这是相当标准的 CakePHP 内容。然后,我们在 AppController 中添加一个 checkAccess 方法,该方法添加组级别的内容来检查是否检查组 ARO 或用户 ARO 的访问权限:
private function checkAccess() {
if(!$user = $this->Auth->user()) {
$role_alias = 'Role-1';
$user_alias = null;
} else {
$role_alias = 'Role-' . $user['User']['role_id'];
$user_alias = 'User-' . $user['User']['id'];
}
// do we have an aro for this user?
if($user_alias && ($user_aro = $this->User->Aro->findByAlias($user_alias))) {
$aro_alias = $user_alias;
} else {
$aro_alias = $role_alias;
}
if ('editown' == $this->Auth->actionMap[$this->action]) {
if($this->Acl->check($aro_alias, $this->name, 'editown') and $this->isMine()) {
$this->Auth->allow();
} else {
$this->Auth->authorize = 'controller';
$this->Auth->deny('*');
}
} else {
// check this user-level aro for access
if($this->Acl->check($aro_alias, $this->name, $this->Auth->actionMap[$this->action])) {
$this->Auth->allow();
} else {
$this->Auth->authorize = 'controller';
$this->Auth->deny('*');
}
}
}
这 setupAuth()
和 checkAccess()
方法被调用在 AppController
的 beforeFilter(
) 打回来。有一个 isMine
AppControler 中的方法(见下文)也只检查所请求项目的 user_id 是否与当前经过身份验证的用户相同。为了清楚起见,我省略了这一点。
这就是全部内容了。然后,您可以允许/拒绝特定组访问特定的 acos -
cake acl grant 'Role-2' 'MyController' 'read'
cake acl grant 'Role-2' 'MyController' 'editown'
cake acl deny 'Role-2' 'MyController' 'update'
cake acl deny 'Role-2' 'MyController' 'delete'
我相信你明白了。
不管怎样,这个答案比我预期的要长得多,而且可能几乎没有意义,但我希望它对你有一些帮助......
- 编辑 -
根据要求,这是经过编辑的(纯粹是为了清楚起见 - 我们的样板代码中有很多东西在这里毫无意义) isMine()
我们在 AppController 中拥有的方法。我也删除了很多错误检查的内容,但这就是它的本质:
function isMine($model=null, $id=null, $usermodel='User', $foreignkey='user_id') {
if(empty($model)) {
// default model is first item in $this->uses array
$model = $this->uses[0];
}
if(empty($id)) {
if(!empty($this->passedArgs['id'])) {
$id = $this->passedArgs['id'];
} elseif(!empty($this->passedArgs[0])) {
$id = $this->passedArgs[0];
}
}
if(is_array($id)) {
foreach($id as $i) {
if(!$this->_isMine($model, $i, $usermodel, $foreignkey)) {
return false;
}
}
return true;
}
return $this->_isMine($model, $id, $usermodel, $foreignkey);
}
function _isMine($model, $id, $usermodel='User', $foreignkey='user_id') {
$user = Configure::read('curr.loggedinuser'); // this is set in the UsersController on successful login
if(isset($this->$model)) {
$model = $this->$model;
} else {
$model = ClassRegistry::init($model);
}
//read model
if(!($record = $model->read(null, $id))) {
return false;
}
//get foreign key
if($usermodel == $model->alias) {
if($record[$model->alias][$model->primaryKey] == $user['User']['id']) {
return true;
}
} elseif($record[$model->alias][$foreignkey] == $user['User']['id']) {
return true;
}
return false;
}