sql >> Base de Datos >  >> RDS >> Mysql

Administración de cuentas de usuario, roles, permisos, autenticación PHP y MySQL -- Parte 5

Esta es la parte 5 de una serie sobre cómo crear un sistema de administración de cuentas de usuario en PHP. Puede encontrar las otras partes aquí:parte 1, parte 2, parte 3 y parte 4.

Terminamos de administrar las cuentas de usuarios administrativos en la última sección, así como los roles. En esta parte, revisaremos la creación de permisos y la asignación y anulación de la asignación de permisos a las funciones de los usuarios.

Asignación de permisos a roles

Como dije en la primera parte de esta serie, los roles están relacionados con los permisos en una relación Muchos a Muchos. Un rol puede tener muchos permisos y un permiso puede pertenecer a muchos roles.

Ya hemos creado la tabla de roles. Ahora crearemos una tabla de permisos para guardar los permisos y una tercera tabla llamada allow_role para guardar la información sobre la relación entre las funciones y la tabla de permisos.

Cree las dos tablas para que tengan las siguientes propiedades:

tabla de permisos:

CREATE TABLE `permissions` (
 `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
 `name` varchar(255) NOT NULL UNIQUE KEY,
 `description` text NOT NULL
)

tabla de roles_permisos:

CREATE TABLE `permission_role` (
 `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
 `role_id` int(11) NOT NULL,
 `permission_id` int(11) NOT NULL,
 KEY `role_id` (`role_id`),
 KEY `permission_id` (`permission_id`),
 CONSTRAINT `permission_role_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
 CONSTRAINT `permission_role_ibfk_2` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`)
)

En la tabla permission_role, role_id hace referencia a la identificación de la función en la tabla de funciones, mientras que allow_id hace referencia a la columna de identificación del permiso en la tabla de permisos. Para asignar un permiso a un rol, lo hacemos simplemente insertando un registro de ese id_permiso contra el id_rol en la tabla rol_permiso y se establece la relación. Esto significa que si queremos anular la asignación de ese permiso de ese rol, simplemente eliminamos el registro de ese role_id contra ese allow_id en esta tabla de permisos_role.

Las dos últimas líneas de la consulta SQL anterior son restricciones que garantizan que cuando se elimine una función o un permiso en particular, la base de datos también eliminará automáticamente todas las entradas en la tabla de permisos_funciones que tengan la identificación de ese permiso o esa identificación de función. Hacemos esto porque no queremos que la tabla allow_role conserve información de relación sobre un rol o un permiso que ya no existe.

También puede establecer estas restricciones manualmente usando PHPMyAdmin:puede hacerlo en la interfaz simplemente seleccionando la tabla allow_role y yendo a Vista relacional> pestaña Estructura y simplemente completando los valores. Si aún no puede hacer esto, deje un comentario a continuación e intentaré ayudarlo.

Ahora la relación está establecida.

Vamos a crear una página para asignar permisos a un rol. En nuestra página roleList.php, enumeramos los distintos roles con un botón de "permisos" al lado de cada uno. Al hacer clic en este enlace, nos llevará a una página llamada AssignPermissions.php. Vamos a crear ese archivo ahora dentro de la carpeta admin/roles.

asignarPermisos.php:

<?php include('../../config.php') ?>
<?php include(ROOT_PATH . '/admin/roles/roleLogic.php') ?>
<?php
  $permissions = getAllPermissions();
  if (isset($_GET['assign_permissions'])) {
    $role_id = $_GET['assign_permissions']; // The ID of the role whose permissions we are changing
    $role_permissions = getRoleAllPermissions($role_id); // Getting all permissions belonging to role

    // array of permissions id belonging to the role
    $r_permissions_id = array_column($role_permissions, "id");
  }
?>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Admin Area - Assign permissions </title>
  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
  <!-- Custome styles -->
  <link rel="stylesheet" href="../../static/css/style.css">
</head>
<body>
  <?php include(INCLUDE_PATH . "/layouts/admin_navbar.php") ?>
  <div class="col-md-4 col-md-offset-4">
    <a href="roleList.php" class="btn btn-success">
      <span class="glyphicon glyphicon-chevron-left"></span>
      Roles
    </a>
    <hr>
    <h1 class="text-center">Assign permissions</h1>
    <br />
    <?php if (count($permissions) > 0): ?>
    <form action="assignPermissions.php" method="post">
      <table class="table table-bordered">
        <thead>
          <tr>
            <th>N</th>
            <th>Role name</th>
            <th class="text-center">Status</th>
          </tr>
        </thead>
        <tbody>
          <?php foreach ($permissions as $key => $value): ?>
            <tr class="text-center">
              <td><?php echo $key + 1; ?></td>
              <td><?php echo $value['name']; ?></td>
              <td>
                  <input type="hidden" name="role_id" value="<?php echo $role_id; ?>">
                  <!-- if current permission id is inside role's ids, then check it as already belonging to role -->
                  <?php if (in_array($value['id'], $r_permissions_id)): ?>
                    <input type="checkbox" name="permission[]" value="<?php echo $value['id'] ?>" checked>
                  <?php else: ?>
                    <input type="checkbox" name="permission[]" value="<?php echo $value['id'] ?>" >
                  <?php endif; ?>
              </td>
            </tr>
          <?php endforeach; ?>
          <tr>
            <td colspan="3">
              <button type="submit" name="save_permissions" class="btn btn-block btn-success">Save permissions</button>
            </td>
          </tr>
        </tbody>
      </table>
    </form>
    <?php else: ?>
      <h2 class="text-center">No permissions in database</h2>
    <?php endif; ?>
  </div>
  <?php include(INCLUDE_PATH . "/layouts/footer.php") ?>
</body>
</html>

Al hacer clic en el botón 'permisos' de un rol, se accede a esta página. Pero en este momento, si hace clic en él y llega a esta página de AssignPermissions.php, hay un mensaje de error que dice que las funciones getAllPermissions() no están definidas. Antes de agregar este método, repasemos cómo implementamos realmente esta asignación y cancelación de permisos en nuestro código PHP.

Cuando hace clic en el botón 'permisos' de una función, se lo lleva a la página de asignación de permisos.php con la identificación de esa función. Pero antes de mostrar la página de asignación de permisos, usamos la identificación del rol para obtener todos los permisos que ya se han asignado a ese rol de la base de datos. Y luego también sacamos todos los permisos disponibles en la tabla de permisos. Ahora tenemos dos conjuntos de permisos:los que se han asignado a un rol y todos los permisos disponibles en nuestra base de datos. Uno de los primeros es un subconjunto del último.

Asignar un permiso a un rol significa agregar ese permiso de la lista general de permisos a la matriz de permisos que pertenecen a ese rol y guardar toda esta información en la tabla allow_role. Desasignar un permiso de una función significa eliminar ese permiso en particular de la lista de permisos que pertenecen a esa función.

Nuestra lógica es recorrer una matriz de todos los permisos disponibles de la base de datos, luego, para cada una de sus identificaciones, determinamos si esa identificación ya está en la matriz de identificaciones para los permisos del rol. si existe, significa que el rol ya tiene ese permiso, por lo que lo mostramos junto con una casilla de verificación marcada. Si no existe, lo mostramos junto a una casilla de verificación sin marcar.

Después de que se haya mostrado todo, hacer clic en una casilla de verificación marcada significa anular la asignación del permiso, mientras que hacer clic en una casilla de verificación sin marcar significa asignar el permiso al rol. Después de realizar todas las comprobaciones y desmarcaciones, el usuario hace clic en el botón 'Guardar permisos' debajo de la tabla para guardar todos los permisos que se han verificado para ese rol.

Agreguemos todas estas funciones al archivo roleLogic.php. Ellos son:

roleLogic.php:

// ... other functions up here ...
  function getAllPermissions(){
    global $conn;
    $sql = "SELECT * FROM permissions";
    $permissions = getMultipleRecords($sql);
    return $permissions;
  }

  function getRoleAllPermissions($role_id){
    global $conn;
    $sql = "SELECT permissions.* FROM permissions
            JOIN permission_role
              ON permissions.id = permission_role.permission_id
            WHERE permission_role.role_id=?";
    $permissions = getMultipleRecords($sql, 'i', [$role_id]);
    return $permissions;
  }

  function saveRolePermissions($permission_ids, $role_id) {
    global $conn;
    $sql = "DELETE FROM permission_role WHERE role_id=?";
    $result = modifyRecord($sql, 'i', [$role_id]);

    if ($result) {
      foreach ($permission_ids as $id) {
        $sql_2 = "INSERT INTO permission_role SET role_id=?, permission_id=?";
        modifyRecord($sql_2, 'ii', [$role_id, $id]);
      }
    }

    $_SESSION['success_msg'] = "Permissions saved";
    header("location: roleList.php");
    exit(0);
  }

Poner los permisos a trabajar

En este punto, podemos determinar cuál es el rol de un usuario y, dado que el rol está relacionado con los permisos, también podemos conocer sus permisos.

Ahora queremos poner estos permisos a trabajar: es decir, garantizar que un usuario administrador pueda realizar solo aquellas acciones para las que tiene los permisos. Lo lograremos usando funciones de middleware. Un middleware básicamente es una pieza de código o una función que se ejecuta antes de que se realice una acción. Por lo general, esta función de middleware puede modificar el comportamiento de la acción o realizar algunas comprobaciones que pueden terminar deteniendo la acción por completo, según los resultados de la comprobación.

Por ejemplo, un usuario puede tener los permisos crear-publicar, actualizar-publicar y eliminar-publicar. Si están conectados e intentan publicar una publicación, nuestra función de middleware primero verifica si este usuario tiene el permiso de publicación. Si tienen este permiso, nuestra función de middleware devolverá verdadero y la publicación se publicará. Si carecen del permiso de publicación, nuestra función de middleware los redirigirá con un mensaje que indica que no tienen permiso para publicar la publicación.

Pondremos todas nuestras funciones de middleware dentro de un solo archivo llamado middleware.php. Créelo ahora en la carpeta admin y pegue este código en él:

middleware.php:

<?php

  // if user is NOT logged in, redirect them to login page
  if (!isset($_SESSION['user'])) {
    header("location: " . BASE_URL . "login.php");
  }
  // if user is logged in and this user is NOT an admin user, redirect them to landing page
  if (isset($_SESSION['user']) && is_null($_SESSION['user']['role'])) {
    header("location: " . BASE_URL);
  }
  // checks if logged in admin user can update post
  function canUpdatePost($post_id = null){
    global $conn;

    if(in_array('update-post', $_SESSION['userPermissions'])){
      if ($_SESSION['user']['role'] === "Author") { // author can update only posts that they themselves created
          $sql = "SELECT user_id FROM posts WHERE id=?";
          $post_result = getSingleRecord($sql, 'i', [$post_id]);
          $post_user_id = $post_result['user_id'];

          // if current user is the author of the post, then they can update the post
          if ($post_user_id === $user_id) {
            return true;
          } else { // if post is not created by this author
            return false;
          }
      } else { // if user is not author
        return true;
      }
    } else {
      return false;
    }
  }

  // accepts user id and post id and checks if user can publis/unpublish a post
  function canPublishPost() {
    if(in_array(['permission_name' => 'publish-post'], $_SESSION['userPermissions'])){
      // echo "<pre>"; print_r($_SESSION['userPermissions']); echo "</pre>"; die();
      return true;
    } else {
      return false;
    }
  }

  function canDeletePost() {
    if(in_array('delete-post', $_SESSION['userPermissions'])){
      return true;
    } else {
      return false;
    }
  }
  function canCreateUser() {
    if(in_array('create-user', $_SESSION['userPermissions'])){
      return true;
    } else {
      return false;
    }
  }
  function canUpdateUser() {
    if(in_array('update-user', $_SESSION['userPermissions'])){
      return true;
    } else {
      return false;
    }
  }
  function canDeleteUser() {
    if(in_array('delete-user', $_SESSION['userPermissions'])){
      return true;
    } else {
      return false;
    }
  }
  function canCreateRole($role_id) {
    if(in_array('create-role', $_SESSION['userPermissions'])){
      return true;
    } else {
      return false;
    }
  }
  function canUpdateRole($role_id) {
    if(in_array('update-role', $_SESSION['userPermissions'])){
      return true;
    } else {
      return false;
    }
  }
  function canDeleteRole($user_id, $post_id) {
    if(in_array('delete-role', $_SESSION['userPermissions'])){
      return true;
    } else {
      return false;
    }
  }
?>
"; morir(); devolver verdadero; } más { devuelve falso; } } function canDeletePost() { if(in_array('delete-post', $_SESSION['userPermissions'])){ return true; } más { devuelve falso; } } function canCreateUser() { if(in_array('create-user', $_SESSION['userPermissions'])){ return true; } más { devuelve falso; } } function canUpdateUser() { if(in_array('update-user', $_SESSION['userPermissions'])){ return true; } más { devuelve falso; } } function canDeleteUser() { if(in_array('delete-user', $_SESSION['userPermissions'])){ return true; } más { devuelve falso; } } function canCreateRole($role_id) { if(in_array('create-role', $_SESSION['userPermissions'])){ return true; } más { devuelve falso; } } function canUpdateRole($role_id) { if(in_array('update-role', $_SESSION['userPermissions'])){ return true; } más { devuelve falso; } } function canDeleteRole($user_id, $post_id) { if(in_array('delete-role', $_SESSION['userPermissions'])){ return true; } más { devuelve falso; } }?>

La primera instrucción if verifica si el usuario ha iniciado sesión. Si el usuario no ha iniciado sesión, será redirigido a la página de inicio. La segunda declaración if verifica si el usuario ha iniciado sesión y si tiene un rol (es administrador). Si se encuentra que el usuario ha iniciado sesión y tiene un rol, accederá a la página; de lo contrario, será redirigido a la página de inicio.

En cada archivo en el que desee restringir el acceso de usuarios que no sean administradores, solo debe incluir este archivo middleware.php en ese archivo. Entonces, todos nuestros archivos de administración en los que no queremos que accedan los usuarios normales, incluiremos este archivo en ellos. Así que abra todos los archivos en las dos carpetas dentro de la carpeta de administración, a saber:usuarios, roles. En cada uno de los archivos, agregue la siguiente línea justo debajo de la inclusión para config.php.

Así:

<?php include('../../config.php'); ?>
<?php require_once '../middleware.php'; ?>

Y eso redirigirá a cualquier usuario que no sea administrador que intente visitar la página.

También en este archivo middleware.php, estamos usando in_array() de PHP para verificar si el permiso que estamos probando está en la matriz de permisos de ese usuario. (Cuando un usuario administrador inicia sesión, colocamos todos sus permisos en una matriz de variables de sesión llamada $_SESSION['userPermissions'].) Si el permiso actual está en la matriz de permisos del usuario, significa que ese usuario tiene ese permiso y por lo tanto, la función devuelve verdadero, de lo contrario, devuelve falso.

Ahora, si desea verificar si un usuario tiene un permiso, digamos que un permiso de publicación y publicación que debe hacer es llamar al método canPublishPost() de esta manera:

<?php if (canPublishPost()): ?>
	<!-- User can publish post. Display publish post button -->
<?php else: ?>
	<!-- User cannot publish post. Do not display publish post button -->
<?php endif ?>

También como middleware, antes de actualizar una publicación, primero llamaremos a la función de middleware canUpdatePost(). Si la función verifica y ve que el usuario no tiene el permiso de actualización de publicación, devolverá falso y luego podemos redirigirlo a la página de inicio con un mensaje que dice que no tiene permiso para actualizar la publicación. Así:

// checks if logged in admin user can update post
function updatePost($post_values){
  global $conn;

  if(canUpdatePost($post_values['id']){
     // proceed to update post
  
  } else {
    // redirect back to homepage with message that says user is not permitted to update post
  }
}

Lo mismo para publicar/despublicar publicaciones:

function togglePublishPost($post_id)
{
	if (!canPublishPost($_SESSION['user']['id'])) {
		// redirect them back to dashboard with the message that they don't have the permission to publish post
	} 
    
    // proceed to publish post

}

Ahora nos queda la última parte de este tutorial que actualiza el perfil de usuario y también brinda a los usuarios registrados la posibilidad de eliminar sus propias cuentas.

Gracias por seguir. Si tiene algo que decir, déjelo en los comentarios.