Встроенная система ACL CakePHP действительно мощная, но плохо документированная с точки зрения фактических деталей реализации. Система, которую мы с некоторым успехом использовали в ряде проектов на основе CakePHP, выглядит следующим образом.
Это модификация некоторых систем доступа группового уровня, которые были задокументированы в другом месте . Цели нашей системы состоят в том, чтобы иметь простую систему, в которой пользователи авторизуются на уровне группы, но они могут иметь определенные дополнительные права на элементы, созданные ими или для каждого пользователя. Мы хотели избежать создания отдельной записи для каждого пользователя (или, более конкретно, для каждого ARO) в таблице aros_acos
.
У нас есть таблица Users и таблица Roles.
Пользователи
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 или аналогичные, чтобы добавить псевдонимы для всех этих элементов, поскольку инструмент командной строки для торта этого не делает. Мы используем «Role- {id}» и «User- {id}» для всех наших.
Затем мы создаем ROOT ACO -
cake acl create aco / 'ROOT'
и затем создайте ACO для всех контроллеров под этим ROOT:
cake acl create aco 'ROOT' 'MyController' ... etc ...
Пока все нормально. Мы добавляем дополнительное поле в таблицу aros_acos с именем _editown
, которое мы можем использовать в качестве дополнительного действия в actionMap компонента ACL.
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. Затем у нас есть метод checkAccess в AppController, который добавляет материал на уровне группы, чтобы проверить, проверять ли групповой 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(
). В AppControler также есть метод isMine
(см. Ниже), который просто проверяет, совпадает ли 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;
}