framework.zend.com
Stable релиз 2.0 / 1.12

Zend_Acl часть 2: различные роли и ресурсы, больше о доступе.

К комментариям

От передводчика

Данный текст - перевод статьи Zend_Acl part 2: different roles and resources, more on access автором которой является Jani Hartikainen. Cтатья уже месяца три лежала на форуме, теперь наконец мы выкладываем её на сайте. Надеюсь на днях достать из ящика в столе третью часть, которая аналогично уже три месяца ждёт последних штрихов перед публикацией. Замечания по орфографии и стилистике приветствуются. As usual I’m thank Alexander Mahomet for his friendship and help.

Введение

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

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

В контексте Zend_Acl, доступ к ресурсам предоставляется ролям. Ролью могут выступать: имя пользователя, группа к которой принадлежит пользователь, или роль может быть назначена через панель администрирования.

Так как Zend_Acl только определяет «абстрактные» роли, ресурсы и привилегии, необходимо наладить их взаимодействие при использовании. Как это сделать? Читайте дальше чтобы узнать! В свою очередь я постараюсь показать больше возможных способов предоставления и ограничения доступа.

Способы работы с обособленными пользователями и группами.

Сформулируем нашу задачу: мы должным иметь возможность ограничить доступ к некоторым ресурсам для определённых пользователей или групп. Как этого добиться?

В Zend_Acl роли идентифицируются с помощью некоторой строки. Поэтому мы можем выработать свою собственную схему имён для ролей. Для пользователей идентификатор роли будет обозначаться как "user-<имя_пользователя> " а для групп как "group-<имя_группы> ".

$acl->addRole(new Zend_Acl_Role('user-john'));
$acl->addRole(new Zend_Acl_Role('group-accounting'));

Проверка доступа согласно ACL в сценарии будет выглядеть следующим образом:

// переменная $user содержит какой – то абстрактный объект User
$name = $user->getName();
$groups = $user->getGroups();

$roles = array('user-' . $name);
foreach($groups as $g)
$roles[] = 'group-' . $g->getName();

foreach($roles as $r) {
if($acl->isAllowed($r, 'someresource', 'someprivilege') {
//разрешено осуществить какие – либо действия
}
}

Усовершенствование примера user/group (пользователь/группа) c помощью использования Zend_Acl_Role_Interface

В нашем предыдущем примере есть небольшая проблема – строковые идентификаторы ролей.

Мы решили обозначать роли как "user-<имя-пользователя>" и "group-<имя-группы>". Но что случится если наш код для работы с ACL придётся дополнительно использовать в другом месте программы? Например мы можем с лёгкостью скопировать этот код в нужное место. В итоге такой подход может затруднить внесение последующих изменений, их контроль и нарушит целостность системы. А в случае если мы добавим новые типы ролей этот подход может привести к большому беспорядку в исходном коде.

К счастью у нас есть довольно простой выход – реализовать в наших обьектах Zend_Acl_Role_Interface.

Так как в нашем примере "пользователь" (user) и "группа" (group) реализованны в виде классов, то мы можем сделать так, чтобы эти классы реализовывали интерфейс роли (Zend_Acl_Role_Interface). С помощью такого подхода мы сможем напрямую передавать их в методы ACL, вместо того чтобы получать их строковые идентификаторы, которые буду переданы в соответсвующие методы.

class User implements Zend_Acl_Role_Interface {
// код класса

// этот метод для реализации интерфейса роли
public function getRoleId() {
return 'user-' . $this->_name;
}
}

Итак, мы добавили метод getRoleId(), который возвращает идентификатор роли для объекта. Мы так же добавим такой же метод для класса "Group", который отличается тем что возвращает идентификатор с префиксом "group". Код для проверки ACL теперь будет выглядеть следующим образом:

//  просто помещаем пользователя, и группы в массив который будем обрабатывать
$roles = array($user) + $user->getGroups();
foreach($roles as $r) {
if($acl->isAllowed($r, 'someresource', 'someprivilege') {
// разрешённые действия
}
}

Различные типы ресурсов

Так же как и у роли, идентификатором ресурса является строка. Подобно роли, ресурс может быть представлен в виде объекта, реализующего Zend_Acl_Resource_Interface. Я предлагаю Вам самим написать соответствующий код, используя в качестве примера ранее представленный код для роли.

Давайте разберём небольшой пример с файлами. Помните наш плагин из первой части? Используем его снова.

class My_Plugin_Acl extends Zend_Controller_Plugin_Abstract {
private $_acl = null;

public function __construct(Zend_Acl $acl) {
$this->_acl = $acl;
}

public function preDispatch(Zend_Controller_Request_Abstract $request) {
// как и в предыдущем примере аутентифицированные пользователи будут
// иметь роль пользователь («user»)
$role = (Zend_Auth::getInstance()->hasIdentity())
? 'user'
: 'guest';

// Для данного примера мы будем использовать имя контроллера в качестве
// ресурса
$resource = $request->getControllerName();

if(!$this->_acl->isAllowed($role, $resource, 'view')) {
// Если у пользователя нет доступа, мы будем его перенаправлять в
// другое место с помощью изменения запроса
$request->setModuleName('auth')
->setControllerName('auth')
->setActionName('login');
}
}
}

Давайте изменим этот плагин таким образом , чтобы мы смогли использовать файлы совместно с нашим ACL.

Создание ресурсов

Вначале создадим тип ресурса для контроллеров. Так как адаптировать сам контроллер - не тривиальная задача, и мы не будем это делать ради тестирования ACL. Затем рассмотрим создания типа ресурсов для файлов.

class Resource_Controller implements Zend_Acl_Resource_Interface {
public function __construct($id) {
$this->_id = $id;
}

public function getResourceId() {
return 'controller-' . $this->_id;
}
}

Мы создали класс – ресурс для контроллеров, так как создать контроллер в ACL является довольно нетривиальной задачей. Этот класс упрощает нашу работу, и позволяет производить проверку на сопоставление ACL.

Теперь вам вероятно необходимо создать класс – модель который будет представлять файл, реализовав интерфейс Zend_Acl_Resource_Interface.

class File implements Zend_Acl_Resource_Interface {
/* мы можем создать какие –то методы для обработки файла */

public function getResourceId() {
return 'file-' . $this->_name;
}
}

В данном коде предполагается, что у вашей модели есть имя (свойство $_name). Именно это имя будет использовано в качестве идентификатора ресурса.

Модернизация плагина

Теперь, изменим плагин Acl. Для начала добавим возможность использования ресурсов – контроллеров. Затем возможность использовать разработанный ранее класс User в качестве роли.

public function preDispatch(Zend_Controller_Request_Abstract $request) {
// Предполагается что в данный момент идентификатор(identity) является
//объектом класса User
$user = Zend_Auth::getInstance()->getIdentity();

// Нам необходимо создать новый ресурс-контроллер использую имя
//контроллера
$resource = new Resource_Controller($request->getControllerName());

if(!$this->_acl->isAllowed($user, $resource, 'view')) {
// Если у пользователя нет доступа, перенаправляем его изменив
// объект запрос.
$request->setModuleName('auth')
->setControllerName('auth')
->setActionName('login');
}
}

Помните, при создании класса "пользователь" (User) мы реализовали интерефейс роли? Теперь подобно ресурсу мы можем передавать объект User в качестве параметра метода isAllowed().

Важно: запомните, при создании ACL Вам необходимо использовать классы Resource_Controller и User. В противном случае работать не будет!

// $someUser является объектом класса User
$this->addRole($someUser);

// $someController someUser является объектом класса Resource_Controller
$this->add($someController);

$this->allow($someUser, $someController, 'view');

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

Создание нового плагина

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

Этот плагин будет очень похож на предыдущий.

class My_Plugin_Acl_File extends Zend_Controller_Plugin_Abstract {
private $_acl = null;

public function __construct(Zend_Acl $acl) {
$this->_acl = $acl;
}

public function preDispatch(Zend_Controller_Request_Abstract $request) {
//This too will use the user object from zend_auth
$user = Zend_Auth::getInstance()->getIdentity();

// вместо того чтобы использовать имя контроллера мы будем использовать
// параметр из запроса
$filename = $request->getParam('file');

//If the request is not for a file we can just exit here without doing anything
if(empty($filename))
return;

$resource = new File();
$resource->setName($filename);

if(!$this->_acl->isAllowed($role, $resource, 'view')) {
$request->setModuleName('auth')
->setControllerName('auth')
->setActionName('login');
}
}
}

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

Замечание: вместо того чтобы создавать новый объект File, лучше создать какой – то класс который представляет модель файла, и проверяет существования файла.

Чуть больше о предоставлении и ограничении доступа.

Как уже было показано несколько раз, мы можем разрешить доступ к ресурсу ACL с помощью метода allow(). Однако, есть некоторые дополнительных возможности о которых я ещё не рассказал.

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

//myrole получит привелегию dosomething ко всем ресурсам.
$acl->allow('myrole', null, 'dosomething');

Подобным образом предоставление привилегии null, даёт все возможные привилегии к ресурсу.

//  myrole получит все возможные привилегии к someresource
$acl->allow('myrole', 'someresource');

Метод isAllowed() имеет схожие возможности. Если при проверке на доступ к ресурсу, вы не укажите привилегию, метод isAllowed будет требовать наличие всех возможных привилегий к ресурсу у роли.

Вы так же можете разрешить доступ ко всем ресурсам со всеми привилегиями. Для этого при вызове метода allow необходимо оставить пустыми и имя ресурса и имя привилегии. А если вы опустите и имя роли, то вы разрешите доступ ко всем ресурсам, со всеми привилегиями и для всех ролей. Это неплохой приём для создания «чёрных списков», где определённым пользователям специально запрещён тот или иной доступ к определённым ресурсам.

В заключение.

Мы уже познакомились с большим количеством способов использования Zend_Acl, но ещё о многом стоит поговорить. В следующей статье мы познакомимся с динамическим созданием ACL из базы данных или файлов. И конечно же усовершенствуем наши ACL и плагины.

Замечание: будет неплохо изменить плагины из этой статьи таким образом, чтобы они предварительно проверяли наличие идентификатора пользователя ( identity ). Так как в случае отсутствия идентификатора метод isAllowed() вызовет исключение ( Exeption ) или будет обладать непредвиденным поведением. Вам так же необходимо проверять наличие ресурса/роли внутри ACL, чтобы не вызвать исключение ( exeption ).

Большинство примеров из статьи будут работать гораздо лучше если ACL создать динамически, из базы данных.

Первая часть. Третья часть.

Тема на форуме.

метки: Zend_Acl, ACL
Лучший способ следить за обновлениями сайта это подписаться на RSS
Если информация была полезной для вас, вы можете поддержать сайт.
Комментарии:
IgorN 20.07.2009 16:12 #
Интересная статья.
Ответить
Baron 21.07.2009 04:31 #
Ребят, спасиб за ваш труд... очень помог в начинаниях с Zend framework. Сейчас уже не совсем нуб в ZF. Вот решил первый раз зайти на первоисточник и заметил, что у него уже есть и третяя часть... я не собираюсь вас упрекать в медлительности))) не имею на этого прав, уже спасибо что делаете такой труд, но иногда не хватает такого материала, чтобы до конца уже разобраться с этими вещами...

Спасибо за понимание :-)
Ответить
Александр Махомет 21.07.2009 12:55 #
На днях будет третья.
Ответить
Максим 16.05.2011 13:46 #
Полезная статья,спасибо!:)
Ответить
Уважаемые пользователи. Комментарии не для того чтобы:
  1. Спрашивать почему у вас не работает код, для этого есть тема форума закрепленная за статьей.
  2. Спрашивать как реализовать ту или иную функциональность, для этого необходимо создать свою тему на форуме.

Комментарии для того чтобы: высказать свое аргументированное мнение о статье, указать какие участки вызывают непонимание, что нужно исправить/улучшить, просто сказать спасибо.

Комментарии имеют древовидную структуру.
Если вы хотите ответить на определенный комментарий - нажмите на ссылку "Ответить" возле этого комментария.

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