Практикум Zend Framework. Часть третья:Zend_Acl

ZendFramework

Сегодня мы рассмотрим пример создания Acl для системы с большим количеством ролей и ресурсов.

Хабраюзер Anexroid любезно предоставил описание такого проекта:

Существуют следующие права доступа: Админ, с доступом в админку. Причем в админке порядка 20-30
разделов, 5 админов. У всех доступ разный. То есть у кого-то 2-3 раздела, у кого-то — все 20. Все
пункты меню храняться в БД. Пользователь — зарегестированный на сайте. Может создавать фотоальбомы,
комментировать новости без ввода капчи и т.д. + всё то, что может делать гость.Предприятие — имеет
личную страницу в каталоге, в зависимости от купленного пакета — различные пункты в личном кабинете.
Ну и гость, который может всё неограниченно просматривать. Комментарии — с капчей.Еще есть
консультанты — отвечают на вопросы в консультациях.Причем у предприятий и консультантов — нет
регистрации, добавляются админом. +опять же, все таблицы в БД отдельные — отдельно админы, отдельно
пользователи, отдельно — предприятия, отдельно — консультанты.

Для начала определимся с ролями и ресурсами, составим иерархии наследования ресурсов и ролей:

Иерархия ролей

В нашем примере верхняя часть иерархия ресурсов будет совпадать по структуре с иерархией ролей. Для того чтобы, удобно отобразить иерархию ресурсов, добавим в наш список прав доступа абстрактные ресурсы для каждой роли, кроме Admin1-N, CompanyPackage1-N. Это так, потому что ресурсы для статусов Guest, User и общие ресурсы для всех админов и компаний имеют простую древовидную структуру, чего нельзя сказать про нижние узлы дерева в которых будут пересечения. Например Admin1 и Admin2 могут иметь доступ к общему ресурсу "Добавление новостей", а деревья ресурсов Zend_Acl, к сожалению, не поддерживает множественное наследование. Поэтому ресурсы для ролей Admin1-N, CompanyPackage1-N будут распределятся как исключения, явно назначаясь нужным ролям.

Иерархия ресурсов

Итак, мы разобрались с иерархией ресурсов, теперь создадим непосредственно Acl. Для этого расширим класс Zend_Acl:

<?php
class Acl extends Zend_Acl {
    public function __construct() {
        // Добавляем роли
        $this->addRole('guest');
        $this->addRole('user', 'guest');
        $this->addRole('admin', 'user');
        $this->addRole('company', 'user');
        $this->addRole('company-package-1', 'company');
        $this->addRole('company-package-2', 'company');
        $this->addRole('company-package-3', 'company');
        // ...
        $this->addRole('admin-1', 'admin');
        $this->addRole('admin-2', 'admin');
        // ...
        $this->addRole('admin-5', 'admin');

        //Добавляем ресурсы
        //
        // РЕСУРСЫ ГОСТЯ !
        $this->add(new Zend_Acl_Resource('guest_res'));
        // перечисляем все ресурсы гостя, как дочерние для guest_res
        $this->add(new Zend_Acl_Resource('add-comments-with-captcha'), 'guest_res');

        // РЕСУРСЫ ПОЛЬЗОВАТЕЛЯ !
        $this->add(new Zend_Acl_Resource('user_res'));
        // перечисляем все ресурсы пользователя, как дочерние для user_res
        $this->add(new Zend_Acl_Resource('add-comments'), 'user_res');

        // РЕСУРСЫ АДМИНА !
        $this->add(new Zend_Acl_Resource('admin_res'));
        // перечисляем общие ресурсы для всех админов, как дочерние для admin_res
        $this->add(new Zend_Acl_Resource('admin-tools-list'), 'admin_res');

        // РЕСУРСЫ КОМПАНИИ !
        $this->add(new Zend_Acl_Resource('company_res'));
        // перечисляем общие ресурсы для пакетов компаний, как дочерние для company_res
        $this->add(new Zend_Acl_Resource('show-company-statistics'), 'company_res');

        // Специфические ресурсы для админов и компаний, ничего не наследуют
        $this->add(new Zend_Acl_Resource('advertise'));
        $this->add(new Zend_Acl_Resource('add-company'));

        //Выставляем права, по-умолчанию всё запрещено
        $this->deny(null, null, null);
        $this->allow('guest', 'guest_res', 'show');
        $this->allow('user', 'user_res', 'show');
        $this->allow('admin','admin_res', 'show');
        $this->allow('company','company_res', 'show');

        // Выставляем ресурсы для пакетов компаний и админов
        $this->allow('company-package-1','advertise', 'show');
        $this->allow('admin-1','add-company', 'show');
    }
}

И сразу проверим что все работает как мы ожидали:

<?php
// ...
echo $acl->isAllowed('guest', 'add-comments-with-captcha', 'show')?'yes':'no'; // yes
echo $acl->isAllowed('guest', 'add-comments', 'show')?'yes':'no'; // no
echo $acl->isAllowed('admin-1', 'add-company', 'show')?'yes':'no'; // yes
echo $acl->isAllowed('company-package-2', 'advertise', 'show')?'yes':'no'; // no

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

Также, очевидно, что данный код не соответствует тому, что пакеты компании и администраторы будут добавляться, а права других ролей могут меняться со временем. Поэтому вам нужно будет хранить данные в базе данных и, либо строить объект по требованию, извлекая необходимые данные, или хранить сериализованный экземпляр класса Acl в каком либо хранилище (например memcached). Этот выбор за вами, лично мне больше нравится второй вариант.

В первом посте данного цикла я рассматривал как можно создать Acl используя в качестве ресурсов строки типа "controller/action", что очень удобно в небольших проектах.