В свете того, что на habr’е появились уже две статьи про ACL — Система разделения прав доступа в веб-приложении — часть 1, теория и Система разделения прав доступа в веб-приложении и обе они сводятся к переизобретению реализации механизмов контроля доступа, то мне хотелось рассказать о другом подходе.
Первая статья – описание довольно спорного подхода. Битовая арифметика это кончено прикольно и все такое ;), но мне бы лично не хотелось с ней часто встречаться, особенно если поддержке, в рамках которой часто нужно менять, то можно пользователям разных групп. Вторая – написана довольно «сумбурно», если можно так сказать. Делать физическую модель сверху вниз (сущности -> атрибуты -> нормализация) для такой маленькой модели вообще смысла нет. Человеку, проектировавшему реальные модели, нужно приложить усилия, чтобы сделать модель ниже 3-ий нормальной формы ;). И в коде (пример использования) чего-то там клонируется, include’ится. IMHO для такой постановки вопроса совсем не KISS ;)
Т.о. два представленных решения не подходят. Чтобы понять, что нужно надо составить требования, к уже обозначенной проблеме.
Проблема: удобный (API) механизм аутентикации и авторизации для интернет-приложения.
Слово «аутентикация» я использую без «фи» т.к. в том слове, от которого оно произошло (authentication) это самое «фи» отсутствует, а еще так говорил один мой преподаватель – сын гордого, южного народа отстаивал именно такое произношение не только среди своих студентов, методом «кнута и пряника», но и среди коллег, но уже другим методом ;).
Чтобы не было путницы, уточню значения этих терминов.
В рамках ACL существуют следующие объекты:
Требования: создать класс, реализующий аутентикацию и авторизацию (ACL) для приложения. Должна быть реализованы группы для ARO с наследованием правил. Правили должны раздаваться как группам, так и пользователям. Правила должны быть двух видов: «разрешить» и «запретить». Должна быть возможность редактировать ACL через графический интерфейс.
Интерфейс для редактирования – довольно удобная штука. В это я убедился на собственном опыте. Опять же это связанно с сопровождением. И именно из-за отсутствия интерфейса я не использую Zend_Acl. Хотя перешел я на Zend Framework позже, чем создал нужный класс, а так может быть и Zend_Acl использовал.
Теперь по поводу «сделать саму» vs «найди/адаптировать готовое». Постольку я уже нашел и адаптировал, будет рассматриваться именно второй вариант.
Использованные компоненты:
Плюсы этих компонент, помимо того, что они готовы и свободно распространяются:
Минусы в стандартной комплектации:
Исправление минусов я считаю подготовительным этапом. Но, в принципе, можно было использовать указанные компоненты в том виде, в котором они поставлялись.
Изменения, проделанные в рамках «подготовки»:
Модель данных phpGACL из

превратилась в

Собственно реализация:
APC означает «Authorization Policy Controller», и было придумано в большей степени из уважения к MJK, насколько я помню ;). Опять же класс был создан до того, как я познакомился с одноименным opcode cacher’ом и до того, как слово Controller стало однозначно ассоциироваться с MVC.
APC унаследован от PEAR::Auth. Основные методы:
Подытожив, скажу, что я описал лишь подход. Реализацию и примеры применений я планируй описать в продолжении, при условии, что эта статья вызовет интерес.
P.S. первый пот на habr’е ;)
Первая статья – описание довольно спорного подхода. Битовая арифметика это кончено прикольно и все такое ;), но мне бы лично не хотелось с ней часто встречаться, особенно если поддержке, в рамках которой часто нужно менять, то можно пользователям разных групп. Вторая – написана довольно «сумбурно», если можно так сказать. Делать физическую модель сверху вниз (сущности -> атрибуты -> нормализация) для такой маленькой модели вообще смысла нет. Человеку, проектировавшему реальные модели, нужно приложить усилия, чтобы сделать модель ниже 3-ий нормальной формы ;). И в коде (пример использования) чего-то там клонируется, include’ится. IMHO для такой постановки вопроса совсем не KISS ;)
Т.о. два представленных решения не подходят. Чтобы понять, что нужно надо составить требования, к уже обозначенной проблеме.
Проблема: удобный (API) механизм аутентикации и авторизации для интернет-приложения.
Слово «аутентикация» я использую без «фи» т.к. в том слове, от которого оно произошло (authentication) это самое «фи» отсутствует, а еще так говорил один мой преподаватель – сын гордого, южного народа отстаивал именно такое произношение не только среди своих студентов, методом «кнута и пряника», но и среди коллег, но уже другим методом ;).
Чтобы не было путницы, уточню значения этих терминов.
- Аутентикация – удостоверение перед системой факта «пользователь именно тот, за кого себя выдает»
- Авторизация – установление возможности осуществить пользователем (аутентицированным) конкретное действие (контролируемое) в системе (Access Control List (ACL) – одна из возможных реализаций)
В рамках ACL существуют следующие объекты:
- Access Request Object (ARO) – тот, кого нужно контролировать e.g. пользователя
- Access Control Object (ACO) – тот, что контролируем e.g. доступ в приватную зону сайта
Требования: создать класс, реализующий аутентикацию и авторизацию (ACL) для приложения. Должна быть реализованы группы для ARO с наследованием правил. Правили должны раздаваться как группам, так и пользователям. Правила должны быть двух видов: «разрешить» и «запретить». Должна быть возможность редактировать ACL через графический интерфейс.
Интерфейс для редактирования – довольно удобная штука. В это я убедился на собственном опыте. Опять же это связанно с сопровождением. И именно из-за отсутствия интерфейса я не использую Zend_Acl. Хотя перешел я на Zend Framework позже, чем создал нужный класс, а так может быть и Zend_Acl использовал.
Теперь по поводу «сделать саму» vs «найди/адаптировать готовое». Постольку я уже нашел и адаптировал, будет рассматриваться именно второй вариант.
Использованные компоненты:
- PEAR::Auth
- phpGACL (русская документация) разработчики которого, судя по документации фанаты Star Wars ;)
Плюсы этих компонент, помимо того, что они готовы и свободно распространяются:
- Достаточно ясно в реализации (документация/код)
- PEAR::Auth имеет некоторые feature’и по усилению безопасности (e.g. контроль того, чтобы IP и UserAgent в течении жизни сессии совпадали с значениями на момент ее создания и пр.)
- PEAR::Auth позволяет писать собственные адаптеры для источников данных
- phpGACL имеет интерфейс редактирования ACL
Минусы в стандартной комплектации:
- Обе компоненты используют слои абстракции баз данных не поддерживающий автоинкрементные поля в MySQL из-за чего количество таблиц заметно увеличивается (создаются таблицы-эмуляторы sequence’ов). Кому-то этот минус может показаться несущественным, но я не хочу испытывать страх, всякий раз заглядывая phpMyAdmin’ом в свою базу. Помимо этого в 99%, я думаю, мои сайты будут на MySQL и хочу использовать именно его feature’и. Для PEAR::Auth это решается просто – написанием простого адаптера. phpGACL придется «переводить» в ручную с AdoDb
- phpGACL выглядит довольно страшно «из коробки». Модель данных тоже не исключение – 18 таблиц + 10 таблиц (sequence’ов). Инсталлятор, кэш, soap-клиент, и пр. Все лишнее нужно будет убрать, сделав embedded версию
- помимо сказанного в предыдущем пункте в phpGACL имеет несколько избыточные функции для большинства случаев. Это выражается в том, что помимо ARO и ACO есть еще AXO (Access eXtension Object – объекты расширения доступа, при использовании которых ACO становятся непосредственно объектом, к которому контролируется доступ, а AXO – действиями, совершаемыми над этим объектом). AXO в большинстве случаев не нужны и от них нужно будет избавиться. Также для ARO помимо принадлежности к группе (дерево групп) задается принадлежность к секции (список). Это тоже, как мне кажется, лишнее.
Исправление минусов я считаю подготовительным этапом. Но, в принципе, можно было использовать указанные компоненты в том виде, в котором они поставлялись.
Изменения, проделанные в рамках «подготовки»:
- для PEAR::Auth написан адаптер для PDOCon (моя обертка для PHP Data Object)
- из phpGACL были удалены AXO и секции для ARO. В соответствие были приведен код и интерфейс управления
- phpGACL был переведен на PDOCon
- для phpGACL был отключен кеш в лице Cache_Lite. Сделано это было из-за того, что запросы phpGACL на работающих проектах не давали большой нагрузки и при необходимости кэширование для phpGACL все равно нужно будет интегрировать с общим кешом (контетна)
Модель данных phpGACL из

превратилась в

Собственно реализация:
- <?php
- class APC extends Auth
- {
- const STATUS_DISABLED = 0;
- const STATUS_ENABLED = 1;
- /**
- * Policy object
- * @var gacl_api
- */
- private $gacl = null;
- /**
- * Database object
- * @var PDOCon
- */
- private $database = null;
- private $userId = null;
- private $status = null;
- private $aclCache = array();
- public function __construct()
- {
- Zend_Loader::loadFile("gacl/gacl.class.php", null, true);
- Zend_Loader::loadFile("gacl/gacl_api.class.php", null, true);
- $params = array(
- "db" => Zend_Registry::get('db'),
- "cryptType" => "md5",
- "table" => "acl__aro",
- "usernamecol" => "value",
- "passwordcol" => "password",
- "postUsername" => "username",
- "postPassword" => "password"
- );
- parent::__construct("PDOCon", $params, null, false);
- //gacl
- $gacl_options = array('db_table_prefix' => 'acl__');
- $this->database = Zend_Registry::get('db');
- $this->gacl = new gacl_api($this->database, $gacl_options);
- }
- /**
- * Returns bool specifies whether user is successfuly authenticated
- * @return boolean
- */
- public function checkAuth()
- {
- if(!parent::checkAuth())
- {
- return false;
- }
- return ($this->getStatus() == self::STATUS_ENABLED);
- }
- public function getStatus()
- {
- if(!parent::checkAuth())
- {
- return false;
- }
- if(is_null($this->status))
- {
- $this->status = ($this->getUserId() !== false)
- ? (int) $this->database->getOne(
- "user", array('user_id' => $this->getUserId()), null, "status"
- )
- : self::STATUS_DISABLED;
- }
- return $this->status;
- }
- public function aclCheck($acoSection, $aco, $returnValue = false)
- {
- if(is_null($this->gacl))
- {
- throw new Exception('GACL does not exist');
- }
- $aro = $this->checkAuth() ? $this->getUsername() : 'guest';
- if(!isset($this->aclCache[$acoSection][$aco][$aro]))
- {
- $this->aclCache[$acoSection][$aco][$aro] = $this->gacl->acl_query($acoSection, $aco, $aro);
- }
- if(!$returnValue)
- {
- return $this->aclCache[$acoSection][$aco][$aro]['allow'];
- }
- else
- {
- return $this->aclCache[$acoSection][$aco][$aro]['return_value'];
- }
- }
- public function isMemberOf($groupName, $userId = null)
- {
- $userId = is_null($userId) ? $this->getUserId() : $userId;
- $sql = "
- SELECT COUNT(*)
- FROM user u
- JOIN acl__aro r ON r.id = u.aro_id
- JOIN acl__groups_aro_map m ON r.id = m.aro_id
- JOIN acl__aro_groups g ON m.group_id = g.id
- ".PDOCon::WHERE;
- return (1 == $this->database->getRow(
- $sql,
- array(
- 'g.value' => $groupName,
- 'u.user_id' => $userId
- ),
- null,
- PDO::FETCH_NUM,
- 0
- ));
- }
- public function getUserId()
- {
- if(!$this->getUsername())
- {
- throw new Exception('user is not set!');
- }
- if(is_null($this->userId))
- {
- $sql = "
- SELECT u.user_id
- FROM acl__aro r
- JOIN user u ON u.aro_id = r.id
- ".PDOCon::WHERE;
- $this->userId = $this->database->getRow(
- $sql,
- array('value' => $this->getUsername()),
- null,
- PDO::FETCH_NUM,
- 0
- );
- }
- return $this->userId;
- }
- }
APC означает «Authorization Policy Controller», и было придумано в большей степени из уважения к MJK, насколько я помню ;). Опять же класс был создан до того, как я познакомился с одноименным opcode cacher’ом и до того, как слово Controller стало однозначно ассоциироваться с MVC.
APC унаследован от PEAR::Auth. Основные методы:
- checkAuth(), возвращающий true есть пользователь аутентицирован и false в противном случае
- aclCheck(), проверяющий авторизация на выполнения конкретного действия e.g. aclCheck('article', 'edit')
Подытожив, скажу, что я описал лишь подход. Реализацию и примеры применений я планируй описать в продолжении, при условии, что эта статья вызовет интерес.
P.S. первый пот на habr’е ;)