Использование библиотек ACL и A2 с Jelly-Auth
Понедельник, 10 мая 2010 г.Рубрика: Web frameworks
Метки: Kohana | обучение
Просмотров: 1976
Подписаться на комментарии по RSS
Briefly in English
In this part of Jelly-Auth Kohana 3 tutorial I try to implement ACL. To be translated.
Итак, продолжаем допиливание макета админки, сделанного на Kohana 3 с помощью расширений Jelly и Jelly-Auth. В прошлый раз мы остановились на том, что мне нужна система управлением правами доступа. Вкратце система ролей может выглядеть примерно так:
- роль 'login' позволяет пользователю логиниться в админку. Фактически, она является признаком того, что пользователь активен, а для (временной) деактивации ее можно удалять;
- роль 'user' соответствует пользователю - не администратору. Позволяет редактировать свой профиль кроме назначения ролей;
- роль 'admin' позволяет осуществлять все возможные действия, за исключением редактирования паролей пользователей (ограничение весьма условное, однако для примера сгодится);
Из уже готовых расширений для Kohana 3, позволяющих реализовать достаточно сложную систему управления правами доступа я отметил для себя A2/Acl и Aacl. Первое внедряет в Кохану Зендовскую систему управления доступом, второе же придерживается другой идеологии, позволяя автоматически задавать доступ к целым классам. И хотя последнее нравится мне гораздо больше, но я не нашел как без своих патчей реализовать вывод элементов страниц в зависимости от разрешений на доступ. Поэтому пока что буду внедрять A2/ACL.
Предварительно рекомендую ознакомиться с руководством Ивана Броткина "A1/A2/Acl - больше, чем Auth"
Сначала я хотел использовать только ACL и , но потом решил попытаться пойти более простым путем, задействовав связку ACL-A2. Прежде всего, скачиваем модули ACL и A2 для Kohana 3. Затем в application/bootstrap.php добавляем
'a2' => MODPATH.'a2', 'acl' => MODPATH.'acl',
Копируем и заполняем конфигурационный файл application/config/a2.php. Нужно отметить, что применяемая для авторизации библиотека должна поддерживать метод get_user(), возвращающий FALSE если пользователь не определен и объект, реализующий интерфейс Acl_Role_Interface в противном случае, а также статический метод instance() для получения экземпляра объекта. Все это в Jelly-Auth есть.
'lib' => array(
'class' => 'Auth_Jelly',
//'params' => array('auth')
),
'params' здесь не играет роли, поскольку Jelly-Auth, как и Auth, не принимает параметров в instance().
выбрасывать исключение при непрохождениии аутентификации мне не нужно
/** * Throws an a2_exception when authentication fails */ 'exception' => FALSE,
записываем систему ролей
'roles' => array ( 'user' => 'login', 'admin' => 'user', ), /* * The name of the guest role * Used when no user is logged in. */ 'guest_role' => 'guest',
Поскольку функциональность моей "админки" ограничивается сугубо работой с пользователями, то в качестве ресурса задаю только модель User
'resources' => array
(
'user' => NULL,
),
Наконец, правила (обратите внимание на пояснения в коде)
'rules' => array
(
'allow' => array
(
array(
'role' => 'user',
'resource' => 'user',
'privilege' => 'edit',
// 'user' может редактировать только свой профиль
'assertion' => array('Acl_Assert_Argument',array('id'=>'id'))
),
array(
// 'admin' может все кроме того, что запретим ниже в deny
'resource' => NULL,
'privilege' => NULL
),
array(
'role' => 'user',
'resource' => 'user',
// 'user' может редактировать только свой пароль
'privilege' => 'edit_pass',
'assertion' => array('Acl_Assert_Argument', array('id'=>'id'))
),
),
'deny' => array(
array(
'role' => 'guest',
// 'guest' не может ничего
'resource' => NULL,
'privilege' => NULL
),
// админ не должен редактировать чужие пароли,
// поэтому для редактирования паролей и создано отдельное правило
array(
'role' => 'admin',
'resource' => 'user',
'privilege' => 'edit_pass',
'assertion' => array('Acl_Assert_NotArgument', array('id'=>'id'))
)
)
)
Последнее правило в секции запрета (deny) означает запрет редактирования пароля если ID пользователя не равен ID админа. Можно было бы реализовать это ограничение и по-другому (раздельно давая разрешения админу на редактирование профиля и редактирование пароля), я так сделал, чтобы проиллюстрировать работу секции deny + создание собственных триггеров. Как видите, я использую триггер неравенства, который легко получается из триггера равенства:
class Acl_Assert_NotArgument implements Acl_Assert_Interface {
protected $_arguments;
public function __construct($arguments)
{
$this->_arguments = $arguments;
}
public function assert(Acl $acl, $role = null, $resource = null, $privilege = null)
{
foreach($this->_arguments as $role_key => $resource_key)
{
if($role->$role_key !== $resource->$resource_key)
{
return TRUE;
}
}
return FALSE;
}
}
Далее нужно модифицировать модель пользователя, чтобы она смогла работать в связке с ACL. Для этого она должна реализовывать интерфейсы роли и ресурса (поскольку является как ролью, так и ресурсом). Соответственно, в классе нужно прописать обязательные методы интерфейсов get_role_id() и get_resource_id():
class Model_User extends Model_Auth_User implements Acl_Role_Interface, Acl_Resource_Interface {
...
/**
* because implements Acl_Role_Interface
*
*/
public function get_role_id()
{
$roles = array();
$user_roles = $this->roles;
foreach ($user_roles as $role)
{
$roles[] = $role->name;
}
return $roles;
}
/**
* because implements Acl_Resource_Interface
*
*/
public function get_resource_id()
{
return 'user';
}
}
Наконец, добрались до модификации контроллера. Можно сделать так, чтобы необходимые изменения затронули лишь методы before() и action_users(). Правда, для этого сначала нужно пропатчить метод A2::logged_in(), который в оригинале не принимает роли в качестве параметра:
class A2 extends A2_Core {
/**
* Alias for logged_in in choosen auth library.
*
* @param string role name
* @return boolean
*/
public function logged_in($role = NULL)
{
return $this->a1->logged_in($role = NULL);
}
}
Теперь мы сможем модифицировать метод before() нашего контроллера:
public function before()
{
parent::before();
$this->a2 = A2::instance('a2');
$this->auth = $this->a2->a1;
$this->user = $this->auth->get_user();
$this->template->menu = $this->_auto_nav();
$this->template->footer = View::factory('admin/footer');
}
Как видите, вводится новое свойство класса $this->a2, которое отвечает за управление правами. Однако чтобы сделать апдэйт легче, отдельно присваиваю $this->auth = $this->a2->a1. Это дает возможность не переписывать код, работавший ранее с функционалом модуля Jelly-Auth.
В методе action_users() добавляются проверки разрешений:
public function action_users($action = NULL, $id = NULL)
{
...
switch ($action)
{
case 'add':
$this->template->title = 'Add user';
$user = Jelly::factory('user');
// проверка разрешения на добавление юзера
if ($this->a2->allowed($user, 'add'))
{
// добавляем пользователя
} else {
$this->template->content = 'You can not add users';
}
break;
case 'edit':
$this->template->title = 'Edit user';
$user = Jelly::select('user')
->load($id);
// проверка разрешения на редактирование юзера
if ($this->a2->allowed($user, 'edit'))
{
...
if ($_POST)
{
...
// только администраторы могут менять роли
if ($this->a2->allowed($user, 'edit_roles'))
{
// задание новых ролей
}
if ($this->a2->allowed($user, 'edit_pass'))
{
// задание нового пароля
}
// сохранение
}
...
} else {
$this->template->content = 'You can not edit other users';
}
break;
case 'del':
$this->template->title = 'Delete user';
$user = Jelly::factory('user');
// проверка разрешения на удаление юзера
if ($this->a2->allowed($user, 'del'))
{
// удаление юзера
} else {
$this->template->content = 'You can not delete users';
}
break;
...
}
} else {
$this->template->title = 'User management';
// view user list
$users = Jelly::select('user')->execute();
$this->template->content = View::factory('admin/user_list')
->set('user', $this->user)
->set('a2', $this->a2)
->set('users', $users);
}
}
Свойство $this->a2 понадобится нам в шаблоне, поскольку оно определяет видимость элементов управления. Например, видность элемента "Добавить пользователя" в шаблоне 'application/views/admin/user_list' определяется таким образом:
if ($a2->allowed($user, 'add'))
{
?>
<div id="add-user">
<a href="/admin/users/add">Add user</a>
</div>
<?php
}
На удивление, все сразу заработало. Разумеется, назвать полнофункциональной данную ACL язык не поворачивается, однако уже виднеется некий прогресс.
Да, чуть не забыл, в таблицу ролей нужно вручную добавить роль, что-то типа:
INSERT INTO `roles` (`id`, `name`, `description`) VALUES (3, 'user', 'Simple user can only edit his own data');
Как обычно, исходники можно скачать / download 53 (zipped ~14 KiB)
Комментариев: 6
Я недавно обнаружил неплохую заготовку для блога на KO3 с использованием Jelly и автозагрузкой представлений.
Довольно оригинально решена авторизация.
К сожалению никакого упоминания о нем в сети,
даже из поиска Github выпал :(
http://github.com/TdroL/schema-blog
Может обсудим данный вариант?...
прошу простить, что долго не отвечал -- проблемы с провайдером :(
спасибо за ссылку, гляну
Посмотрел. Действительно, вариант достаточно подробно проработанный, включая менеджмент страниц. Однако подход "роль=контроллер" и "роль=метод.контроллера" мне не очень нравится, поскольку не позволяет гибко модифицировать шаблоны в зависимости от прав пользователей. В aacl, как мне кажется, аналогичный подход.
Здравствуйте, Александр. Отличная серия статей о kohana и jelly но вот остался у меня небольшой вопрос... Беру за каркас ваш исходник с этой статьи - вес замечательно, но кроме ресурса user у меня еще должен быть request и учетная запись (роль) - service - что я должен сделать?
1 - добавляем в конфиг a2 в resources "поле" request
2 - добавить туда же только в role service и унаследовать его от user?
3 - в контроллере есть функция action_request по аналогии с action_users
4 - создать модель для таблицы request
<?php defined('SYSPATH') OR die('No direct access allowed.'); class Model_Request extends Jelly_Model implements Acl_Resource_Interface{ public static function initialize(Jelly_Meta $meta) { $meta->fields = array( 'id' => new Field_Primary, ); } /** * because implements Acl_Resource_Interface * */ public function get_resource_id() { return 'request'; } } // End User ModelВсе? или я что то пропустил - ведь не работает
Забыл сказать что права назначены
array( 'role' => 'service', 'resource' => 'request', 'privilege' => 'add' ),но зайдя под этим юзером (с ролью service)
не срабатывает
прошу простить за несвоевременный ответ.
В вашем случае
проверяет права текущего юзера (под которым работает контроллер) на добавление ресурса $user. Еслі нужно добавить request, то передайте в шаблон аналогичный объект $request и проверьте