php zend-framework zend-auth zend-acl

php - Implementación práctica Zend_ACL+Zend_Auth y mejores prácticas



zend-framework zend-auth (1)

Contexto:

Mis preguntas pertenecen a un foro que estoy desarrollando exactamente como SO, donde hay:

  1. invitados que tienen acceso para ver los hilos, pero que no pueden responder ni votar
  2. miembros que, con suficiente representante, pueden editar / votar otros hilos, y de forma predeterminada pueden responder y tener los mismos privilegios que los invitados
  3. administradores que pueden hacer prácticamente cualquier cosa

Me gustaría que esta ACL se aplicara en todo el sitio, y por defecto denegaría todos los recursos.

Leí los conceptos básicos del uso de Zend_Acl: básicamente, creas roles (invitado, miembro, administrador) y niegas o permites recursos (controladores, métodos) a esos roles. La documentación no es muy específica sobre cómo debe implementar el código acl en su aplicación, así que fui buscando SO ...

Me encontré con una respuesta bastante útil de stackoverflow de marek que arroja algo de luz sobre el tema, sin embargo, debido a mi falta de familiaridad, todavía no puedo entender completamente cómo implementar esto correctamente con las mejores prácticas en mente.

El póster tiene un archivo estático configAcl.php en la raíz de la aplicación que inicializa el objeto acl, agrega roles, crea un recurso de cada controlador, le da acceso de admin a todo, da acceso normal a todo menos al administrador y almacena el objeto acl en el registro para uso posterior.

$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);

Pregunta n. ° 1 : ¿Debería este código estar en el arranque o en un archivo independiente como este? Si es así, ¿sería mejor si estuviera dentro, por ejemplo, el directorio de la biblioteca?

La segunda parte es una nueva clase que amplía la clase Zend Controller Plugin Abstract que permite que se enganche en auth/login , la lógica es básicamente si el inicio de sesión falla, redirige ... de lo contrario, toma el objeto acl del registro, capta la identidad y determina si el usuario puede ver este recurso.

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

Pregunta # 2 : ¿Cómo exactamente codificaría la parte del complemento de autenticación que realmente devuelve la identidad del usuario? Me di cuenta de que tenía un código debajo que generaba un objeto Auth adapter db table que consultaba la columna de la tabla de la base de datos por ID de usuario y credencial (verificación de paso hash). Estoy confundido sobre dónde encaja esto con la parte getIdentity.

Digamos que mi tabla de usuarios estaba compuesta de esta información:

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

Donde nivel 3 = administrador, 2 = miembro, 1 = invitado.

Pregunta n. ° 3 : ¿dónde exactamente es un buen lugar para colocar el código de autorización anterior? ¿Dentro del controlador de inicio de sesión?

Pregunta n. ° 4 : otro póster replies con su artículo sobre cómo se debe hacer la lógica de acl dentro de los modelos, pero el método específico que utiliza no es compatible de forma nativa y requiere una solución, ¿es esto factible? ¿Y es así como idealmente debería hacerse?


Mi implementación:

Pregunta 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(); } }

en 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; }

y 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); } } } }

y 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(''/''); } }

Pregunta 2

mantenlo dentro de Zend_Auth .

después de la autenticación exitosa: escriba la identidad en el almacenamiento. $auth->getStorage()->write($result->getIdentity());

la identity - es simplemente user_id

Diseño de DB

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;

algo de azúcar

/** * 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; } }

para cosas como esta en cualquier script de vista

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

¿Preguntas? :)