13 октября 2011 г.

Zend и контроль доступа

Основы контроля доступа в приложениях на зенде разжёваны в интернетах вдоль и поперёк, поэтому подробно рассматривать эту тему я смысла не вижу.
Если вкратце:
  1. Создали ACL (статически или динамически)
  2. Определили роль юзера (как правило используются сессии)
  3. Проверили наличие привилегии к ресурсу
  4. Разрешили или отказали (способов отказа как правило несколько, от редиректа до исключения)
Если в Вашем приложении эти четыре пункта часто повторяются, рекомендую завернуть их в некоторую абстракцию. Свой вариант этой обёртки в виде action helper-а я и приведу.

Собственно сам ACL (простой и меняется крайне редко, поэтому жёстко захардкожен)

Copy Source | Copy HTML
  1. /*
     * Access control list 
     */

  2. class MyAcl extends Zend_Acl

  3. {

  4.     public function __construct()

  5.     {

  6.         //roles

  7.         $this->addRole(new Zend_Acl_Role('guest'));

  8.         $this->addRole(new Zend_Acl_Role('user'), 'guest');

  9.         $this->addRole(new Zend_Acl_Role('demo'), 'user');

  10.         $this->addRole(new Zend_Acl_Role('moder'), 'user');

  11.         //resourses

  12.         $this->add(new Zend_Acl_Resource('profile')); //всё что связанно с профилем юзера

  13.         $this->add(new Zend_Acl_Resource('autosearch')); //весь автопоиск юзера

  14.         $this->add(new Zend_Acl_Resource('logs')); //логи скриптов

  15.         $this->add(new Zend_Acl_Resource('others')); //другие фишки для залогиненых

  16.         //юзеры

  17.         $this->allow( 'user', 'profile', array('view', 'edit') );

  18.         $this->allow( 'user', 'autosearch', array('view', 'add', 'del'));

  19.         //$this->allow( 'user', 'monitoring' );

  20.         $this->allow( 'user', 'others', array('searchLimitX2'));

  21.         //модеры

  22.         $this->allow( 'moder', 'logs', array('view'));

  23.     }

  24. }

Action хелпер для проверки прав

Copy Source | Copy HTML
  1. /*
     * хелпер для проверки прав пользователя
     *
     * инстанцирует наш ACL
     * учитывает незалогиненых юзеров как гостей
     * 
     */

  2. class My_Action_Helper_CheckAccess extends Zend_Controller_Action_Helper_Abstract

  3. {

  4.     private $_hasIdentity;

  5.     private $_role;

  6.     private $_acl;

  7.     public function init()

  8.     {

  9.         $auth = Zend_Auth::getInstance();

  10.         $this->_hasIdentity = $auth->hasIdentity();

  11.         $this->_role = ( $this->_hasIdentity ) ? $auth->getStorage()->read()->role : 'guest';

  12.         $this->_acl = new MyAcl();

  13.     }

  14.     /*
         * @param string $mode (redirect | return | excep )
         */

  15.     public function check($resourse, $privileges, $mode)

  16.     {

  17.         $cntr = $this->getActionController();

  18.         $result = $this->_acl->isAllowed( $this->_role, $resourse, $privileges );

  19.         switch ($mode)

  20.         {

  21.             case 'redirect':

  22.                 if( !$result )

  23.                 {

  24.                     $url = $cntr->getHelper('url')->url(array(),'staticLogin',true);

  25.                     if( $this->_hasIdentity )

  26.                     {

  27.                         $message = 'Попробуйте перезайти под другим пользователем.';

  28.                     }else{

  29.                         $message = 'Войдите в систему';

  30.                         $returnUrl = $this->getRequest()->getServer('REQUEST_URI', $cntr->getHelper('url')->url(array(),'userProfile',true) );

  31.                         $url .= "?return={$returnUrl}";

  32.                     }

  33.                     $cntr->getHelper('flashMessenger')->addMessage( array( 'error' => "У вас недостаточно прав для просмотра данной страницы. {$message}.") );

  34.                     $cntr->getHelper('redirector')->gotoUrlAndExit( $url );

  35.                 }

  36.                 break;

  37.             case 'excep':

  38.                 if( !$result )

  39.                     throw new Exception("Access denied");

  40.                 break;

  41.             case 'return':

  42.                 return $result;

  43.                 break;

  44.             default:

  45.                 throw new Exception('Undefined mode (helper access)');

  46.                 break;

  47.         }

  48.     }

  49.     public function direct($resourse = null, $privileges = null, $mode = 'return')

  50.     {

  51.         return $this->check($resourse, $privileges, $mode );

  52.     }

  53. }

Использование (внутри действия)

редирект на форму входа, если нет привелегии view к ресурсу profile
$this->_helper->checkAccess('profile','view','redirect');

исключение, если нет прав на весь ресурс profile
$this->_helper->checkAccess('profile',null,'excep');

возврат результата проверки наличия привилегии add к ресурсу autosearch
$this->_helper->checkAccess('autosearch','add')




Несколько пояснений:
  • Хук с init() используется не по своему прямому назначению и может быть заменён на __construct()
  • Параметр $mode определяет реакцию на отсутствие доступа (редирект на форму входа, исключение или возврат результата)
  • В случае редиректа на страницу входа авторизованные пользователи и гости увидят разные сообщения
  • Также в случае редиректа отправляется параметр return, для возврата пользователя обратно на запрошенную страницу.
  • Метод direct() риализует паттерн стратегия (или я чтото напутал?) и нужен только для удобства использования

ПыСы: если планируется использовать данный хелпер не только через брокер помощников, но и напрямую (например через Zend_Controller_Action_HelperBroker::getStaticHelper('CheckAccess'); ), то необходимо заменить init() на __construct(). Иначе есть риск пропустить этап инита =)



2 комментария:

Анонимный комментирует...

А почему используете Action Helper?
Не проще ли юзать плагин фронт-контроллера, в котором реализовать метод preDispatch?
Action Helper надо будет вызывать в каждом контроллере, а плагин всего лишь раз прописать и все...

esemi комментирует...

А зачем тут плагин?

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

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