Search
Write a publication
Pull to refresh

Zend Framework: Прямое использование Zend_Acl

Поделюсь с вами своим опытом в этом вопросе.

Задача:

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

Тип сайта:

интранет.

Что будет использоваться:

Oracle, Zend Framework, Zend_Acl

1. База данных


Используем 3 таблицы, USERS, USERS_RIGHTS, MODULES

USERS:
CREATE TABLE "USERS" (
"ID" NUMBER NOT NULL ,
"USERNAME" VARCHAR2(50 BYTE) NULL ,
"PASSWORD" VARCHAR2(32 BYTE) NULL
);

USERS_RIGHTS:
CREATE TABLE "USER_RIGHTS" (
"USER_ID" NUMBER NULL ,
"RULE" VARCHAR2(100 BYTE) NULL ,
"TYPE" VARCHAR2(500 BYTE) NULL
);

MODULES:
CREATE TABLE "MODULES" (
"ID" VARCHAR2(20 BYTE) NOT NULL ,
"SHORTNAME" VARCHAR2(200 BYTE) NULL ,
"NAME" VARCHAR2(200 BYTE) NULL ,
"LINK" VARCHAR2(200 BYTE) NULL ,
"PARENT_ID" NUMBER NULL ,
"LANGUAGE" VARCHAR2(2 BYTE) DEFAULT 'lv'  NULL
);

Сайт построен таким образом, что модуль и контроллер должны быть указанны всегда (/module/controller). В результате Можно держать уникальные записи для каждого раздела. В моем случае это MODULES.SHORTNAME. В этом поле будут хранится связки module-controller, module-controller-action.

Добавим немного данных в таблицу MODULES:
INSERT INTO "MODULES" VALUES ('1', 'data', 'Данные', '/data', '0', 'ru');
INSERT INTO "MODULES" VALUES ('2', 'data-users', 'Пользователи', '1', 'ru');

В таблицу USERS:
INSERT INTO "USERS" VALUES ('1', 'demo_username', 'demo_password');

В таблицу USER_RIGHTS:
INSERT INTO "USER_RIGHTS" VALUES ('1', 'data', 'read');
INSERT INTO "USER_RIGHTS" VALUES ('1', 'data-users', 'read,add,edit,delete,search,export,history');

2. Настройка Zend_Acl


Следующий шаг будет изменение booststrap.php файла. В него нужно добавить запись о настройке Zend_Acl. Так как я использую классову структуру этого файла, то мой вариант может отличаться от того, как вы это реализуете.
public static function setupAcl() {
    	$auth = Zend_Auth::getInstance();
    	if ($auth->hasIdentity()) {
    		self::$acl = new Zend_Acl();
                self::$acl->addRole(new Zend_Acl_Role(null));

	        $db = self::$registry->database;

    		$sql = "SELECT shortname FROM modules WHERE ".$db->quoteInto("language = ?", $auth->getIdentity()->LANGUAGE);
	        $modules = $db->fetchAll($sql);

	        foreach ($modules as $v)
	        	self::$acl->add(new Zend_Acl_Resource($v['SHORTNAME']));

	        $sql = "SELECT rule,type FROM user_rights WHERE ".$db->quoteInto("user_id  = ?", $auth->getIdentity()->ID);
	        $rules = $db->fetchAll($sql);

	        foreach ($rules as $v) {
	        	$perms = explode(',', $v['TYPE']);
	        	if (self::$acl->has($v['RULE'])) self::$acl->allow(null, $v['RULE'], array_values($perms));
	        }

	        self::$registry->set('Zend_Acl', self::$acl);
    	}
    }

Немного обьяснения:
self::$acl->addRole(new Zend_Acl_Role(null));

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

Далее мы выбираем все модули из таблицы (да, я знаю что это можно оптимизировать, но пока мы опускаем эту необходимость :)).
Присваиваем все модули и создает ресурсы под них.

Следующий шаг, это выбор прав пользователся: логика здесь такова, что мы выбираем все модули доступные пользователю и тип доступа, который хранится через запятую в поле USER_RIGHTS.TYPE.

И последним шагом является присвоение этих самых прав. И теперь когда мы будет проверять $acl->isAllowed(null, 'data-users', 'read') будем получать true, а если напишем вместо read какой-либо не существующий тип, то получим false. В общем то, к чему стремились.

3. Плагин для Фронт контроллера



class System_Rights extends Zend_Controller_Plugin_Abstract {
	public function preDispatch(Zend_Controller_Request_Abstract $request) {
		$db = Zend_Registry::get('Zend_Db');
		$query = array();
        $query[] = $request->getModuleName();

        if ($request->getControllerName() != 'index') $query[] = $request->getControllerName();
        if ($request->getActionName() != 'index') $query[] = $request->getActionName();

        /* define module/controller/action like /data/users/add */
        define('_CURRENT_MODULE', implode('-', $query));

        /* define current main module/controller like /data/users */
        if (count($query) > 2) array_pop($query);
        define('_CURRENT_MAIN_MODULE', implode('-', $query));
	}
}

Это пойдет в наш bootstrap.php:
$frontController->registerPlugin(new System_Rights());

Как вы могли заметить в планине нет ничего особенного… в принципе там нет ничего :) Просто это не весь функционал, который был в него заложен, но для ознакомление с этим вариантом этого достаточно.

4. View


Теперь можно запускать проверки в шаблонах в виде:
<?php if ($this->acl->has(_CURRENT_MAIN_MODULE) && $this->acl->isAllowed(null, _CURRENT_MAIN_MODULE, 'read')):?>
Все окей прав достаточно
<?php else:?>
Уля ля, куда это вы ?
<?php endif;?>


_CURRENT_MAIN_MODULE или _CURRENT_MODULE использовать в зависимости от надобности и выданных прав.
В контроллерах также нужно проверять $acl->isAllowed(); или же можно дописать немного плагин, который сам будет проверять, может ли человек смотреть тот раздел сайта, в котором находится.

Если кто-то заинтересуется более подробно этим вопросом, могу выложить demo код, который будет включать в себя все то, что мы здесь проделали :)
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.