User account management, roles, permissions, authentication PHP and MySQL -- Part 5


This is part 5 of a series on how to create a user accounts management system in PHP. You can find the other parts here: part1, part2part 3 and part 4.

We finished up managing administrative user accounts in the last section as well as roles. In this part, we will go through creating permissions and assigning and un-assigning the permissions to user roles.

Assigning permissions to roles

As I said in the first part of this series, roles are related to permissions in a Many-To-Many relationship. A role can have many permissions and a permission can belong to many roles.

We have already created the roles table. Now we will create a permissions table to hold permissions and a third table called permission_role to hold the information about the relationship between the roles and permissions table.

ad

 

 

Create the two tables to have the following properties:

permissions table:

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

permission_role table:

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`)
)

On the permission_role table, the role_id references the role id on the roles table while the permission_id references the permission id column on the permissions table. To assign a permission to a role, we do that by simply inserting a record of that permission_id against the role_id on the permission_role table and the relationship is established. This means if we want to un-assign that permission from that role, we just delete the record of that role_id against that permission_id on this permission_role table.

The last two lines of the SQL query above are constraints that ensure that when a particular role or permission is deleted, all entries in the permission_role table having that permission's id or that role id will also be automatically deleted by the database. We do this because we don't want the permission_role table keeping relationship information about a role or a permission that no longer exists.

You can also set these constraints manually using PHPMyAdmin: you can do it on the interface simply by selecting the permission_role table and going to Relational view > Structure tab and just filling in the values. If you still can't do this, leave a comment below and I'll try and help you out.

Now the relationship is set. 

Let's create a page to assign permissions to a role. On our roleList.php page, we list the various roles with a 'permissions' button alongside each. Clicking on this link will take us to a page called assignPermissions.php. Let's create that file now inside admin/roles folder.

assignPermissions.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>

Clicking on the 'permissions' button of a role leads you to this page. But right now if you click on it and come to this assignPermissions.php page, there is an error message saying getAllPermissions() functions is undefined. Before we add this method, let's go over how we actually implement this assigning and un-assiging of permission in our PHP code.

vli_ad

When you click on 'permissions' button of a role, you are taken to the assignPermissions.php page with the id of that role. But before displaying the assign permissions page, we use the role id to fetch all permissions that have already been assigned to that role from the database. And then we also bring out all the permissions available in the permissions table. Now we have two arrays of permissions: the ones that have been assigned to a role and all the permissions available in our database. One former is a subset of the latter.

Assigning a permission to a role means adding that permission from the overall list of permissions to the array of permission belonging to that role and saving the whole of this info in the permission_role table. Un-assigning a permission from a role means removing that particular permission from the list of permissions that belong to that role.

Our logic is to loop through an array of all the available permissions from the database, then for each of their id, we determine if that id is already in the array of ids for the role's permissions. if it exists, it means the role already has that permission so we display it alongside a checked checkbox. If it doesn't exist, we display it alongside an unchecked checkbox. 

After everything has been displayed, clicking on a checked checkbox means un-assigning the permission while clicking on an unchecked checkbox means assigning the permission to the role. After all the checking and unchecking is done, the user then clicks on the 'Save Permissions' button under the table to save all the permissions that have been checked to that role.

Let's add all these functions to the roleLogic.php file. They are:

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);
  }

Putting the permissions to work

At this point, we can determine what a user's role is and since the role is related to permissions, we can therefore also know their permissions.

Now we want to put these permissions to work: that is, to ensure that an admin user is permitted to perform only those actions for which he has the permissions. We will achieve this using middleware functions. A middleware basically is a piece of code or a function that gets executed before an action is performed. Typically, this middleware function may modify the behavior of the action or perform some checks that might end up stopping the action altogether depending on the results of the check.

For instance, a user may have the permissions create-post, update-post, and delete-post. If they are logged in and they try to publish a post, our middleware function first checks to see if this user has the publish-post permission. If they have this permission, our middleware function will return true and the post will be published. If they lack the publish-post permission, our middleware function will redirect them back with a message saying that they do not have the permission to publish the post. 

vli_ad

We will put all our middleware functions inside a single file called middleware.php. Create it now in the folder admin and paste this code into it:

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;
    }
  }
?>
ad

The first if statement checks if user is logged in. If the user is not logged in, they will be redirected to the homepage. The second if statement checks whether user is logged in and whether he/she has a role (is admin). If the user is found to be logged in and has a role, they will access the page, otherwise the will be redirected back to the homepage.

In every file where you want to restrict non-admin users from accessing, you should just include this middleware.php file into that file. So all our admin files in which we do not want normal users to access, we will include this file in them. So open all the files in the two folders inside admin folder namely: users, roles. In each of the files, add the following line just below the include for config.php. 

Like so:

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

And that will redirect any non-admin user attempting to visit the page.

Also in this middleware.php file, we are using PHP's in_array() to check whether the permission we are testing is in the array of that user's permissions. (When an admin user logs in, we put all their permission in a session variable array called $_SESSION['userPermissions'].) If the current permission is in the array of the user's permissions, it means that that user has that permission and therefore the function returns true, otherwise it returns false.

Now if you want to check if a user has a permission, say a publish-post permission you have to do is call the method canPublishPost() like this:

<?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 ?>

Also as a middleware, before we update a post, we will first call the canUpdatePost() middleware function. If the function checks and sees that the user does not have the update-post permission, it will return false and we can then redirect them to the homepage with a message saying he is not permitted to update the post. Like this:

// 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
  }
}

Same thing for publishing/unpublishing posts:

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

}

Now we are left with the last part of this tutorial which is updating the user profile and also giving registered users the ability to delete their own accounts.

Thanks for following. If you have anything to say, please drop it in the comments.

Awa Melvine

Next Part: Edit user Profile

ad

Related posts


Comments