Ответ 1
Встроенная система ACL CakePHP действительно мощная, но плохо документированная с точки зрения фактических деталей реализации. Система, которую мы использовали с некоторыми успехами в ряде проектов на основе CakePHP, выглядит следующим образом.
Это модификация некоторых систем доступа на уровне группы, которые были задокументированы в другом месте. Наша система нацелена на то, чтобы иметь простую систему, в которой пользователи авторизованы на уровне группы, но у них могут быть определенные дополнительные права на элементы, созданные ими или на основе каждого пользователя. Мы хотели избежать необходимости создавать конкретную запись для каждого пользователя (или, более конкретно, для каждой ARO) в таблице aros_acos
.
У нас есть таблица "Пользователи" и таблица "Роли".
Пользователи
user_id, user_name, role_id
Роли
id, role_name
Создайте дерево ARO для каждой роли (обычно у нас есть 4 роли - Unauthorized Guest (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'
а затем создайте ACOs для всех контроллеров в этом ROOT:
cake acl create aco 'ROOT' 'MyController' ... etc ...
До сих пор так нормально. Мы добавляем дополнительное поле в таблицу aros_acos с именем _editown
, которую мы можем использовать в качестве дополнительного действия в компоненте action 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(
). Там также существует метод 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;
}