Roles

 

Control de acceso basado en funciones en PHP

Por Martin Psinas 12 de marzo de 2012

Hay varios enfoques diferentes cuando se trata de administrar los permisos de los usuarios, y cada uno tiene sus propios positivos y negativos. Por ejemplo, usar mascarilla de bits es extremadamente eficiente, pero también le limita a 32 o 64 permisos (el número de bits en un entero de 32 o 64 bits). Otro enfoque es utilizar una lista de control de acceso (ACL), sin embargo, sólo puede asignar permisos a objetos en lugar de operaciones específicas o significativas.

En este artículo discutiré mi enfoque favorito personal: control de acceso basado en roles (RBAC). RBAC es un modelo en el que se crean funciones para varias funciones de trabajo y, a continuación, los permisos para realizar determinadas operaciones se vinculan a funciones. A un usuario se le puede asignar una o varias funciones que restringen el acceso del sistema a los permisos para los que han sido autorizados.

La desventaja de usar RBAC es que si no se gestiona correctamente, sus roles y permisos pueden convertirse fácilmente en un caótico desorden. En un entorno de negocios que cambia rápidamente, puede ser un trabajo en sí mismo para realizar un seguimiento de la asignación de funciones adecuadas a los nuevos empleados y la eliminación de ellos en el momento oportuno de los antiguos empleados o las posiciones de cambio. Además, la identificación de nuevos roles para funciones de trabajo únicas y la revisión o eliminación requiere una revisión periódica. Si no gestiona adecuadamente sus funciones puede abrir la puerta a muchos riesgos de seguridad.

Voy a empezar por discutir las tablas de base de datos necesarios, a continuación, voy a crear dos archivos de clases: ( Role.php ) que llevará a cabo una serie de tareas específicas a los roles, y ( PrivilegedUser.php ) que ampliarán su clase de usuario existente. Por último voy a caminar a través de algunos ejemplos de cómo puede integrar el código en su aplicación. Gestión de roles y gestión de usuarios van de la mano, y por lo tanto en este artículo voy a asumir que ya tiene algún tipo de sistema de autenticación de usuario en su lugar.

Base de datos

Se necesitan cuatro tablas para almacenar papel y el permiso de la información: los roles tabla almacena un nombre de identificación del papel y el papel, los permissions tabla almacena un ID de autorización y de la descripción, los role_perm tabla asocia los cuales pertenecen los permisos para que las funciones y los user_role tabla asocia qué funciones Se asignan a qué usuarios.

Utilizando este esquema, puede tener un número ilimitado de funciones y permisos y cada usuario puede asignar múltiples funciones.

Estos son los CREATE TABLE declaraciones de la base de datos:

 CREATE TABLE roles ( role_id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, role_name VARCHAR(50) NOT NULL, PRIMARY KEY (role_id) ); CREATE TABLE permissions ( perm_id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, perm_desc VARCHAR(50) NOT NULL, PRIMARY KEY (perm_id) ); CREATE TABLE role_perm ( role_id INTEGER UNSIGNED NOT NULL, perm_id INTEGER UNSIGNED NOT NULL, FOREIGN KEY (role_id) REFERENCES roles(role_id), FOREIGN KEY (perm_id) REFERENCES permissions(perm_id) ); CREATE TABLE user_role ( user_id INTEGER UNSIGNED NOT NULL, role_id INTEGER UNSIGNED NOT NULL, FOREIGN KEY (user_id) REFERENCES users(user_id), FOREIGN KEY (role_id) REFERENCES roles(role_id) );

Tenga en cuenta la mesa final, user_role , hace referencia a un users mesa que no he definido aquí. Esto supone que user_id es la clave principal de su users mesa.

No es necesario hacer ninguna modificación en su users tabla para almacenar la información de funciones como la información se almacena por separado en estas nuevas tablas. Contrariamente a algunos otros sistemas RBAC, un usuario aquí no está obligado a tener un rol por defecto; En su lugar, el usuario simplemente no tendrá privilegios hasta que se haya asignado específicamente un rol. Como alternativa, sería posible en el PrivilegedUser clase para detectar un papel vacío y responder con un papel sin privilegios por defecto cuando sea necesario, o se puede optar por escribir una secuencia de comandos SQL corto para copiar los ID de usuario e inicializar mediante la asignación de un papel sin privilegios por defecto.

clase de rol

El enfoque principal de la Role clase es devolver un objeto de función que se rellena con cada uno de los roles permisos correspondientes. Esto le permitirá comprobar fácilmente si un permiso está disponible sin tener que realizar consultas SQL redundantes con cada solicitud.

Utilice el siguiente código para crear Role.php :

 permissions = array(); } // return a role object with associated permissions public static function getRolePerms($role_id) { $role = new Role(); $sql = "SELECT t2.perm_desc FROM role_perm as t1 JOIN permissions as t2 ON t1.perm_id = t2.perm_id WHERE t1.role_id = :role_id"; $sth = $GLOBALS["DB"]->prepare($sql); $sth->execute(array(":role_id" => $role_id)); while($row = $sth->fetch(PDO::FETCH_ASSOC)) { $role->permissions[$row["perm_desc"]] = true; } return $role; } // check if a permission is set public function hasPerm($permission) { return isset($this->permissions[$permission]); } }

El getRolePerms() método crea un nuevo Role objeto basado en un ID de función específica y, a continuación, utiliza un JOIN cláusula de combinar la role_perm y perm_desc tablas. Para cada permiso asociado con la función dada, la descripción se almacena como la clave y su valor se establece en true. El hasPerm() método acepta una descripción permiso y devuelve el valor basado en el objeto actual.

Clase de usuario privilegiado

Mediante la creación de una nueva clase que amplíe su clase de usuario existente, puede reutilizar la lógica de código existente para administrar usuarios y, a continuación, agregar algunos métodos adicionales a los que están orientados específicamente a trabajar con privilegios.

Utilice el siguiente código para crear el archivo PrivilegedUser.php :

 prepare($sql); $sth->execute(array(":username" => $username)); $result = $sth->fetchAll(); if (!empty($result)) { $privUser = new PrivilegedUser(); $privUser->user_id = $result[0]["user_id"]; $privUser->username = $username; $privUser->password = $result[0]["password"]; $privUser->email_addr = $result[0]["email_addr"]; $privUser->initRoles(); return $privUser; } else { return false; } } // populate roles with their associated permissions protected function initRoles() { $this->roles = array(); $sql = "SELECT t1.role_id, t2.role_name FROM user_role as t1 JOIN roles as t2 ON t1.role_id = t2.role_id WHERE t1.user_id = :user_id"; $sth = $GLOBALS["DB"]->prepare($sql); $sth->execute(array(":user_id" => $this->user_id)); while($row = $sth->fetch(PDO::FETCH_ASSOC)) { $this->roles[$row["role_name"]] = Role::getRolePerms($row["role_id"]); } } // check if user has a specific privilege public function hasPrivilege($perm) { foreach ($this->roles as $role) { if ($role->hasPerm($perm)) { return true; } } return false; } }

El primer método, getByUsername() , devuelve un objeto que contenga información acerca de un usuario específico. Un método casi idéntica a esto probablemente ya existen en su clase de usuario, pero hay que anularla aquí, así que el PrivilegedUser métodos 's se pueden llamar con el objeto apropiado. Si intenta invocar un PrivilegedUser método en un User objeto, obtendrá un error que indica que el método no existe.

El segundo método, initRoles() , utiliza un JOIN para combinar los user_role y roles mesas para recoger los roles asociados con el ID del usuario actual. Cada papel que luego se llena con sus permisos correspondientes con una llamada a la Role método de la clase creada previamente, Role::getRolePerms() .

El último método, hasPrivilege() , acepta una descripción permiso y devuelve verdadero del usuario tiene el permiso o falso en caso contrario.

Con las dos clases anteriores en su lugar, comprobar si un usuario tiene un privilegio específico es tan simple como sigue:

 hasPrivilege("thisPermission")) { // do something }

Aquí el nombre de usuario se almacena en la sesión activa y una nueva PrivilegedUser se crea objeto para ese usuario en la que el hasPrivilege() puede ser llamado método. Dependiendo de la información en su base de datos, la salida del objeto se verá similar a lo siguiente:

  Objeto (PrivilegedUser) # 3 (2) {

   [ "Roles": "PrivilegedUser": privado] =>

   Array (1) {

     [ "Admin"] =>

     Objeto (Rol) # 5 (1) {

       [ "Permissions": protected] =>

       Matriz (4) {

         [ "AddUser"] => bool (true)

         [ "EditUser"] => bool (true)

         [ "DeleteUser"] => bool (true)

         [ "EditRoles"] => bool (true)

       }

     }

   }

   [ "Campos": "Usuario": privado] =>

   Matriz (4) {

     [ "User_id"] => cadena (1) "2"

     [ "Username"] => cadena (7) "mpsinas"

     [ "Password"] => bool (false)

     [ "Email_addr"] => cadena (0) ""

   }

 }

Mantener las cosas organizadas

Uno de los muchos beneficios de usar un enfoque OOP con RBAC es que le permite separar lógica de código y validación de tareas específicas de objetos. Por ejemplo, se podría añadir los siguientes métodos para su Role de clase para ayudar a manejar las operaciones específicas de conducta tales como la inserción de nuevas funciones, eliminando funciones y así sucesivamente:

 // insert a new role public static function insertRole($role_name) { $sql = "INSERT INTO roles (role_name) VALUES (:role_name)"; $sth = $GLOBALS["DB"]->prepare($sql); return $sth->execute(array(":role_name" => $role_name)); } // insert array of roles for specified user id public static function insertUserRoles($user_id, $roles) { $sql = "INSERT INTO user_role (user_id, role_id) VALUES (:user_id, :role_id)"; $sth = $GLOBALS["DB"]->prepare($sql); $sth->bindParam(":user_id", $user_id, PDO::PARAM_STR); $sth->bindParam(":role_id", $role_id, PDO::PARAM_INT); foreach ($roles as $role_id) { $sth->execute(); } return true; } // delete array of roles, and all associations public static function deleteRoles($roles) { $sql = "DELETE t1, t2, t3 FROM roles as t1 JOIN user_role as t2 on t1.role_id = t2.role_id JOIN role_perm as t3 on t1.role_id = t3.role_id WHERE t1.role_id = :role_id"; $sth = $GLOBALS["DB"]->prepare($sql); $sth->bindParam(":role_id", $role_id, PDO::PARAM_INT); foreach ($roles as $role_id) { $sth->execute(); } return true; } // delete ALL roles for specified user id public static function deleteUserRoles($user_id) { $sql = "DELETE FROM user_role WHERE user_id = :user_id"; $sth = $GLOBALS["DB"]->prepare($sql); return $sth->execute(array(":user_id" => $user_id)); }

Del mismo modo, se podría añadir en su PrivilegedUser clase con métodos similares:

 // check if a user has a specific role public function hasRole($role_name) { return isset($this->roles[$role_name]); } // insert a new role permission association public static function insertPerm($role_id, $perm_id) { $sql = "INSERT INTO role_perm (role_id, perm_id) VALUES (:role_id, :perm_id)"; $sth = $GLOBALS["DB"]->prepare($sql); return $sth->execute(array(":role_id" => $role_id, ":perm_id" => $perm_id)); } // delete ALL role permissions public static function deletePerms() { $sql = "TRUNCATE role_perm"; $sth = $GLOBALS["DB"]->prepare($sql); return $sth->execute(); }

Debido a que los permisos se vinculan directamente a la lógica de código subyacente de la aplicación, los nuevos permisos deben insertarse manualmente o eliminarse de la base de datos según sea necesario. Las funciones, por otro lado, pueden crearse, modificarse o eliminarse fácilmente a través de una interfaz de administración.

 

Cuantos más papeles y permisos tengan las cosas más difíciles será gestionar; Mantener las listas mínimas es importante, pero a veces el contrario es inevitable. Sólo puedo aconsejar que use su mejor juicio y trate de no dejarse llevar.