Использование библиотек ACL и A2 с Jelly-Auth

Просмотров: 4788Комментарии: 6
Web frameworks

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 79 (zipped ~14 KiB)

Комментариев: 6 RSS

1 Zares 15-05-2010 15:12

Я недавно обнаружил неплохую заготовку для блога на KO3 с использованием Jelly и автозагрузкой представлений.

Довольно оригинально решена авторизация.

К сожалению никакого упоминания о нем в сети,

даже из поиска Github выпал :(

http://github.com/TdroL/schema-blog

Может обсудим данный вариант?...

2 Александр Купреев 17-05-2010 14:51

прошу простить, что долго не отвечал -- проблемы с провайдером :(

спасибо за ссылку, гляну

3 Александр Купреев 20-05-2010 20:08

Посмотрел. Действительно, вариант достаточно подробно проработанный, включая менеджмент страниц. Однако подход "роль=контроллер" и "роль=метод.контроллера" мне не очень нравится, поскольку не позволяет гибко модифицировать шаблоны в зависимости от прав пользователей. В aacl, как мне кажется, аналогичный подход.

4 Александр 02-03-2011 07:31

Здравствуйте, Александр. Отличная серия статей о 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

Все? или я что то пропустил - ведь не работает

5 Александр 02-03-2011 07:34

Забыл сказать что права назначены

array(
                'role'      => 'service',
                'resource'  => 'request',
                'privilege' => 'add'
                ),

но зайдя под этим юзером (с ролью service)

if ($a2->allowed($user, 'add'))

не срабатывает

6 Александр Купреев 03-03-2011 22:06

прошу простить за несвоевременный ответ.

В вашем случае

$a2->allowed($user, 'add')

проверяет права текущего юзера (под которым работает контроллер) на добавление ресурса $user. Еслі нужно добавить request, то передайте в шаблон аналогичный объект $request и проверьте

$a2->allowed($request, 'add')
Оставьте комментарий!


Используйте нормальные имена.

     

  

Если вы уже зарегистрированы как комментатор или хотите зарегистрироваться, укажите пароль и свой действующий email. При регистрации на указанный адрес придет письмо с кодом активации и ссылкой на ваш персональный аккаунт, где вы сможете изменить свои данные, включая адрес сайта, ник, описание, контакты и т.д., а также подписку на новые комментарии.

MaxSiteAuth. Войти через loginza

(обязательно)