PHP

Реализация механизма разграничения прав доступа к админ-части

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

Что ж, сама по себе данная тема довольно грузная, и требует определённого времени на анализ и постанувку задачи.

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

Давайте с самого начала обсудим архитектуру модульной системы на выбранной нами псевдо-системе.

Все модули представлены ввиде подключаемых к главному документу (индекс-файлу) вставок. Запрос модуля происходит из строки запроса QUERY_STRING, и название подключаемого модуля передаётся в качестве аргумента act. В некотором месте индекса файла происходит изъятие и обработка данного параметра. После, если у пользователя достаточно прав для доступа к модулю в контексте чтения, происходит проверка существования указанного в строке запроса модуля, и если таковой существует, то происходит его подключение к индекс файлу.

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

Для воплощения данного механизма мы будет проверять значение переменной строки запроса `do`, которая обрабатывается в самом модуле и носит информацию о том, к какому разделу модуля необходимо предоставить доступ пользовалю.

Значение do буду фиксированными, данная переменная будет принимать следующие значения:

main - главная часть модуля (доступно в контексте чтения)
config - раздел настройки модуля (доступно в контексте записи)
create - произвести некоторые действия, по добавлению информации в БД (доступно в контексте записи)
delete - доступ к разделу, предоставляющему возможности удалить некоторую информацию, в контексте данного модуля (доступно в контексте записи)
edit - доступ к редактированию информации в контексте модуля (доступно в контексте записи)

В целом, этот список можно увеличить, при этом всё зависит лишь только от масштабов проекта и его потребностей в функционале.

Теперь непосредственно о модулях. Кроме физического существования некоторого модуля в контексте файловой системы проекта, модуль так же должен быть добавлен в особую таблицу БД, которая будет содержать информацию о всех существующих модулях в системе. Добавление и изменение данных данной таблицы, обычно, производится непосредственно в контексте модулей, а то есть во время их инсталяции в системе. Однако это уже углубление в принципы посмотроения расширяемых систем, о чём мы как-то в другой раз поговорим, и посему, мы ограничимся ручным обновлением и добавлением данных о модулях.

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

Кроме модулей у нас будут ещё две таблицы, а именно таблица в которой будут хранится данные относительно профилей прав доступа и таблица с информацией о пользователях непосредственно.

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

Что ж, давайте рассмотрим эту особую структуру. Она будет следующей: [ module_indefier : [0 | 1]+ \: [0 | 1]+ \;] *

То есть идёт список из пар: имя модуля ":" права чтения "," права записи ";". При этом данная метка обновляется в момент внесения изменений о правах доступа пользователя к системе. Если в системе появляется информация о модуле, который не вошёл в данную метку, то стоит просто произвести процедуру редактирования, и данные сохранятся автоматически.

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

Я не буду добавлять ничего лишнего в неё, но лишь то, что будет использоватся в контексте темы данной статьи. Таблица пользователей будет содержать следующие поля: идентифицатор пользователя (числовой счётчик), логин, пароль (хеш оригинального пароля), профиль безопасности пользователя (идетификатор группы пользователя, относительно прав в системе), и всё. Мне кажется этой информации нам с вами вполне хватит, для реализации поставленной задачи, а уже все остальные надстройки я предоставляю возможность сделать самим.

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

Таблица `modules`:
CREATE TABLE `modules` (
  `id` bigint(20) NOT NULL auto_increment,
  `indefier` text collate utf8_unicode_ci NOT NULL,
  `title` text collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1
  DEFAULT CHARSET=utf8
  COLLATE=utf8_unicode_ci;

Таблица `secure_groups`:
CREATE TABLE `secure_groups` (
  `id` bigint(20) NOT NULL auto_increment,
  `title` text collate utf8_unicode_ci NOT NULL,
  `perms` text collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1
  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;

Таблица `users`
CREATE TABLE `users` (
  `id` bigint(20) NOT NULL auto_increment,
  `login` text collate utf8_unicode_ci NOT NULL,
  `passwd` text collate utf8_unicode_ci NOT NULL,
  `groupId` int(1) NOT NULL default '0',
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1
  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;

Далее описан класс для внедрения функций проверки прав доступа пользователей к модулям системы.
<?php
class security extends engine{
    var $temp=array();

    function getUserId(){
        $hash=explode('::',$_COOKIE['site_hash']);
        $id=$hash[0];
        return $id;
    }

    function getUserSecurityAccess($id){
        if(is_numeric($id)){
            $id=rawurlencode($id);
            $conn_id=mysql_connect('host','user','passwd');
            mysql_select_db('database');
            $q=mysql_query("SELECT groupid FROM `users`
                            WHERE id='".$id."'",$conn_id);
            if($q){
                if(mysql_numrows($q)!=0){
                    $result=@mysql_fetch_assoc($q);
                    return $result['group_id'];
                }else{
                    return -1;
                }
            }else{
                return -1;
            }
            mysql_close($conn_id);
        }else{
            return -1;
        }
    }

    function checkUserPermission($module,$act){
        #return true;
        $this->temp=array();
        $this->temp['_result']=0;
        $this->temp['_uid']=explode('::',$_COOKIE['site_hash']);
        $this->temp['_uid']=$this->temp['_uid'][0];
        $this->temp['_gid']=$this->getUserSecurityAccess($this->temp['_uid']);
        $this->temp['_conn_id']=mysql_connect('host','user','passwd');
        mysql_select_db('database');
        $this->temp['_q1']=mysql_query('SELECT perms'
                        .'FROM `secure_groups`' 
                        .'WHERE id='.$this->temp['_gid']);    
        $this->temp['_access_stamp']=mysql_fetch_assoc($this->temp['_q1']);
        $this->temp['_access_stamp']=$this->temp['_access_stamp']['perms'];
        $this->temp['_access_stamp']=explode(';',$this->temp['_access_stamp']);
        $this->temp['_access_stamp']=array_slice($this->temp['_access_stamp'],0,-1);
        foreach($this->temp['_access_stamp'] as $this->temp['v']){
            $this->temp['_mod_access']=explode(':',$this->temp['v']);
            $this->temp['_mod_indefier']=$this->temp['_mod_access'][0];
            if($this->temp['_mod_indefier']==$module){
                $this->temp['_perms']=explode(',',$this->temp['_mod_access'][1]);
                switch($act){
                    case 'r':
                        $this->temp['_result']=($this->temp['_perms'][0]==1)? 1:0;
                        break;
                    case 'w':
                        $this->temp['_result']=($this->temp['_perms'][1]==1)? 1:0;
                        break;
                }
                break;
            }
        }
        mysql_close($conn_id);
        return $this->temp['_result'];
    }
}
?>

Данный класс внедряет функции, предназначенные для воплещения алгоритмического задания, описанного выше. Сейчас мы обсудим каждую функцию отдельно.
Функция secure::getUserId()

Используя данную функцию, мы подразумеваем, что во время авторизации пользователя в системе в переменной среде $_COOKIE была установлена переменная `site_hash`, состоящая из идентификатора пользователя в системе и хеша для проверки аутентичности его в системе. Функция просто изымает значение идентификатора, возращая его значение на выходе.
Функция secure::getUserSecurityAccess($id)

На выходе данная функция возвращает идентификатор профиля безопасности текущего пользователя в системе.
Функция secure::checkUserPermission($module,$act))

Производится запрос к БД, относительно прав пользователя на произведение действий чтения/записи в контексте переданного в качестве параметра модуля.

Осталось лишь описать процедуру формирования переменной в среде $_COOKIE, и тему статьи можно будет считать расскрытой.

Процедура авторизации будет выглядеть ввиде внесения личных данных пользователя (логин и пароль) в специальную форму, после отправки которой произойдёт обработка данных, переданных пользователем, по-методу функции checkAuthData(), и, в случае корректности данных, будет произведено сохранение данных о пользователе ввиде куки записи на период установленный пользователем, либо в отсутствии заданного значение на период по-умолчанию.

Для проверки аутентичности данных хранимых в переменной среде $_COOKIE, мы будем использовать функцию EatCookie(), которая будет производить валидацию данных, возвращая булевый результат проверки (истина - ложь).

Я не привожу форму для отправки, так как это не часть теории программирования, указав лишь идентификаторы полей.

`ulogin` - логин пользователя
`upasswd` - пароль пользователя
`stime` - время сессии, устанавливаемое пользователем (от 1 до 5 часов)
`auth` - имя кнопки отправки
<?php
function checkAuthData($info){
    $login=rawurlencode($info['ulogin']);
    $passwd=rawurlencode($info['upasswd']);
    $stime=(rawurlencode(trim($info['stime']))=='')?1:rawurlencode($info['stime']);    
    $err_code=0;
    $result=false;
    if(trim($login)==''){
        $err_code=1;
    }
    if(trim($passwd)==''){
        $err_code=1;
    }
    if($err_code){
        $result=false;
    }else{
        $conn_id=mysql_connect('host','user','passwd');
        mysql_select_db('database');
        $q=mysql_query("SELECT * FROM `users` WHERE passwd='".md5($passwd)."'
                        AND login='".$login."'",$conn_id);
        if($q){
            if(@myql_numrows($q)!=0){
                $row=mysql_fetch_assoc($q);
                if($row['status']=='on'){
                                $hash=$row['id'].'::'.md5(md5($login).
                                md5($passwd));
                                setcookie('site_hash',$hash,$stime*3600*60);
                    $result=true;
                      }else{
                          $result=false;
                      }
                }else{
                    $result=false;
                }
            }else{
                $result=false;
            }
        mysql_close($conn_id);
        }
    return $result;    
}

function EatCookie(){
    $result=false;
    $hash=isset($_COOKIE['site_hash'])?explode('::',$_COOKIE['site_hash']):($result=false);
    $id=$hash[0];
    $hash=$hash[1];
    $conn_id=mysql_connect('host','user','passwd');
    mysql_select_db('database');
    if(!is_numeric($id)){
        $result=false;
    }else{
        $q=mysql_query("SELECT * FROM `users` WHERE id='".$id."'",$conn_id);
        if(mysql_numrows($q)==0){
            $result=false;
        }else{
            $row=mysql_fetch_assoc($q);
            $newHash=md5(md5($row['login']).md5($row['passwd']));
            if($newHash==$hash){
                $result=true;
            }else{
                $result=false;
            }
        }
    }
    return $result;
}
?>

Вот, в целом и всё. Осталось лишь пробовать, экспериментировать, ошибатся и находить решение, что я всецело и оставляю вам.
Добавлено: 21 Августа 2013 08:36:19 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...