Автор Тема: Косяк механизма авторизации через плагин Zend_Controller  (Прочитано 4908 раз)

0 Пользователей и 1 Гость смотрят эту тему.

Оффлайн dmig

  • Завсегдатай
  • **
  • Сообщений: 61
  • Карма: 0
Один из рецептов прикручивания авторизации -- использование плагина контроллера (описан тут: http://www.kachayev.ru/2009/02/02/autentifikaciya-polzovatelej-zend_auth-zend_acl-moj-recept/), который в preDispatch() выполняет проверку и редирект, если надо. Насколько помню, в ZF планируется (рекомендуется?) использовать этот метод.
В оригинальном плагине используется "внутренний редирект", т.е. юзер не увидит, что отработало не то, что он просил.
В случае с отсутствием прав показать затычку на месте страницы -- допустимо, а в случае, когда пользователь просто не авторизован -- неправильно. Когда пользователь не авторизован, надо явно кидать его на страницу авторизации. Вот тут и вылезет проблема.

У метода плагина есть огромный минус, который я сейчас пытался решить: после редиректа в плагине, диспетчеризация не прерывается! т.е. запрошенное действие выполняется в любом случае.
Способа прервать обработку в Zend_Controller_Front нет. Либо я его не нашёл, хотя вроде посмотрел всё, к чему обращается Zend_Controller_Front::dispatch().

Вариант решения раз: использовать костыли Zend_Auth::hasIdentity() везде, где нужно обращение к данным пользователя.
Вариант решения два: проверять авторизацию в базовом классе контроллера (а наличие прав -- можно и в плагине).

Второй метод описывается в сети чаще, однако я, увидев нечто новое, а так же увидев обсуждение этого механизма в предлагаемых фичах ZF, решил попробовать. Придётся теперь отказаться от нехорошего решения.

В общем, хочу предостеречь остальных -- есть такой вот архитектурный недостаток механизма плагинов.

Оффлайн lcf

  • Модератор
  • Герой
  • *****
  • Сообщений: 2468
  • Карма: 153
    • Homepage
Цитировать
У метода плагина есть огромный минус, который я сейчас пытался решить: после редиректа в плагине, диспетчеризация не прерывается! т.е. запрошенное действие выполняется в любом случае.
А как вы редиректите? С помощью редиректора? Если без него то можно просто делать exit; и вуаля, цикл диспетчеризации прервался.
Если вы используете редиректор, то у него есть набор методов типа блаблабла и выйти (ну один точно ^_^), например gotoUrlAndExit

Оффлайн IgorN

  • Team
  • Герой
  • ***
  • Сообщений: 2908
  • Карма: 90
    • Мой сайт
gotoUrlAndExit - я часто пользуюсь
Мой сайт: http://igor-negrutsa.info/
Я знаю только то,что ничего не знаю, а многие не знают даже этого.

Оффлайн dmig

  • Завсегдатай
  • **
  • Сообщений: 61
  • Карма: 0
А как вы редиректите? С помощью редиректора? Если без него то можно просто делать exit; и вуаля, цикл диспетчеризации прервался.
$router Zend_Controller_Front::getInstance()->getRouter();
$url $router->assemble($params);
$this->getResponse()->setRedirect($url);
$this->getResponse()->sendResponse();

некрасиво конечно, с exit, но попробую

Оффлайн lcf

  • Модератор
  • Герой
  • *****
  • Сообщений: 2468
  • Карма: 153
    • Homepage
А как вы редиректите? С помощью редиректора? Если без него то можно просто делать exit; и вуаля, цикл диспетчеризации прервался.
$router Zend_Controller_Front::getInstance()->getRouter();
$url $router->assemble($params);
$this->getResponse()->setRedirect($url);
$this->getResponse()->sendResponse();

некрасиво конечно, с exit, но попробую
не красиво, да, согласен, поэтому пара моментов:

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

2. Еесли по каким-то (пока не ясным) причинам понадобилось бы сделать именно так, то я бы сделал следующее:

class My_Controller_Response_Http extends Zend_Controller_Response_Http
{

&
#160;   public function sendResponseAndExit()
    {
        $this->sendResponse();
        exit();
    }

    public function sendHeadersAndExit()
    {
        $this->sendHeaders();
        exit();
    }

}

и использовать этот объект ответа в замен (метод фронт контроллера кажется называется setResponse, в апи посмотрите подробнее)
собсна для того ооп и придумали, чтобы иметь возможность такое делать. и это конечно не косяк авторизации, она тут вообще не при чем.

3. Для редиректа достаточно отправить заголовки.
« Последнее редактирование: Май 14, 2009, 14:15:43 от lcf »

Оффлайн Czar

  • Мастер
  • ****
  • Сообщений: 316
  • Карма: 14
если я правильно понял, то у меня похожая сейчас задача..
делаю разделение прав на  сайте... пока что делается с трудом и объект ACL строится на основе базы, но это всё такое...
сделал плагин
<?php
class Aclplugin extends Zend_Controller_Plugin_Abstract
{
    public function 
preDispatch(Zend_Controller_Request_Abstract $request)
    {
        
$frontendOptions = array(
	
	
   
'lifetime' => 7200// время жизни кэша - 2 часа
	
	
   
'automatic_serialization' => true
	
	
);

	
	
$backendOptions = array(
	
	
	
### директория, в которой размещаются файлы кэша
	
	
	
'cache_dir' => Zend_Registry::get('config')->path->cache
	
	
);

	
	
// получение объекта Zend_Cache_Core
	
	
$cache Zend_Cache::factory('Core',
	
	
	
	
	
	
	
	
	
 
'File',
	
	
	
	
	
	
	
	
	
 
$frontendOptions,
	
	
	
	
	
	
	
	
	
 
$backendOptions);
	
	

	
	
### получаем экземпляр Acl
	
	
if(!
$acl $cache->load('acl'))
	
	
{
	
	
	
$acl = new Acl();
	
	
	
$cache->save($acl,'acl');
	
	
}
	
	

	
	
### определяем ресурс как модуль:контроллер
	
	
$resource $request->getModuleName().':'.$request->getControllerName();
	
	
### определяем действие как привилегию
	
	
$privilege $request->getActionName();
	
	
### определем роль по авторизации пользователя
	
	
$auth Zend_Auth::getInstance();
	
	
$roles $auth->hasIdentity()?$auth->getIdentity()->login:'guest';
	
	
### если доступ запрещён
	
	
#var_dump($roles,$resource,$privilege);
	
	
if(!
$acl->isAllowed($roles,$resource,$privilege)) 
	
	
{
	
	
	
$request
	
	
	
	
->
setModuleName('default')
	
	
	
	
->
setControllerName('error')
	
	
	
	
->
setActionName(!$auth->hasIdentity() ? 'noauth' 'deny')
	
	
	
;
	
	
}
	
 
    }
}

в общем  он меня устраивает, за исключением того, что в некоторых страницах есть использование других экшенов
 К примеру есть страничка с лентой сообщений, где пользователь(авторизированный) может добавлять сообщения.
то есть у меня есть действие listAction() в котором выводится эта лента сообщений, а в виде этого действия я добавляю

echo $this->action('edit',,'index','board');

чтобы внизу у меня выводилась форма добавления.
но вот что интересно, что если заходить по ссылке http://mysite/board/index/edit то доступ у меня запрещёно, а в  http://mysite/board/index/list эта форма появляется...

не то чтобы ее трудно убрать, достаточно

if(Zend_Auth::getInstance()->hasIdentity()) echo $this->action('edit',,'index','board');


но всё же вопрос остаётся другим, а именно, как ещё можно закрыть иммено такой доступ.. если мне вдруг понадобится чтобы добавляли только определённая группа пользователей???
« Последнее редактирование: Май 21, 2009, 12:34:09 от Czar »

Оффлайн IgorN

  • Team
  • Герой
  • ***
  • Сообщений: 2908
  • Карма: 90
    • Мой сайт
Все зависит от того как вы реализовали работу с ACL плагин для проверки прав это верхушка айзберга. Как у вас реализован интерфейс по работе с ролями, привилегиями, ресурсами и правилами. Как вы это все храните в БД и т.д. я реализовал все по мануалу и у меня нет проблем настроить права на определенную роль.
Мой сайт: http://igor-negrutsa.info/
Я знаю только то,что ничего не знаю, а многие не знают даже этого.

Оффлайн Czar

  • Мастер
  • ****
  • Сообщений: 316
  • Карма: 14
так у меня тоже не возникает проблемы настроить определённую роль.
я же написал, что при заходе именно на это ресурс непосредсвенно, правило для роли срабатывает, а при вызове через хелпер $this->action() нет.
 в общем сейчас по копирую и всё сюда скину. и опишу всё что я делал.

формирование объекта Acl
<?php
class Acl extends Zend_Acl
{
    protected static 
$_instance null;
	
private 
$_acl;
	

	
### статический экземпляр класса
	
public static function 
getInstance()
	
{
	
	
if (
null === self::$_instance) {
            
self::$_instance = new self();
        }

        return 
self::$_instance;
	
}
	

	
public function 
__construct()
	
{
	
	
$this->_setRoles();
	
	
$this->_setResources();
	
	
$this->_setRules();
	
}
	

	
private function 
_setResources()
	
{
	
	
### получаем экземпляр базы данных с ресурсами
	
	
$resources = new Acl_Resources();
	
	

	
	
### добавляем ресурсы
	
	
foreach(
$resources->getList() as $row)
	
	
{
	
	
	
$parent $row->findParentAcl_Resources();
	
	
	
if(
$parent)
	
	
	
{
	
	
	
	
$this->add(new Zend_Acl_Resource($row->title),$parent->title);
	
	
	
}else
	
	
	
{
	
	
	
	
$this->add(new Zend_Acl_Resource($row->title));
	
	
	
}
	
	
}
	
}
	

	
private function 
_setRoles()
	
{
	
	
### получаем экземпляр базы данных с ролями
	
	
$resources = new Acl_Roles();
	
	

	
	
### добавляем ресурсы
	
	
foreach(
$resources->getList() as $row)
	
	
{
	
	
	
#основные роли
	
	
	
$parent $row->findParentAcl_Roles();
	
	
	
if(!
$this->hasRole($row->title))
	
	
	
{
	
	
	
	
if(
$parent)
	
	
	
	
{
	
	
	
	
	
$this->addRole(new Zend_Acl_Role($row->title),$parent->title);
	
	
	
	
}else
	
	
	
	
{
	
	
	
	
	
$this->addRole(new Zend_Acl_Role($row->title));
	
	
	
	
}
	
	
	
}
	
	
	
### роли пользователей
	
	
	
$children $row->findStaff();
	
	
	
if(
$children)
	
	
	
{
	
	
	
	
### для каждого ребянка утанавливаем роль
	
	
	
	
foreach(
$children as $child)
	
	
	
	
{
	
	
	
	
	
if(!
$this->hasRole($child->login))
	
$this->addRole(new Zend_Acl_Role($child->login),$row->title);
	
	
	
	
}
	
	
	
}
	
	
	

	
	
}
	
}
	

	
private function 
_setRules()
	
{
	
	
### получаем экземпляр базы данных с правилами
	
	
$rules = new Acl_Rules();
	
	

	
	
foreach(
$rules->getList() as $row)
	
	
{
	
	
	
$type = (string)$row->type;
	
	
	
$this->$type(
	
	
	
	
(
$row->findParentAcl_Roles()?$row->findParentAcl_Roles()->title:null),
	
	
	
	
(
$row->findParentAcl_Resources()?$row->findParentAcl_Resources()->title:null),
	
	
	
	
(
$row->findParentAcl_Privileges()?$row->findParentAcl_Privileges()->title:null)
	
	
	
);
	
	
}
	
	
$this->allow('administrator');
	
}

}


таблицы в базе следующие:
Acl_Resources():
ID; TITLE; PARENT;

Acl_Roles():
ID; TITLE; PARENT;
таблица с ролями, это так сказать группы ролей.
у меня так же естьтаблица staff  в которой находятся пользователи сайта и которые наследуют роли из таблицы Acl_Roles

Acl_Privileges():
ID; TITLE;

Acl_Rules():
ID; ROLE; RESOURCE; PRIVILEGE; TYPE;

вот вроде бы и все штуки,которые я использую для создания объекта Acl;
« Последнее редактирование: Май 21, 2009, 12:02:18 от Czar »

Оффлайн stfalcon

  • Team
  • Герой
  • ***
  • Сообщений: 1129
  • Карма: 54
  • Добрый сокольничий ^_~
    • My name is Tanasiychuk Stepan і це мій блог
Я использую вот этот плагин. Вроде нормально работает. Описанного выше косяка не наблюдаю.

Для того, чтобы неавторизированных пользователей бросало на страницу авторизаии, а авторизированным показывало, что доступ запрещен, я его немного доработал:
    /**
     * Predispatch
     * Checks if the current user identified by roleName has rights to the requested url (module/controller/action)
     * If not, it will call denyAccess to be redirected to errorPage
     *
     * @return void
     **/
    
public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        
$role $this->getRoleName();
        
$resource 'mvc:' $request->getControllerName();
        
$privilege $request->getActionName();

        
/** Check if the controller/action can be accessed by the current user */
        
if (!$this->getAcl()->isAllowed($role$resource$privilege)) {
            if (
Zend_Auth::getInstance()->hasIdentity()) {
                
/** Redirect to access denied page */
                
$this->denyAccess();
            } else {
                
/** Redirect to login page */
                
$this->pleaseLogin();
            }
        }
    }

    
/**
     * Please Login Function
     * Redirects to loginPage, this can be called from an action using the action helper
     *
     * @return void
     **/
    
public function pleaseLogin()
    {
        
$this->_request->setModuleName($this->_loginPage['module']);
        
$this->_request->setControllerName($this->_loginPage['controller']);
        
$this->_request->setActionName($this->_loginPage['action']);
    }

Оффлайн Czar

  • Мастер
  • ****
  • Сообщений: 316
  • Карма: 14
собсвенно у меня похожий плагин, я не стал жделать две функции, а просто пересылал на  контроллер ошибок с разными действиями.

и всё же это не даёт возможности запретить действие в разрешённом действии, если первое действие подключается через хелпер!