Управление привилегиями пользователя на основе ролей

Существуют различные подходы к управлению доступом к информации, каждый из них имеет свои плюсы и минусы. Например, использование битовой маски очень эффективно, но ограничено 32 или 64 уровнями (количеством бит в формате целого числа). А подход на основе списка доступа (ACL) позволяет назначать уровни доступа объектам, но с операциями могут возникнуть сложности.

В данной статье описывается реализация доступа на основе ролей (role based access control  - RBAC). RBAC - модель системы разграничения прав доступа, в которой роли используются для выполнения различных задач, а разрешения на выполнение операций привязываются к ролям. Пользователь может получать одну или несколько ролей, которые ограничивают его полномочия на действия в системе.

Обратной стороной использования модели RBAC является то, что при плохом управлении ролями и разрешениями вся система доступа превращается в хаотичную массу. В быстро меняющейся бизнес среде может возникнуть необходимость в специальном операторе, который будет заниматься только отслеживанием назначений соответствующих ролей  новым сотрудникам и удалений полномочий у тех, кто покинул компанию или сменил позицию в структуре. Определение новых ролей для возникающих рабочих мест, ревизия и удаление полномочий требуют постоянного контроля. Ошибка в управлении полномочиями может сделать брешь в системе безопасности.

Мы обсудим создание необходимых таблиц баз данных. Затем создадим два файла с классами: Role.php - для выполнения определенных задач по управлению ролями, и PrivilegedUser.php - для расширения класса пользователя. Затем приведем несколько примеров, как код может быть интегрирован в ваше приложение. Управление ролями и пользователями весьма похожи, поэтому у вас появится система для идентификации пользователей.

 

База данных

Нужно 4 таблицы для хранения ролей и информации о разрешениях: таблица roles содержит ID и имя роли, таблица permissions хранит ID и описание разрешения, таблица role_perm содержит соответствие ролей и разрешений, а таблица user_role содержит соответсвие ролей и пользователей.

С помощью такой схемы можно иметь неограниченное количество ролей и разрешений, а каждому пользователю может быть назначено любое число ролей.

Вот запрос CREATE TABLE для создания структуры в базе данных:

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

Обратите внимание, что последняя таблица user_role ссылается на таблицу users, которая определяется в другом месте. В нашем случае мы предполагаем, что user_id является основным ключом в таблице users.

Никаких изменений для таблицы users не требуется, так как информация о правах доступа хранится отдельно. В отличие от других систем RBAC, в нашем случае пользователь не имеет никакой роли по умолчанию. Он просто не получает никаких полномочий.  В классе PrivilegedUser возможно сделать обнаружение пользователей без ролей и выдачу роли без полномочий в таком случае. Или также можно сделать запрос SQL для копирования ID пользователей и инициализации их нужной ролью по умолчанию.

 

Класс Role

Основная цель класса Role - вернуть объект роли, который содержит список соответствующих полномочий. Таким образом можно просто проверить наличие нужных полномочий без дополнительных запросов SQL.

Вот код класса, который содержится в файле Role.php:

<?php
class Role
{
    protected $permissions;

    protected function __construct() {
        $this->permissions = array();
    }

    // Возвращаем объект роли с соответствующими полномочиями
    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;
    }

    // Проверка установленных полномочий
    public function hasPerm($permission) {
        return isset($this->permissions[$permission]);
    }
}

Метод getRolePerms() создает новый объект Role на основе заданного ID роли. Затем использует выражение JOIN для комбинации таблиц role_perm и perm_desc. Каждое разрешение, связанное с ролью, хранится в виде описания в качестве ключа и значения true. Метод hasPerm() принимает описание разрешения и возвращает значение из текущего объекта.

 

Класс PrivilegedUser

С помощью нового класса, который дополняет существующий класс пользователя, можно использовать существующий код управления пользователями, дополняя его новыми методами для работы с привилегиями.

Код класса в файле PrivilegedUser.php:

<?php
class PrivilegedUser extends User
{
    private $roles;

    public function __construct() {
        parent::__construct();
    }

    // Изменяем метод класса User
    public static function getByUsername($username) {
        $sql = "SELECT * FROM users WHERE username = :username";
        $sth = $GLOBALS["DB"]->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;
        }
    }

    // Наполняем объект roles соответствующими разрешениями
    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"]);
        }
    }

    // Проверяем, обладет ли пользователь нужными разрешениями
    public function hasPrivilege($perm) {
        foreach ($this->roles as $role) {
            if ($role->hasPerm($perm)) {
                return true;
            }
        }
        return false;
    }
}

Метод getByUsername() возвращает объект, заполненный информацией о соответствующем пользователе. Метод почти идентичен тому, что обычно используется в класса пользователей, но его нужно изменить, чтобы добавить объект для ролей.

Метод initRoles() использует выражение JOIN для комбинирования таблиц user_role и roles для сбора информации о ролях для текущего пользователя. Каждая роль наполняется соответствующими разрешениями с помощью метода класса Role  Role::getRolePerms().

Метод hasPrivilege() получает описание разрешения и возвращает результат проверки его наличия у текущего пользователя.

С помощью описанных двух классов  проверка привилегий пользователя будет выполняться очень просто:

<?php
require_once "Role.php";
require_once "PrivilegedUser.php";

// Соединяемся с базой данных...
// ...

session_start();

if (isset($_SESSION["loggedin"])) {
    $u = PrivilegedUser::getByUsername($_SESSION["loggedin"]);
}

if ($u->hasPrivilege("thisPermission")) {
    // выполняем действие
}

Здесь имя пользователя хранится в  активной сессии, и для него создается новый объект PrivilegedUser, для которого вызывается метод hasPrivilege(). В зависимости от информации, хранящейся в базе данных, объект может выглядеть следующим образом:

object(PrivilegedUser)#3 (2) {
  ["roles":"PrivilegedUser":private]=>
  array(1) {
    ["Admin"]=>
    object(Role)#5 (1) {
      ["permissions":protected]=>
      array(4) {
        ["addUser"]=>bool(true)
        ["editUser"]=>bool(true)
        ["deleteUser"]=>bool(true)
        ["editRoles"]=>bool(true)
      }
    }
  }
  ["fields":"User":private]=>
  array(4) {
    ["user_id"]=>string(1) "2"
    ["username"]=>string(7) "mpsinas"
    ["password"]=>bool(false)
    ["email_addr"]=>string(0) ""
  }
}

 

Организационные вопросы

Одним из преимуществ использования ООП для RBAC является возможность простого разделения логики и сервисных функций. Например, можно добавить следующие методы к классу 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));
}

// Вставить массив ролей для заданного 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;
}

// удалить массив ролей и все связи
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;
}

// Удалить все роли для заданного пользователя
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));
}

Также можно добавить подобные методы для класса PrivilegedUser:

// Проверка, обладает ли пользователь заданной ролью
public function hasRole($role_name) {
    return isset($this->roles[$role_name]);
}

// Вставляем новое разрешение в роль
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));
}

// Удаляем все разрешения из роли
public static function deletePerms() {
    $sql = "TRUNCATE role_perm";
    $sth = $GLOBALS["DB"]->prepare($sql);
    return $sth->execute();
}

Разрешения очень тесно связаны с логикой приложения, поэтому они требуют достаточно сложных процедур добавления и удаления. А роли можно легко модифицировать, создавать и удалять. с помощью интерфейса администратора.

 

Заключение

В данном уроке мы рассмотрели базу для системы управления доступом на основе использования ролей. Достаточно простой код позволяет построить гибкую масштабируемую систему разграничения прав пользователей. Однако должно внимание надо уделять управлению пользовательским полномочиями на этапе использования, иначе гибкость может привести к хаосу и неразберихе.

Источник: http://feedproxy.google.com/~r/ruseller/CdHX/~3/qtZVQdn1vhs/lessons.php

Читать комменты и комментировать

Добавить комментарий / отзыв



Защитный код
Обновить

Управление привилегиями пользователя на основе ролей | | 2012-06-19 11:57:55 | | Статьи Web-мастеру | | Существуют различные подходы к управлению доступом к информации, каждый из них имеет свои плюсы и минусы. Например, использование битовой маски очень эффективно, но ограничено 32 или 64 уровнями | РэдЛайн, создание сайта, заказать сайт, разработка сайтов, реклама в Интернете, продвижение, маркетинговые исследования, дизайн студия, веб дизайн, раскрутка сайта, создать сайт компании, сделать сайт, создание сайтов, изготовление сайта, обслуживание сайтов, изготовление сайтов, заказать интернет сайт, создать сайт, изготовить сайт, разработка сайта, web студия, создание веб сайта, поддержка сайта, сайт на заказ, сопровождение сайта, дизайн сайта, сайт под ключ, заказ сайта, реклама сайта, хостинг, регистрация доменов, хабаровск, краснодар, москва, комсомольск |
 
Поделиться с друзьями: