Configuración de la base de datos ACL de CakePHP:¿Estructura ARO/ACO?
-
09-06-2019 - |
Pregunta
Estoy luchando por implementar ACL en CakePHP.Después de leer la documentación en el manual de pastel Además de varios otros tutoriales, publicaciones de blogs, etc., encontré el excelente tutorial de Aran Johnson que me ha ayudado a llenar muchos de los vacíos.Sus ejemplos parecen entrar en conflicto con otros que he visto en algunos lugares, específicamente en la estructura de árbol ARO que utiliza.
En su ejemplos Sus grupos de usuarios se configuran como un árbol en cascada, con el tipo de usuario más general en la parte superior del árbol y sus hijos ramificándose para cada tipo de acceso más restringido.En otros lugares, normalmente he visto cada tipo de usuario como hijo del mismo tipo de usuario genérico.
¿Cómo configuras tus ARO y ACO en CakePHP?¡Se agradecen todos y cada uno de los consejos!
Solución
El sistema ACL integrado de CakePHP es realmente poderoso, pero está mal documentado en términos de detalles de implementación reales.Un sistema que hemos utilizado con cierto éxito en varios proyectos basados en CakePHP es el siguiente.
Es una modificación de algunos sistemas de acceso a nivel de grupo que han sido documentado en otro lugar.El objetivo de nuestro sistema es tener un sistema simple en el que los usuarios estén autorizados a nivel de grupo, pero puedan tener derechos adicionales específicos sobre elementos creados por ellos o por usuario.Queríamos evitar tener que crear una entrada específica para cada usuario (o, más específicamente, para cada ARO) en el aros_acos
mesa.
Tenemos una tabla de Usuarios y una tabla de Roles.
Usuarios
user_id, user_name, role_id
Roles
id, role_name
Cree el árbol ARO para cada rol (generalmente tenemos 4 roles: Invitado no autorizado (id 1), Usuario autorizado (id 2), Moderador del sitio (id 3) y Administrador (id 4)):
cake acl create aro / Role.1
cake acl create aro 1 Role.2 ... etc ...
Después de esto, debes usar SQL o phpMyAdmin o similar para agregar alias para todos estos, ya que la herramienta de línea de comando cake no lo hace.Usamos 'Role-{id}' y 'User-{id}' para todos los nuestros.
Luego creamos una ACO RAÍZ -
cake acl create aco / 'ROOT'
y luego cree ACO para todos los controladores bajo este ROOT:
cake acl create aco 'ROOT' 'MyController' ... etc ...
Hasta ahora todo normal.Agregamos un campo adicional en la tabla aros_acos llamado _editown
que podemos usar como acción adicional en el actionMap del componente 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;
Luego podemos configurar el componente Auth para usar el método 'crud', que valida el controlador/acción solicitado contra un AclComponent::check().En app_controller tenemos algo como:
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 ...
}
}
Nuevamente, esto es algo bastante estándar de CakePHP.Luego tenemos un método checkAccess en AppController que agrega elementos a nivel de grupo para verificar si se debe verificar el acceso de una ARO de grupo o de un usuario:
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('*');
}
}
}
El setupAuth()
y checkAccess()
Los métodos se llaman en el AppController
's beforeFilter(
) llamar de vuelta.Hay un isMine
También hay un método en AppControler (ver más abajo) que simplemente verifica que el ID de usuario del elemento solicitado sea el mismo que el del usuario actualmente autenticado.He omitido esto para mayor claridad.
Eso es realmente todo lo que hay que hacer.Luego puede permitir/denegar el acceso de grupos particulares a acos específicos.
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'
Estoy seguro de que te haces una idea.
De todos modos, esta respuesta es mucho más larga de lo que pretendía y probablemente no tenga casi ningún sentido, pero espero que te ayude...
-- editar --
Según lo solicitado, aquí hay una versión editada (solo para mayor claridad; hay muchas cosas en nuestro código repetitivo que no tienen sentido aquí) isMine()
método que tenemos en nuestro AppController.También eliminé muchas cosas de verificación de errores, pero esta es la esencia:
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;
}