Вступление
Представьте себе, что вы создали огромный сайт с большим количеством уроков, которые помогают пользователям совершенствовать свои навыки. Кроме обычных зарегистрировавшихся людей у вас есть ещё несколько видов пользователей: администраторы, авторы и так далее...
Проблема:
Вам необходимо ограничить права каждой группы пользователей для того, чтобы они имели доступ к той или иной части сайта.
Решение:
Внедрение списка доступа (ACL) это как раз то, что вам нужно для решения проблемы распределения привилегий.
Если вы посмотрите демо на сайте или скачаете исходный код, то на главной странице вы найдёте демонстрацию переключения между разными типами пользователей. Для того, чтобы переключиться между пользователями, просто нажимайте на ссылку с их инициалами. Если вы зайдёте в ‘Админку’, ссылка на которую находится вверху страницы, вы попадёте в центр управления пользователями, где сможете управлять самими пользователями, их ролями и привилегиями. Вы так же можете установить эту систему у себя на локальном компьютере, предварительно скачав демо файлы. ПРЕДУПРЕЖДЕНИЕ: если пользователю под номером 1 убрать привилегию ‘access admin’, то вам не удастся перейти в админку.
Эта система позволит вам создавать любые группы пользователей (гостей, премиум пользователей, администраторов и т.д.). Так же вы сможете указывать уникальные привилегии как для каждой из перечисленных групп так и для отдельно взятых пользователей. Давайте же начнём реализацию такой системы с создания базы данных MySQL.
Шаг 1: Создание базы данных
Наш ACL будет использовать 6 таблиц (включая таблицу самих пользователей). Так же, если вы решили поставить себе такую систему вам нужно создать базу данных. Мы создадим таблицы со следующими структурами:
Код создания БД записан в файле install.sql. Так же там присутствует файл sampleData.sql, который добавляет 4 пользователя, несколько ролей и полномочий. Просто откройте эти файлы в своём любимом текстовом редакторе и скопируйте содержимое в панель SQL в phpMyAdmin.
Шаг 2: Доступ к базе
Нам необходимо создать файл, который будет подключать нас к БД. Создайте файл assets/php/database.php и добавьте в него следующий код (замените значения переменных на свои, если это необходимо):
<?php
session_start();
ob_start();
$hasDB = false;
$server = 'localhost';
$user = 'root';
$pass = 'mysql';
$db = 'acl_test';
$link = mysql_connect($server,$user,$pass);
if (!is_resource($link)) {
$hasDB = false;
die("Не могу подключиться к базе MySQL.");
} else {
$hasDB = true;
mysql_select_db($db);
}
?>
На одной из первых строк мы вызываем session_start(); в принципе не будем использовать переменные сессии, но они нам понадобится как часть механизма работы переключения пользователей. Затем мы вызываем ob_start() чтобы создать буфер. Обычно PHP отсылает страницу браузеру сразу после того, как её сгенерирует. Использование ob_start(), не позволит отослать заголовки браузеру до тех пор, пока они не будут загружены полностью или до тех пор пока мы не вызовем ob_end_flush(). Благодаря буферизации страницы мы можем делать редирект из любой её части, а не только с самого верха. Дело в том, что если заголовки страницы отсылаются браузеру, в лучшем случае только JavaScript может помочь нам производить редирект. Даже самый начинающий хакер может отключить JavaScript и попасть туда, куда ему попадать нельзя. Так что лучше предохраняться не только в жизни, но и в создаваемых системах.
На строках 4-8 мы инициализируем наши переменные. $hasDB будет содержать true или false в зависимости от того, подключены мы к базе или нет. $server, $user, $pass и $db это, я надеюсь, всем известные переменные, которые используются для подключения к базе. На строке 9 мы соединяемся с сервером баз данных, а на строке 10 определяем было ли это подключение успешным. Если да, то выбираем нужную нам базу. Если нет выдаём сообщение об ошибке через функцию die().
Шаг 3: Создание класса ACL
Приготовьтесь к долгой и изнурительной работе, т.к. класс ACL является основой нашей системы. Вперёд.
Наша система ACL будет объектно-ориентирована, поэтому нам нужно создать файл, где будет располагаться наш класс. Мы начнём с объявления класса, переменных и конструктора. Поместим наш свежеиспечённый класс в папку /assets/php/class.acl.php:
<?php
class ACL
{
var $perms = array(); // Массив : Содержит привилегия текущего пользователя
var $userID = 0; // Целое число : Содержит ID текущего пользователя
var $userRoles = array(); // Массив : Содержат роли текущего пользователя
function __constructor($userID = '')
{
if ($userID != '')
{
$this->userID = floatval($userID);
} else {
$this->userID = floatval($_SESSION['userID']);
}
$this->userRoles = $this->getUserRoles('ids');
$this->buildACL();
}
function ACL($userID='')
{
$this->__constructor($userID);
}
?>
Анализ
После того как класс создан, мы создали 3 переменные для хранения информации, которая будет использоваться для генерирования ACL.
Конструктор
__constructor() предназначен для того, чтобы инициализировать объект при создании экземпляра класса ACL. Он вызывается автоматически после вот этой записи: new ACL();. В качестве аргумента конструктор получает ID пользователя. Внутри конструктора мы делаем проверку на существование ID пользователя. Если ID не существует, то мы создадим ACL для текущего пользователя, который зашёл в систему, считав значение переменной сессии. Если мы передали ID, ACL будет создан для этого конкретного пользователя (может быть полезно для входа в панель администратора).
После того, как мы получили ID, вызываем getUserRoles() который сгенерирует массив всех ролей, которые прикреплены к этому пользователю и помещает его в переменную $userRoles. В конце конструктора мы вызываем buildACL() для того, чтобы сгенерировать ACL. Метод ACL() создан для того, чтобы вся система работала и на PHP4. Когда мы вызываем new ACL() в PHP5, PHP интерпретатор запускает __constructor(). Если вы запустите этот же скрипт на PHP4, интерпретатор запустит ACL(). В PHP4 имя конструктора совпадало с именем класса.
Каждый раз, когда мы будем создавать экземпляр класса ACL, передавая ID пользователя, экземпляр класса будет содержать все привилегии найденного пользователя.
Вспомогательные методы
Пришло время добавить некоторые вспомогательные методы в наш класс. Эти методы будут использоваться другими методами для выполнения специальных задач:
function getRolePerms($role)
{
if (is_array($role))
{
$roleSQL = "SELECT * FROM `role_perms` WHERE `roleID` IN (" . implode(",",$role) . ") ORDER BY `ID` ASC";
} else {
$roleSQL = "SELECT * FROM `role_perms` WHERE `roleID` = " . floatval($role) . " ORDER BY `ID` ASC";
}
$data = mysql_query($roleSQL);
$perms = array();
while($row = mysql_fetch_assoc($data))
{
$pK = strtolower($this->getPermKeyFromID($row['permID']));
if ($pK == '') { continue; }
if ($row['value'] === '1') {
$hP = true;
} else {
$hP = false;
}
$perms[$pK] = array('perm' => $pK,'inheritted' => true,'value' => $hP,'Name' => $this->getPermNameFromID($row['permID']),'ID' => $row['permID']);
}
return $perms;
}
function getUserPerms($userID)
{
$strSQL = "SELECT * FROM `user_perms` WHERE `userID` = " . floatval($userID) . " ORDER BY `addDate` ASC";
$data = mysql_query($strSQL);
$perms = array();
while($row = mysql_fetch_assoc($data))
{
$pK = strtolower($this->getPermKeyFromID($row['permID']));
if ($pK == '') { continue; }
if ($row['value'] == '1') {
$hP = true;
} else {
$hP = false;
}
$perms[$pK] = array('perm' => $pK,'inheritted' => false,'value' => $hP,'Name' => $this->getPermNameFromID($row['permID']),'ID' => $row['permID']);
}
return $perms;
}
function getAllPerms($format='ids')
{
$format = strtolower($format);
$strSQL = "SELECT * FROM `permissions` ORDER BY `permName` ASC";
$data = mysql_query($strSQL);
$resp = array();
while($row = mysql_fetch_assoc($data))
{
if ($format == 'full')
{
$resp[$row['permKey']] = array('ID' => $row['ID'], 'Name' => $row['permName'], 'Key' => $row['permKey']);
} else {
$resp[] = $row['ID'];
}
}
return $resp;
}
По функциональности эти методы очень схожи т.к. они извлекаю данные из БД. Единственный аргумент это ID роли/пользователя для которого выполняется выборка. В функцию извлекающую роли может быть передано, как целое так и массив. Метод is_array(), помогает нам определить массив это или нет и в зависимости от этого работать дальше для извлечения привилегий для каждой роли. Если был передан массив, мы воспользуемся implode() для создания списка, разделённого запятыми. В любом случае, мы используем это значение в SQL. Затем мы создаём новый пустой массив $perms – для хранения извлечённых привилегий.
Внутри цикла while(), мы сначала генерируем переменную $pK, которую будем использовать как ключ массива. Поскольку мы будем проверять пользователя на наличие отдельной привилегии, важно чтобы название привилегии было приведено к соответствующему формату. Для этой цели мы используем метод strtolower(). Если ключ пустой, мы пропускаем следующую итерацию при помощи continue;. Далее смотрим на $row['value'] для того, чтобы установить boolean значение для привилегии. Значение ’1′ в таблице будет ассоциироваться с true (другими словами у пользователя есть привилегия). Это важно для безопасности. В противном случае выставляем привилегии значение false. В конце функции мы создаём массив с несколькими ключами, чтобы иметь возможность получить всю информацию о привилегиях. Этот массив прикреплён к новому ключу в массиве$perms, который мы создали ранее. Заметьте, что мы используем $pK для создания нового индекса. В конце мы возвращаем массив.
Вы можете заметить, что в возвращаемом массиве есть индекс названный ‘inherited’. Он имеет особое значение для нашего ACL. Если пользователь обладает привилегиями по причине наличия у него роли (к которой относятся данные привилегии), то значение данной привилегии будет inherited. Если привилегия назначена пользователю отдельно, то значение будет другое.
В методе getAllPerms(), мы формируем список всех доступных для данного пользователя привилегий. В getAllRoles() мы так же можем указать в каком виде возвратить результат. Напишем ещё пару методов:
function userHasRole($roleID)
{
foreach($this->userRoles as $k => $v)
{
if (floatval($v) === floatval($roleID))
{
return true;
}
}
return false;
}
function hasPermission($permKey)
{
$permKey = strtolower($permKey);
if (array_key_exists($permKey,$this->perms))
{
if ($this->perms[$permKey]['value'] === '1' || $this->perms[$permKey]['value'] === true)
{
return true;
} else {
return false;
}
} else {
return false;
}
}
}
?>
Два последних метода очень важны для функционирования ACL. userHasRole() принимает единственный аргумент - ID роли. Пробежавжись по всем элементам массива $userRoles, мы можем определить, является данный пользователь обладателем роли или нет. Если да, возвращаем true, нет - false. hasPermission() предназначен для тех же целей, но он проверяет наличие привилегии. В качестве аргумента метод принимает ключ привилегии. Мы приводим значение ключа к нижнему регистру, и проверяем массив $perms на наличие данного ключа. Если такой ключ есть, проверяем его значение и если оно равно ’1′ возвращаем true, если нет - false. Эти методы понадобятся нам для проверки возможностей пользователя.
Шаг 4: Администрирование
Администраторская часть будет позволять управлять пользователями. Для этого нам нужно создать несколько интерфейсов: для вывода списка пользователей, редактирования, назначения ролей и привилегий.
Откройте /admin/users.php и добавьте следующий код:
<?php
include("../assets/php/database.php");
include("../assets/php/class.acl.php");
$myACL = new ACL();
if ($myACL->hasPermission('access_admin') != true){ header("location: ../index.php");}
?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>ACL Test</title>
<link href="../assets/css/styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="header"></div>
<div id="adminButton"><a href="../">На главную</a> | <a href="index.php">Админка</a></div>
<div id="page">
<!-- PAGE CONTENT -->
</div>
</body>
</html>
Список пользователей
Вставьте этот код в div с id ‘page’:
<?php if ($_GET['action'] == '' ) { ?>
<h2>Select a User to Manage:</h2>
<?php
$strSQL = "SELECT * FROM `users` ORDER BY `Username` ASC";
$data = mysql_query($strSQL);
while ($row = mysql_fetch_assoc($data))
{
echo "<a href=\"?action=user&userID=" . $row['ID'] . "\">" . $row['username'] . "</a><br/ >";
}
} ?>
Всё очень просто. Мы формируем SQL запрос, и запускаем его в цикле. Для каждого пользователя мы генерируем ссылку для редактирования пользователя.
Редактирование отдельного пользователя
Вставьте этот блок кода сразу после того, что мы только что написали:
<?php
if ($_GET['action'] == 'user' ) {
$userACL = new ACL($_GET['userID']);
?>
<h2>Управление пользователем <?php echo $myACL->getUsername($_GET['userID']); ?>:</h2>
... Форма редактирования информации о пользователе ...
<h3>Роли: (<a href="users.php?action=roles&userID=<?php echo $_GET['userID']; ?>">Управление Ролями</a>)</h3>
<ul>
<?php $roles = $userACL->getUserRoles();
foreach ($roles as $k => $v)
{
echo "<li>" . $userACL->getRoleNameFromID($v) . "</li>";
}
?>
</ul>
<h3>Полномочия: (<a href="users.php?action=perms&userID=<?php echo $_GET['userID']; ?>">Управление Полномочиями</a>)</h3>
<ul>
<?php $perms = $userACL->perms;
foreach ($perms as $k => $v)
{
if ($v['value'] === false) { continue; }
echo "<li>" . $v['Name'];
if ($v['inheritted']) { echo " (inheritted)"; }
echo "</li>";
}
?>
</ul>
<?php } ?>
Для редактирования пользователя мы должны загрузить его ACL. Это позволит нам узнать какие роли и привилегии есть у данного пользователя. Мы создаём новый объект ACL, и передаём ему $userID который извлекается из GET. После этого мы формируем форму, состоящую из текстовых полей для редактирования логина, пароля и т.д. После мы выводим список ролей пользователя и ссылку для добавления новой роли. На строкак 10-16 загружаем все роли которые назначены данному пользователю, и выводим их на экран в цикле foreach(). Точно таким же образом мы выводим привилегии пользователя. Мы выводим только те роли и привилегии значения которых равно true для данного пользователя.
Присвоение ролей
Форма добавления роли пользователю будет такова:
Добавьте этот код сразу после предыдущего блока:
<?php if ($_GET['action'] == 'roles') { ?>
<h2>Управление ролями пользователя: (<?php echo $myACL->getUsername($_GET['userID']); ?>)</h2>
<form action="users.php" method="post">
<table border="0" cellpadding="5" cellspacing="0">
<tr><th></th><th>Участник</th><th>Не участник</th></tr>
<?php
$roleACL = new ACL($_GET['userID']);
$roles = $roleACL->getAllRoles('full');
foreach ($roles as $k => $v)
{
echo "<tr><td><label>" . $v['Name'] . "</label></td>";
echo "<td><input type=\"radio\" name=\"role_" . $v['ID'] . "\" id=\"role_" . $v['ID'] . "_1\" value=\"1\"";
if ($roleACL->userHasRole($v['ID'])) { echo " checked=\"checked\""; }
echo " /></td>";
echo "<td><input type=\"radio\" name=\"role_" . $v['ID'] . "\" id=\"role_" . $v['ID'] . "_0\" value=\"0\"";
if (!$roleACL->userHasRole($v['ID'])) { echo " checked=\"checked\""; }
echo " /></td>";
echo "</tr>";
}
?>
</table>
<input type="hidden" name="action" value="saveRoles" />
<input type="hidden" name="userID" value="<?php echo $_GET['userID']; ?>" />
<input type="submit" name="Submit" value="ОК" />
</form>
<form action="users.php" method="post">
<input type="button" name="Cancel" onclick="window.location='?action=user&userID=<?php echo $_GET['userID']; ?>'" value="Отмена" />
</form>
<?php } ?>
В первую очередь нам необходимо создать форму и таблицу. Таблица будет состоять из 3х колонок: одна для ролей, вторая для checkbox который говорит, что данная роль уже присвоена, третья колонка для checkbox, который говорит, что роль не присвоена. После создания нового объекта ACL, мы загружаем массив со всеми ролями при помощи метода getAllRoles(). Это позволит нам вывести все строки (как назначенные, так и не назначенные).
Внутри цикла foreach() мы делаем следующее: мы выводим на экран label с названием роли. Затем мы выводим radio кнопку. Значение атрибутов name и id каждой кнопки radio будет уникальной и записанной в формате “role_
” (пример: role_0012). На строках 13 и 16 определяется какие radio кнопки должны быть нажаты. Первая будет нажата, если пользователь является обладателем данной роли, вторая если нет. Затем мы завершаем строку таблицы и переходим к следующей.
После всего этого мы добавляем некоторые скрытые (hidden) элементы. Они будут сообщать нам какую операцию проводить. Так же эти данные будут содержать ID пользователя, чтобы знать для кого сохранить изменения. Затем мы добавляем кнопку submit и кнопку cancel (отмены).
Назначение привилегий
Назначение привилегий очень схоже с назначением роли, за исключение нескольких моментов. Добавьте следующий код:
[PHP] <?php if ($_GET['action'] == 'perms' ) { ?>
<h2>Управление полномочиями пользователя: (<?php echo $myACL->getUsername($_GET['userID']); ?>)</h2>
<form action="users.php" method="post">
<table border="0" cellpadding="5" cellspacing="0">
<tr><th></th><th></th></tr>
<?php
$userACL = new ACL($_GET['userID']);
$rPerms = $userACL->perms;
$aPerms = $userACL->getAllPerms('full');
foreach ($aPerms as $k => $v)
{
echo "<tr><td>" . $v['Name'] . "</td>";
echo "<td><select name=\"perm_" . $v['ID'] . "\">";
echo "<option value=\"1\"";
if ($rPerms[$v['Key']]['value'] === true && $rPerms[$v['Key']]['inheritted'] != true) { echo " selected=\"selected\""; }
echo ">Да</option>";
echo "<option value=\"0\"";
if ($rPerms[$v['Key']]['value'] === false && $rPerms[$v['Key']]['inheritted'] != true) { echo " selected=\"selected\""; }
echo ">Нет</option>";
echo "<option value=\"x\"";
if ($rPerms[$v['Key']]['inheritted'] == true || !array_key_exists($v['Key'],$rPerms))
{
echo " selected=\"selected\"";
if ($rPerms[$v['Key']]['value'] === true )
{
$iVal = '(Да)';
} else {
$iVal = '(Нет)';
}
}
echo ">Унаследованно $iVal</option>";
echo "</select></td></tr>";
}
?>
</table>
<input type="hidden" name="action" value="savePerms" />
<input type="hidden" name="userID" value="<?php echo $_GET['userID']; ?>" />
<input type="submit" name="Submit" value="ОК" />
<input type="button" name="Cancel" onclick="window.location='?action=user&userID=<?php echo $_GET['userID']; ?>'" value="Отмена" />
</form>
<?php } ?>
Мы начнём с добавления таблицы, только теперь у неё будет 2 колонки. Затем мы создадим объект ACL, вытащим массив привилегий (для этого пользователя ) и массив всех привилегий. В цикле foreach() в строке мы пишем название привилегии. Затем мы создаём элемент select. У него будет 3 опции: Allow, Deny и Inherit. Мы считываем значение $rPerms[$v['Key']]['value'] для того, чтобы знать какую из этих 3х опций выбрать по умолчанию. Если привилегия имеет значение inherited, то выберется опция Inherited.
После завершения элемента select и таблицы, мы как и в прошлом примере добавляем кнопки.
Сохранение данных
Добавьте этот код в /admin/users.php перед тегом doc type:
<?php if (isset($_POST['action']))
{
switch($_POST['action'])
{
case 'saveRoles':
$redir = "?action=user&userID=" . $_POST['userID'];
foreach ($_POST as $k =>; $v)
{
if (substr($k,0,5) == "role_")
{
$roleID = str_replace("role_","",$k);
if ($v == '0' || $v == 'x') {
$strSQL = sprintf("DELETE FROM `user_roles` WHERE `userID` = %u AND `roleID` = %u",$_POST['userID'],$roleID);
} else {
$strSQL = sprintf("REPLACE INTO `user_roles` SET `userID` = %u, `roleID` = %u, `addDate` = '%s'",$_POST['userID'],$roleID,date ("Y-m-d H:i:s"));
}
mysql_query($strSQL);
}
}
break;
case 'savePerms':
$redir = "?action=user&userID=" . $_POST['userID'];
foreach ($_POST as $k =>; $v)
{
if (substr($k,0,5) == "perm_")
{
$permID = str_replace("perm_","",$k);
if ($v == 'x')
{
$strSQL = sprintf("DELETE FROM `user_perms` WHERE `userID` = %u AND `permID` = %u",$_POST['userID'],$permID);
} else {
$strSQL = sprintf("REPLACE INTO `user_perms` SET `userID` = %u, `permID` = %u, `value` = %u, `addDate` = '%s'",$_POST['userID'],$permID,$v,date ("Y-m-d H:i:s"));
}
mysql_query($strSQL);
}
}
break;
}
header("location: users.php" . $redir);
}
?>
В первую очередь, данный код проверяет существование $_POST['action']. Значение этой переменной хранится в одном из скрытых полей 2х форм, которые мы только что создали.
Работа происходит следующим образом:
Мы формируем $redir - адрес куда отправимся после отправки формы.
Проходимся по всему массиву $_POST.
Используя substr() определяем переменную у которой 5 первых символов равны “role_”. Таким образом мы получаем значения списка привилегий.
Если значение привилегии равно ’0′ или ‘x’ (если мы не хотим, чтобы у пользователя была эта роль), мы запускаем запрос на удаление. Если мы удалим роль из таблицы user_roles, то данному пользователю она будет уже недоступна.
Если значение не ’0′ или ‘x’ мы запускаем запрос на совмещение (replace).
Для формирование строки запроса мы используем sprintf() для безопасности (sprintf() помогает защититься от SQL инъекций более подробно).
Выполняем SQL запрос при помощи mysql_query().
Обратим внимание на запрос replace: Это специальная команда MySQL, которая позволяет как вставлять, так и обновлять строки. Благодаря replace нам не придётся писать много дополнительного кода. У таблицы user_roles есть первичный ключ userID. Когда мы запускаем запрос ‘replace into’ он ищет наличие подобной записи в таблице и если она есть, добавляет её, если нет - вставляет. Более подробно об этом запросе тут.
В случае с привилегиями происходит то же самое. Только ищется другой префикс и работа производится с другой таблицей. После того, как все операции закончены, мы используем header("location:…") для того, чтобы сделать redirect на страницу, на которой находились до того благодаря сформированной ссылке $redir.
Шаг 5: Редактирование Ролей
Теперь после того, как мы закончили формы для редактирования пользователей нам необходимо сделать то же самое и для ролей. Тут будет полегче. Нам необходимо реализовать: просмотр списка ролей и редактирование роли. Создайте файл /admin/roles.php со следующим содержанием:
<?php
include("../assets/php/database.php");
include("../assets/php/class.acl.php");
$myACL = new ACL();
if ($myACL->hasPermission('access_admin') != true){ header("location: ../index.php");}
?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>ACL Test</title>
<link href="../assets/css/styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="header"></div>
<div id="adminButton"><a href="../">На главную</a> | <a href="index.php">Админка</a></div>
<div id="page">
<!-- PAGE CONTENT -->
</div>
</body>
</html>
Список ролей
И снова мы начнём с создания ACL объекта. Поведение страницы по умолчанию это вывод доступных ролей. Так что вставьте это код вместо <!– PAGE CONTENT –>:
<?php if ($_GET['action'] == '') { ?>
<h2>Выберите роль:</h2>
<?php
$roles = $myACL->getAllRoles('full');
foreach ($roles as $k => $v)
{
echo "<a href=\"?action=role&roleID=" . $v['ID'] . "\">" . $v['Name'] . "</a><br/ >";
}
if (count($roles) < 1)
{
echo "Нет ролей.<br/ >";
} ?>
<input type="button" name="New" value="Новая роль" onclick="window.location='?action=role'" />
<?php } ?>
Сначала проверим пуста ли строка запроса. Затем создадим массив ролей $roles при помощи getAllRoles(). На каждой итерации цикла foreach(), создадим ссылку, которая будет вести на страницу редактирования каждой отдельной роли. Если ролей нет, мы выводим соответствующее сообщение. В конце вставим кнопку, которая даст нам возможность вставить новую роль.
Редактирование роли
Добавьте этот код в /admin/roles.php прямо под предыдущим блоком:
<?php if ($_GET['action'] == 'role') {
if ($_GET['roleID'] == '') {
?>
<h2>Новая Роль:</h2>
<?php } else { ?>
<h2>Управление Ролью: (<?php echo $myACL->getRoleNameFromID($_GET['roleID']); ?>)</h2><?php } ?>
<form action="roles.php" method="post">
<label for="roleName">Наиминование:</label><input type="text" name="roleName" id="roleName" value="<?php echo $myACL->getRoleNameFromID($_GET['roleID']); ?>" />
<table border="0" cellpadding="5" cellspacing="0">
<tr><th></th><th>Да</th><th>Нет</th><th>Игнор</th></tr>
<?php
$rPerms = $myACL->getRolePerms($_GET['roleID']);
$aPerms = $myACL->getAllPerms('full');
foreach ($aPerms as $k => $v)
{
echo "<tr><td><label>" . $v['Name'] . "</label></td>";
echo "<td><input type=\"radio\" name=\"perm_" . $v['ID'] . "\" id=\"perm_" . $v['ID'] . "_1\" value=\"1\"";
if ($rPerms[$v['Key']]['value'] === true && $_GET['roleID'] != '') { echo " checked=\"checked\""; }
echo " /></td>";
echo "<td><input type=\"radio\" name=\"perm_" . $v['ID'] . "\" id=\"perm_" . $v['ID'] . "_0\" value=\"0\"";
if ($rPerms[$v['Key']]['value'] != true && $_GET['roleID'] != '') { echo " checked=\"checked\""; }
echo " /></td>";
echo "<td><input type=\"radio\" name=\"perm_" . $v['ID'] . "\" id=\"perm_" . $v['ID'] . "_X\" value=\"X\"";
if ($_GET['roleID'] == '' || !array_key_exists($v['Key'],$rPerms)) { echo " checked=\"checked\""; }
echo " /></td>";
echo "</tr>";
}
?>
</table>
<input type="hidden" name="action" value="saveRole" />
<input type="hidden" name="roleID" value="<?php echo $_GET['roleID']; ?>" />
<input type="submit" name="Submit" value="OK" />
</form>
<form action="roles.php" method="post">
<input type="hidden" name="action" value="delRole" />
<input type="hidden" name="roleID" value="<?php echo $_GET['roleID']; ?>" />
<input type="submit" name="Delete" value="Удалить" />
</form>
<form action="roles.php" method="post">
<input type="submit" name="Cancel" value="Отмена" />
</form>
<?php } ?>
Проверяя строку запроса мы ищем переменную roleID. Если она присутствует, то мы отображаем форму редактирования роли, если нет, то форму добавления новой роли. Внутри формы нам необходимо отобразить называние роли и таблицу её привилегий. Таблица содержит колонку для имени привилегии и значений, allow, deny, ignore. Дальше пройдёмся по массиву привилегий ($myACL->getAllPerms(‘full’))
Для каждой строки мы выводим название привилегии, и 3 кнопки типа radio. Кнопки radio содержит те же значения, что и раньше (в форме редактирования пользователей) (“perm_
“). ‘Allow’ или ‘Deny’ будут нажаты в зависимости от значения в базе. Если вы выбираете ‘ignore’, то ни одно из значений не записывается в данную роль. Заметьте, что первые 2 if() блока содержат && $_GET['roleID'] != ”. Если было передано ID пользователя (мы создаём новую роль), значение ignore выбрано по умолчанию. Затем мы снова добавляем наши скрытые элементы. Так же мы добавим ещё одну форму для удаления роли и кнопку отмены.
Если всё произошло успешно, после редактирования роли у нас должно отобразиться примерно следующее:
Сохранение данных
Вставьте этот код в /admin/roles.php перед тегом doc type:
[PHP] <?php if (isset($_POST['action']))
{
switch($_POST['action'])
{
case 'saveRole':
$strSQL = sprintf("REPLACE INTO `roles` SET `ID` = %u, `roleName` = '%s'",$_POST['roleID'],$_POST['roleName']);
mysql_query($strSQL);
if (mysql_affected_rows() > 1)
{
$roleID = $_POST['roleID'];
} else {
$roleID = mysql_insert_id();
}
foreach ($_POST as $k => $v)
{
if (substr($k,0,5) == "perm_")
{
$permID = str_replace("perm_","",$k);
if ($v == 'X')
{
$strSQL = sprintf("DELETE FROM `role_perms` WHERE `roleID` = %u AND `permID` = %u",$roleID,$permID);
mysql_query($strSQL);
continue;
}
$strSQL = sprintf("REPLACE INTO `role_perms` SET `roleID` = %u, `permID` = %u, `value` = %u, `addDate` = '%s'",$roleID,$permID,$v,date ("Y-m-d H:i:s"));
mysql_query($strSQL);
}
}
header("location: roles.php");
break;
case 'delRole':
$strSQL = sprintf("DELETE FROM `roles` WHERE `ID` = %u LIMIT 1",$_POST['roleID']);
mysql_query($strSQL);
$strSQL = sprintf("DELETE FROM `user_roles` WHERE `roleID` = %u",$_POST['roleID']);
mysql_query($strSQL);
$strSQL = sprintf("DELETE FROM `role_perms` WHERE `roleID` = %u",$_POST['roleID']);
mysql_query($strSQL);
header("location: roles.php");
break;
}
}
?>
Проверяем массив $_POST, ищем $_POST['action']. Если нам необходимо сохранить новую роль, то:
Формируем и выполняем replace запрос. Это обновит/вставит название роли. Если нам необходимо произвести обновление роли, то используем ID роли, который у нас есть. Когда мы выполняем запрос replace, нам возвращается количество затронутых строк. Если это число больше 1, то это значит, что строка была обновлена. Если запрос replace вернул число не превышающее 1, то произошла вставка роли ID которой мы можем получить используя mysql_insert_id() которая возвращает ID последней вставленной строки.
Затем мы пробегаемся по массиву $_POST и ищем переменные начинающиеся на "perm_".
Помещаем значение в floatval(), получаем ID привилегии.
if ($v == ‘x’) {…} запустится если мы выбрали для привилегии значение ‘Ignore’. Это позволит удалить привилегию для данной роли. Если это случится, мы используем continue; для того, чтобы перейти к следующей переменной.
Если мы дошли до этого шага, то это значит, что мы должны вставить или обновить привилегию для данной роли. Используем ‘replace into’. Важно, чтобы у нас были сформированы roleID и permID для того, чтобы правильно произвести обновление.
В конце концов мы выполняем SQL запрос и делаем redirect.
Если мы нажали на кнопку Delele, то просто удалим роль из таблицы. Затем мы удалим все значения связанные с данной ролью из таблиц user_roles и role_perms для актуализации данных. Затем делаем redirect на страницу со списком ролей.
Шаг 6: Управление привилегиями
Управление привилегиями практически совпадает с управлением ролями. Нам необходимо реализовать: вывод списка всех возможных привилегий, и их редактирование. Добавьте следующий файл и код /admin/perms.php:
<?php
include("../assets/php/database.php");
include("../assets/php/class.acl.php");
$myACL = new ACL();
if ($myACL->hasPermission('access_admin') != true){ header("location: ../index.php");}
?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>ACL Test</title>
<link href="../assets/css/styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="header"></div>
<div id="adminButton"><a href="../">На главную</a> | <a href="index.php">Админка</a></div>
<div id="page">
<!-- PAGE CONTENT -->
</div>
</body>
</html>
Список привилегий
Вставьте этот код вместо <!– PAGE CONTENT — >:
<?php if ($_GET['action'] == '') { ?>
<h2>Выберите Полномочия:</h2>
<?php
$roles = $myACL->getAllPerms('full');
foreach ($roles as $k => $v)
{
echo "<a href=\"?action=perm&permID=" . $v['ID'] . "\">" . $v['Name'] . "</a><br />";
}
if (count($roles) < 1)
{
echo "Нет полномочий.<br />";
} ?>
<input type="button" name="New" value="Новое полномочие" onclick="window.location='?action=perm'" />
<?php } ?>
Используем getAllPerms() для получения массива привилегий. Впоследствии пройдёмся по нему, чтобы отобразить список. Каждая итерация внутри цикла foreach() выводит ссылку, которая используется для переноса на страницу редактирования привилегии. Если привилегий вообще нет, выводим соответствующее сообщение. Далее выводим кнопку добавления новой привилегии. Вот результат:
Редактирование привилегии
Для того, чтобы редактировать или добавить новую привилегию, вставьте этот код после предыдущего блока:
<?php if ($_GET['action'] == 'perm') {
if ($_GET['permID'] == '') {
?>
<h2>Новое Полномочие:</h2>
<?php } else { ?>
<h2>Управление Полномочиями: (<?php echo $myACL->getPermNameFromID($_GET['permID']); ?>)</h2><?php } ?>
<form action="perms.php" method="post">
<label for="permName">Наиминование:</label><input type="text" name="permName" id="permName" value="<?php echo $myACL->getPermNameFromID($_GET['permID']); ?>" maxlength="30" /><br />
<label for="permKey">Ключ:</label><input type="text" name="permKey" id="permKey" value="<?php echo $myACL->getPermKeyFromID($_GET['permID']); ?>" maxlength="30" /><br />
<input type="hidden" name="action" value="savePerm" />
<input type="hidden" name="permID" value="<?php echo $_GET['permID']; ?>" />
<input type="submit" name="Submit" value="ОК" />
</form>
<form action="perms.php" method="post">
<input type="hidden" name="action" value="delPerm" />
<input type="hidden" name="permID" value="<?php echo $_GET['permID']; ?>" />
<input type="submit" name="Delete" value="Удалить" />
</form>
<form action="perms.php" method="post">
<input type="submit" name="Cancel" value="Отмена" />
</form>
<?php } ?>
Как и в выводе формы для ролей, мы проверяем наличие ID привилегии в строке запроса и в зависимости от этого выводим отображение добавления или редактирования записи. Мы открывает тег формы и добавляем 2 текстовых поля: одно для наименования привилегии, другое для ключа привилегии. Ключ мы используем для ск