- 1. Обзор
- 2. Хранение прав в БД
- 3. Наложение прав
- 4. Владельцы объектов
- 5. Работа с ACL
- 6. Запуск модулей из шаблонов
- 7. obj_id
- 8. Метод convertArgsToObj()
- 9. Метод getAcl()
Одной из основных идей mzz является: "Всё на сайте является объектом". Т.е. новость это объект класса news, фотография в галерее - объект класса photo, пользователь - объект класса user. Каждый из объектов обладает набором действий, которые можно над ним произвести. Например "удалить", "создать", "изменить", "переместить" и т.д. Этот набор не является жёстко фиксированным и создаётся программистом в процессе разработки приложения. Система прав mzz позволяет разграничивать права на выполнение определённых действий на уровне конкретных объектов, для пользователей и групп. Т.е. вы можете разрешить (или наоборот, запретить) выполнение "редактирования" новости определённым пользователем (или группой пользователей). При этом проверка прав на действия - производится автоматически. Писать код, работающий с правами, вам придётся лишь в том случае, если будет необходимо реализовать нетривиальную схему авторизации, не вписывающуюся в имеющуюся в mzz. Обо всём по порядку.
Данные о правах хранятся в группе таблиц в БД mzz.
| Имя таблицы | Описание |
sys_access |
Главная таблица ACL. Непосредственно в ней хранятся все права на все объекты в mzz. |
sys_access_registry |
Реестр объектов. Для того, чтобы объекту можно было назначать права, он должен быть зарегистрирован в ACL. Эта таблица хранит в себе список объектов и ссылку на тип объекта и раздел, в контектсе которого этот объект существует. |
sys_actions |
Справочник действий. Таблица, содержащая список всех действий, которые могут быть выполнены с объектами системы. |
sys_classes |
Справочник классов. Таблица, содержащая список всех типов объектов, доступных в системе. |
sys_classes_actions |
Таблица, связывающая между собой конкретный класс и действия. Именно на основании данных этой таблицы определяется - какие действия возможно производить над объектом. |
sys_classes_sections |
Таблица, связывающая между собой конкретный класс и секцию, в контексте которой хранится объект этого класса. |
sys_modules |
Справочник модулей. Таблица, содержащая список всех типов модулей, доступных в системе. |
sys_obj_id |
Таблица, выполняющая роль секвенции. Используется для определения следующего значения obj_id (уникального в пределах системы идентификатора объекта). |
sys_obj_id_named |
Таблица, содержащая набор "именованных" объектов. Они используются в тех случаях, когда реального объекта не существует, но он необходим идеологически. |
sys_sections |
Справочник разделов. Таблица, содержащая список всех типов разделов, в контексте которых могут работать модули. |
Права на объекты "наследуются" в случае, если на одного и того же пользователя действует несколько разрешений на действие (т.е. право дано конкретному пользователю и группам, в которых он состоит). Наследование прав состоит из правил, определённых для конкретного пользователя, группы пользователей и дефолтных прав на тип объектов. Вся система прав mzz может быть вкратце описана в трёх тезисах:
- Если на действие не установлено разрешения или запрещения - то считается, что действие запрещено.
- Если в процессе "наследования" права присутствуют лишь разрешения, либо разрешения чередуются с неопределёнными состояниями (когда ни разрешения, ни запрещения на данное действие для пользователя или группы не установлено) - то считается, что действие разрешено.
- Если в процессе "наследования" права встречается хотя бы одно запрещающее правило - то считается что действие запрещено.
Как уже было сказано - права на конкретный объект можно выставлять как конкретному пользователю, так и группе пользователей. Также существует набор дефолтных прав, которые действуют на всю коллекцию объектов определённого типа. Разберём процесс авторизации на нескольких примерах.
Пусть имеется в наличии объект класса news с уникальным идентификатором obj_id = 100. Для простоты представим, что с объектом класса news можно производить 2 действия: просмотр и редактирование. Также зарегистрировано 3 пользователя с именами visitor, editor и hacker, которые в примерах будут фигурировать, как рядовой посетитель, контент-менеджер системы и злоумышленник соответственно. Также в системе имеются 3 группы: viewers (посетители), managers (контент-менеджеры системы), banned (забаненные пользователи). Пользователь editor состоит в группах viewers и managers, пользователь visitor - только в группе viewers, а пользователь hacker - в группе banned.
Пользователи
| Ид | Имя |
| 1 | visitor |
| 2 | editor |
| 3 | hacker |
Группы
| Ид | Имя |
| 1 | viewers |
| 2 | managers |
| 3 | banned |
В следующих примерах будет использоваться упрощённая структура таблиц, облегчающая понимание процесса проверки прав, но при этом достаточно адекватная по отношению к существующей структуре.
Ситуация 1. Права по умолчанию на все объекты класса news.
| Действие | Пользователь | Группа | Правило |
Просмотр |
1 | разрешено |
|
Редактирование |
2 | разрешено |
|
Просмотр |
3 | запрещено |
Из этой таблицы видно, что по умолчанию всем пользователям из группы viewers разрешён просмотр новостей, группе managers разрешено редактирование всех новостей, а пользователям, включённым в группу banned доступ на просмотр всех новостей закрыт. Применительно к пользователям: visitor и editor получат права на просмотр, причём у editor'а также будет право и на редактирование всех новостей. А у hacker доступ ко всем действиям всех объектов класса news - закрыт.
Ситуация 2. Права на конкретный объект news (obj_id = 100).
Теперь давайте добавим несколько частных прав на конкретную новость (у которой obj_id = 100).
| Действие | Пользователь | Группа | Правило |
Редактирование |
1 | разрешено |
|
Редактирование |
2 | запрещено |
|
Просмотр |
3 | разрешено |
Разберёмся с этими установленными правами:
1 строка: в ней мы разрешаем доступ для пользователя visitor на редактирование. Ранее у этого пользователя на редактирование права выставлены не были, сейчас правило - разрешающее. Вердикт: доступ разрешён (См. тезис 2).
2 строка: в ней мы запрещаем доступ для редактирования для всей группы managers. До этого группе было выставлено разрешающее правило. Вердикт: доступ запрещён (См. тезис 3).
3 строка: в ней мы разрешаем доступ на просмотр конкретной новости пользователю hacker. Для этого пользователя это единственное правило. Но он состоит в группе, на которую уже было установлено запрещающее. доступ запрещён (См. тезис 3).
В системе управления правами mzz также существует и такое понятие, как владелец. Это тот пользователь, от чьего имени был создан объект. Для владельцев можно установить специальный набор прав, разрешающий или запрещающий ряд действий, производимых над вновь созданным объектом. Номинально - при создании и регистрации объекта набор "прав для владельца" копируется для конкретного объекта и конкретного пользователя, т.е. правила фактически становятся конкретными правилами для объекта. Из этого следует, что если вы измените "права для владельца" объекта, то новый набор прав будет применён лишь к создаваемым после этого события объектам. На права, выставленные на объекты, созданные до изменения набора "прав для владельца", это никак не повлияет.
ACL предоставляет интерфейс, позволяющий производить необходимые действия по определению и установке прав на объекты.
Перед описанием принципов работы с ACL, приведём прототип конструктора класса acl:
public function __construct($user = null, $object_id = 0, $class = '', $section = '')
$user- пользователь, для которого будут проверяться или устанавливаться права на объект, либо от имени которого объект будет создаваться и регистрироваться в системе ACL.$object_id- уникальный идентификатор объекта, для которого будет производиться проверка и модификация правил.$class- имя класса (используется только при регистрации объекта).$section- имя раздела, в контексте которого будет зарегистрирован объект (используется только при регистрации объекта).
Получение прав (в качестве примера возьмём все данные из примера в прошлом пункте):
<?php $user = $userMapper->searchByLogin('editor'); // получаем пользователя 'editor' $news = $newsMapper->searchById(100); // получаем новость с id = 100 (obj_id этой новости примем также равным 100) $acl = new acl($user, $news->getObjId()); $access = $acl->get(); // будет возвращён массив array('edit' => true, 'view' => true); $access = $acl->get('view'); // true $user2 = $userMapper->searchByLogin('visitor'); // получаем пользователя 'visitor' $acl = new acl($user2, $news->getObjId()); $access = $acl->get(); // будет возвращён массив array('edit' => false, 'view' => true); $access = $acl->get('view'); // true $access = $acl->get('edit'); // false $user3 = $userMapper->searchByLogin('hacker'); // получаем пользователя 'hacker' $acl = new acl($user3, $news->getObjId()); $access = $acl->get(); // будет возвращён массив array('edit' => false, 'view' => false); $access = $acl->get('view'); // false $access = $acl->get('edit'); // false ?>
Модификация прав:
<?php $user = $userMapper->searchByLogin('editor'); // получаем пользователя 'editor' $news = $newsMapper->searchById(100); // получаем новость с id = 100 (obj_id этой новости примем также равным 100) $acl = new acl($user, $news->getObjId()); $access = array('edit' => false, 'view' => false); $acl->set($access); // будут установлены запрещающие права для пользователя 'editor' $acl->set('view', true); // будет разрешён просмотр новости $group = $groupMapper->searchByName('banned'); $acl->set('view', true, $group->getId()); // для группы 'banned' будет разрешён просмотр новости //(однако в результате всё равно никто из этой группы просмотреть эту новость не сможет, см. тезис 3) ?>
ACL автоматически проверяет на возможность запуска действия модуля из шаблона. В случае, если права на выполнение действия есть - происходит запуск модуля, если прав нет - показывается страница 403 с соответствующим сообщением для пользователя (либо произвольная, определённая программистом страница). Для управления поведением системы проверки прав используется параметр 403handle. Он может принимать 3 значения:
auto- значение по умолчанию. Означает, что за проверку прав на запуск действия и вывод ошибки ответственен сам фреймворк.manual- проверка прав производится, однако действие запускается в любом случае. Полученное в результате проверки прав значение носит лишь информационный характер для программиста. На основе его программист уже должен сам определить - запускать действие или нет, вывод ошибки доступа также становится ответственностью программиста. Значение можно получить изhttpRequest'а, имя переменной -access, область видимости -SC_PATH.none- проверка прав запуска действия не производится. Действие запускается в любом случае.
Вторым параметром, имеющим отношение к системе проверки прав, является 403tpl. В нём указывается путь до шаблона, который будет отображён, в случае если на запуск метода у текущего пользователя не хватает прав.
Пример запуска модулей:
{load module="news" action="list" section="news" 403handle="manual"} {load module="news" action="list" section="news" 403tpl="news/deny.tpl"}
В случае, если значение 403handle не было изменено, и 403tpl также не был указан - вызывается контроллер simple403Controller, реализация которого и определяет, что именно будет показано в случае, когда у пользователя не хватает прав на запуск действия. Реализация по умолчанию, идущая в фреймворке, представляет собой отображение страницы с именем 403 модуля page, секции page. В случае, если вам нужно переопределить логику работы этого контроллера - воспользуйтесь предусмотренным для этого механизмом переопределения классов, используя резолверы.
obj_id это служебное поле, используемое в системе проверки прав доступа, а также в некоторых служебных целях в ORM. Это поле должно присутствовать во всех таблицах, которые находятся под управлением ACL и ORM, и иметь тип integer. obj_id является уникальным в пределах проекта, т.е. не существует двух объектов с одинаковым значением этого параметра. Доступ к значению obj_id производится с помощью метода ДО getObjId(). Это достигается с помощью таблицы sys_obj_id.
Бывают ситуации, когда необходимо зарегистрировать объект в ACL и дать ему некоторое значение obj_id, но создавать целую сущность ради одной записи в таблице - не оправданно. Тогда следует прибегнуть к механизму так называемых "фейковых" объектов. В терминологии мзз - это такие объекты, которые фактически не существуют в БД, однако зарегистрированы в ACL и имеют свои значения obj_id. Это реализуется с помощью метода getObjectId() тулкита. "Фейковые" объекты идентифицируются по имени. Пример получения obj_id для "фейкового" объекта:
$obj_id = $this->toolkit->getObjectId('sample_fake_object_name');
В результате - если "фейковый" объект с именем sample_fake_object_name не существовал, то он будет создан и возвращено значение его obj_id, если уже существовал - тогда просто возвращён obj_id.
Также бывают ситуации, когда использование obj_id нецелесообразно. В число таких ситуаций можно отнести случаи, когда объекты не участвуют в ACL и когда использование лишнего поля - просто не имеет смысла. В число демо-приложения входят как минимум 2 класса, для которых obj_id не нужно - это userAuth и userOnline. Эти сущности предназначены для реализации "запоминания" аутентификации и хранения пользователей онлайн соответственно. "Отключение" использования obj_id для такого рода классов осуществляется следующим образом:
<?php class userAuthMapper extends simpleMapper { protected $obj_id_field = null; [...] } ?>
Т.е. достаточно защищённое свойство $obj_id_field установить в null.
Метод предназначен для поиска и создания доменного объекта по параметрам запроса, что необходимо для разграничения прав доступа к действию над ним. В качестве аргумента в него передаются параметры запроса в виде массива, а в качестве результата должен возвращаться искомый объект, либо бросаться исключение - если объект не найден. Пример реализации этого метода:
<?php class newsMapper extends simpleMapper { [...] public function convertArgsToObj($args) { $news = $this->searchOneByField('id', $args['id']); if ($news) { return $news; } throw new mzzDONotFoundException(); } } ?>
Метод getAcl класса simple предназначен для усложнения системы прав, в тех случаях, когда штатных средств ACL становится недостаточно. Этот метод принимает аргументом имя экшна, и должен возвращать булево значение, true - в случае если права есть и false в противном случае.
<?php class photo extends simple { [...] public function getAcl($name = null) { $access = parent::getAcl($name); if (in_array($name, array('viewPhoto', 'viewThumbnail', 'view')) && $access) { $access = $this->getAlbum()->getAcl('viewAlbum'); } return $access; } } ?>
В этом примере происходит уточнение прав на некоторые экшны доменного объекта photo. Из кода видно, что доступ к действиям view, viewPhoto и viewThumbnail есть лишь в том случае, если есть доступ к экшну viewAlbum просматриваемого альбома.