Практическая реализация и лучшие практики Zend_ACL + Zend_Auth

Контекст:

Мои вопросы относятся к форуму, который я разрабатываю практически так же, как SO, где есть:

  1. Гости, у кого есть доступ, чтобы просматривать темы, но не могут отвечать или голосовать
  2. члены, которые с достаточной репутацией могут редактировать / голосовать за другие темы, и по умолчанию они могут отвечать и иметь те же привилегии, что и гости
  3. администраторы, которые могут многое сделать

Я бы хотел, чтобы этот ACL применялся на всех сайтах и ​​по умолчанию запретил все ресурсы.

Я прочитал основы использования Zend_Acl – в том, что вы в основном создаете роли (guest, member, admin) и либо запрещаете, либо разрешаете ресурсы (контроллеры, методы) для этих ролей. Документация не очень конкретна в отношении того, как вы должны фактически реализовать acl-код в своем приложении, поэтому я пошел посмотреть на SO ..

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

Плакат имеет статический файл configAcl.php в корне приложения, который инициализирует объект acl, добавляет роли, создает ресурс из каждого контроллера, дает доступ admin ко всему, обеспечивает normal доступ ко всем, кроме администратора, и сохраняет объект acl в реестр для последующего использования.

 $acl = new Zend_Acl(); $roles = array('admin', 'normal'); // Controller script names. You have to add all of them if credential check // is global to your application. $controllers = array('auth', 'index', 'news', 'admin'); foreach ($roles as $role) { $acl->addRole(new Zend_Acl_Role($role)); } foreach ($controllers as $controller) { $acl->add(new Zend_Acl_Resource($controller)); } // Here comes credential definiton for admin user. $acl->allow('admin'); // Has access to everything. // Here comes credential definition for normal user. $acl->allow('normal'); // Has access to everything... $acl->deny('normal', 'admin'); // ... except the admin controller. // Finally I store whole ACL definition to registry for use // in AuthPlugin plugin. $registry = Zend_Registry::getInstance(); $registry->set('acl', $acl); 

Вопрос № 1 – Должен ли этот код находиться в начальной загрузке или в автономном файле, таком как этот? Если бы было бы лучше, если бы это было внутри, скажем, в библиотечном каталоге?

Вторая его часть – это новый класс, расширяющий класс абстрактного плагина Zend Controller Abstract, который позволяет auth/login к auth/login , логика в основном, если логин завершается с ошибкой, он перенаправляет .. в противном случае он захватывает объект acl из реестра, захватывает личность и определяет, разрешено ли пользователю просматривать этот ресурс.

 $identity = $auth->getIdentity(); $frontController->registerPlugin(new AuthPlugin()); 

Вопрос №2. Как точно я бы закодировал часть плагина auth, которая фактически возвращает идентификатор пользователя? Я понимаю, что у него был код ниже, который создавал объект таблицы адаптера db Auth, который запрашивал бы столбец таблицы базы данных по идентификатору пользователя и учетным данным (проверка хэширования). Я смущен тем, где это вписывается в часть getIdentity.

Скажем, моя таблица пользователей состояла из этих данных:

 user_id user_name level 1 superadmin 3 2 john 2 3 example.com 1 

Где уровень 3 = admin, 2 = член, 1 = гость.

Вопрос № 3 – где именно хорошее место для размещения вышеуказанного кода авторизации? Внутри контроллера входа?

Вопрос № 4 – еще один плакат отвечает своей статьей о том, как логика acl должна выполняться внутри моделей, но конкретный метод, который он использует, не поддерживается и не требует обходного пути, это возможно? И действительно ли это идеально должно быть сделано?

Solutions Collecting From Web of "Практическая реализация и лучшие практики Zend_ACL + Zend_Auth"

Моя реализация:

Вопрос 1

 class App_Model_Acl extends Zend_Acl { const ROLE_GUEST = 'guest'; const ROLE_USER = 'user'; const ROLE_PUBLISHER = 'publisher'; const ROLE_EDITOR = 'editor'; const ROLE_ADMIN = 'admin'; const ROLE_GOD = 'god'; protected static $_instance; /* Singleton pattern */ protected function __construct() { $this->addRole(new Zend_Acl_Role(self::ROLE_GUEST)); $this->addRole(new Zend_Acl_Role(self::ROLE_USER), self::ROLE_GUEST); $this->addRole(new Zend_Acl_Role(self::ROLE_PUBLISHER), self::ROLE_USER); $this->addRole(new Zend_Acl_Role(self::ROLE_EDITOR), self::ROLE_PUBLISHER); $this->addRole(new Zend_Acl_Role(self::ROLE_ADMIN), self::ROLE_EDITOR); //unique role for superadmin $this->addRole(new Zend_Acl_Role(self::ROLE_GOD)); $this->allow(self::ROLE_GOD); /* Adding new resources */ $this->add(new Zend_Acl_Resource('mvc:users')) ->add(new Zend_Acl_Resource('mvc:users.auth'), 'mvc:users') ->add(new Zend_Acl_Resource('mvc:users.list'), 'mvc:users'); $this->allow(null, 'mvc:users', array('index', 'list')); $this->allow('guest', 'mvc:users.auth', array('index', 'login')); $this->allow('guest', 'mvc:users.list', array('index', 'list')); $this->deny(array('user'), 'mvc:users.auth', array('login')); /* Adding new resources */ $moduleResource = new Zend_Acl_Resource('mvc:snippets'); $this->add($moduleResource) ->add(new Zend_Acl_Resource('mvc:snippets.crud'), $moduleResource) ->add(new Zend_Acl_Resource('mvc:snippets.list'), $moduleResource); $this->allow(null, $moduleResource, array('index', 'list')); $this->allow('user', 'mvc:snippets.crud', array('create', 'update', 'delete', 'read', 'list')); $this->allow('guest', 'mvc:snippets.list', array('index', 'list')); return $this; } protected static $_user; public static function setUser(Users_Model_User $user = null) { if (null === $user) { throw new InvalidArgumentException('$user is null'); } self::$_user = $user; } /** * * @return App_Model_Acl */ public static function getInstance() { if (null === self::$_instance) { self::$_instance = new self(); } return self::$_instance; } public static function resetInstance() { self::$_instance = null; self::getInstance(); } } class Smapp extends Bootstrap // class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { /** * @var App_Model_User */ protected static $_currentUser; public function __construct($application) { parent::__construct($application); } public static function setCurrentUser(Users_Model_User $user) { self::$_currentUser = $user; } /** * @return App_Model_User */ public static function getCurrentUser() { if (null === self::$_currentUser) { self::setCurrentUser(Users_Service_User::getUserModel()); } return self::$_currentUser; } /** * @return App_Model_User */ public static function getCurrentUserId() { $user = self::getCurrentUser(); return $user->getId(); } } 

в class bootstrap в class bootstrap

 protected function _initUser() { $auth = Zend_Auth::getInstance(); if ($auth->hasIdentity()) { if ($user = Users_Service_User::findOneByOpenId($auth->getIdentity())) { $userLastAccess = strtotime($user->last_access); //update the date of the last login time in 5 minutes if ((time() - $userLastAccess) > 60*5) { $date = new Zend_Date(); $user->last_access = $date->toString('YYYY-MM-dd HH:mm:ss'); $user->save(); } Smapp::setCurrentUser($user); } } return Smapp::getCurrentUser(); } protected function _initAcl() { $acl = App_Model_Acl::getInstance(); Zend_View_Helper_Navigation_HelperAbstract::setDefaultAcl($acl); Zend_View_Helper_Navigation_HelperAbstract::setDefaultRole(Smapp::getCurrentUser()->role); Zend_Registry::set('Zend_Acl', $acl); return $acl; } 

и Front_Controller_Plugin

 class App_Plugin_Auth extends Zend_Controller_Plugin_Abstract { private $_identity; /** * the acl object * * @var zend_acl */ private $_acl; /** * the page to direct to if there is a current * user but they do not have permission to access * the resource * * @var array */ private $_noacl = array('module' => 'admin', 'controller' => 'error', 'action' => 'no-auth'); /** * the page to direct to if there is not current user * * @var unknown_type */ private $_noauth = array('module' => 'users', 'controller' => 'auth', 'action' => 'login'); /** * validate the current user's request * * @param zend_controller_request $request */ public function preDispatch(Zend_Controller_Request_Abstract $request) { $this->_identity = Smapp::getCurrentUser(); $this->_acl = App_Model_Acl::getInstance(); if (!empty($this->_identity)) { $role = $this->_identity->role; } else { $role = null; } $controller = $request->controller; $module = $request->module; $controller = $controller; $action = $request->action; //go from more specific to less specific $moduleLevel = 'mvc:'.$module; $controllerLevel = $moduleLevel . '.' . $controller; $privelege = $action; if ($this->_acl->has($controllerLevel)) { $resource = $controllerLevel; } else { $resource = $moduleLevel; } if ($module != 'default' && $controller != 'index') { if ($this->_acl->has($resource) && !$this->_acl->isAllowed($role, $resource, $privelege)) { if (!$this->_identity) { $request->setModuleName($this->_noauth['module']); $request->setControllerName($this->_noauth['controller']); $request->setActionName($this->_noauth['action']); //$request->setParam('authPage', 'login'); } else { $request->setModuleName($this->_noacl['module']); $request->setControllerName($this->_noacl['controller']); $request->setActionName($this->_noacl['action']); //$request->setParam('authPage', 'noauth'); } throw new Exception('Access denied. ' . $resource . '::' . $role); } } } } 

и finnaly – Auth_Controller` 🙂

 class Users_AuthController extends Smapp_Controller_Action { //sesssion protected $_storage; public function getStorage() { if (null === $this->_storage) { $this->_storage = new Zend_Session_Namespace(__CLASS__); } return $this->_storage; } public function indexAction() { return $this->_forward('login'); } public function loginAction() { $openId = null; if ($this->getRequest()->isPost() and $openId = ($this->_getParam('openid_identifier', false))) { //do nothing } elseif (!isset($_GET['openid_mode'])) { return; } //$userService = $this->loadService('User'); $userService = new Users_Service_User(); $result = $userService->authenticate($openId, $this->getResponse()); if ($result->isValid()) { $identity = $result->getIdentity(); if (!$identity['Profile']['display_name']) { return $this->_helper->redirector->gotoSimpleAndExit('update', 'profile'); } $this->_redirect('/'); } else { $this->view->errorMessages = $result->getMessages(); } } public function logoutAction() { $auth = Zend_Auth::getInstance(); $auth->clearIdentity(); //Zend_Session::destroy(); $this->_redirect('/'); } } 

Вопрос 2

держите его внутри Zend_Auth .

после succesfull auth – записать идентификатор в хранилище. $auth->getStorage()->write($result->getIdentity());

identity – это просто user_id

Дизайн БД

 CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `open_id` varchar(255) NOT NULL, `role` varchar(20) NOT NULL, `last_access` datetime NOT NULL, `created_at` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `open_id` (`open_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 CREATE TABLE `user_profile` ( `user_id` bigint(20) NOT NULL, `display_name` varchar(100) DEFAULT NULL, `email` varchar(100) DEFAULT NULL, `real_name` varchar(100) DEFAULT NULL, `website_url` varchar(255) DEFAULT NULL, `location` varchar(100) DEFAULT NULL, `birthday` date DEFAULT NULL, `about_me` text, `view_count` int(11) NOT NULL DEFAULT '0', `updated_at` datetime NOT NULL, PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

немного сахара

 /** * SM's code library * * @category * @package * @subpackage * @copyright Copyright (c) 2009 Pavel V Egorov * @author Pavel V Egorov * @link http://epavel.ru/ * @since 08.09.2009 */ class Smapp_View_Helper_IsAllowed extends Zend_View_Helper_Abstract { protected $_acl; protected $_user; public function isAllowed($resource = null, $privelege = null) { return (bool) $this->getAcl()->isAllowed($this->getUser(), $resource, $privelege); } /** * @return App_Model_Acl */ public function getAcl() { if (null === $this->_acl) { $this->setAcl(App_Model_Acl::getInstance()); } return $this->_acl; } /** * @return App_View_Helper_IsAllowed */ public function setAcl(Zend_Acl $acl) { $this->_acl = $acl; return $this; } /** * @return Users_Model_User */ public function getUser() { if (null === $this->_user) { $this->setUser(Smapp::getCurrentUser()); } return $this->_user; } /** * @return App_View_Helper_IsAllowed */ public function setUser(Users_Model_User $user) { $this->_user = $user; return $this; } } 

для подобных вещей в скрипте любого вида

  <?php if ($this->isAllowed('mvc:snippets.crud', 'update')) : ?> <a title="Edit &laquo;<?=$this->escape($snippetInfo['title'])?>&raquo; snippet">Edit</a> <?php endif?> 

Вопросов? 🙂