Need Guidance to Start with Zend Acl

Need guidance to start with Zend ACL

I implemented similar thing not so long ago. Basic concept follows in an example code.

I created my own configAcl.php file which is loaded in bootstrap file, in my case it is index.php. Here is how it'd be according to your case:

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

Another case is if you want to allow normal user only "list" action on all your controllers. It's pretty simple, you'd add line like this:

$acl->allow('normal', null, 'list'); // Has access to all controller list actions.

Next you should create new plugin which takes care of credential checking automatically when there is a request for some controller action. This checking takes place in preDispatch() method that is called before every call to the controller action.

Here is AuthPlugin.php:

class AuthPlugin extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$loginController = 'auth';
$loginAction = 'login';

$auth = Zend_Auth::getInstance();

// If user is not logged in and is not requesting login page
// - redirect to login page.
if (!$auth->hasIdentity()
&& $request->getControllerName() != $loginController
&& $request->getActionName() != $loginAction) {

$redirector = Zend_Controller_Action_HelperBroker::getStaticHelper('Redirector');
$redirector->gotoSimpleAndExit($loginAction, $loginController);
}

// User is logged in or on login page.

if ($auth->hasIdentity()) {
// Is logged in
// Let's check the credential
$registry = Zend_Registry::getInstance();
$acl = $registry->get('acl');
$identity = $auth->getIdentity();
// role is a column in the user table (database)
$isAllowed = $acl->isAllowed($identity->role,
$request->getControllerName(),
$request->getActionName());
if (!$isAllowed) {
$redirector = Zend_Controller_Action_HelperBroker::getStaticHelper('Redirector');
$redirector->gotoUrlAndExit('/');
}
}
}
}

Final steps are loading your configAcl.php and register the AuthPlugin in bootstrap file (probably index.php).

require_once '../application/configAcl.php';

$frontController = Zend_Controller_Front::getInstance();
$frontController->registerPlugin(new AuthPlugin());

So this is the basic concept. I didn't test the code above (copy and paste and rewrite just for the showcase purpose) so it's not bullet-proof. Just to give an idea.

EDIT

For the clarity. The code above in AuthPlugin suppose that the $identity object is filled with user data ("role" column in the database). This could be done within the login process like this:

[...]
$authAdapter = new Zend_Auth_Adapter_DbTable($db);
$authAdapter->setTableName('Users');
$authAdapter->setIdentityColumn('username');
$authAdapter->setCredentialColumn('password');
$authAdapter->setIdentity($username);
$authAdapter->setCredential(sha1($password));
$authAdapter->setCredentialTreatment('? AND active = 1');
$auth = Zend_Auth::getInstance();
$result = $auth->authenticate($authAdapter);
if ($result->isValid()) {
$data = $authAdapter->getResultRowObject(null, 'password'); // without password
$auth->getStorage()->write($data);
[...]

An easy way to load ACL in Zend Framework 2?

ZF2 contains ACL and also RBAC (role based ACL - might be in ZF2.1), but to put it in place, easier is to use module which you can plug into your application. BjyAuthorize seems to me a bit bloated, you have to use ZfcUser module. I prefer ZfcRbac, the ACL rules are based on user roles (group) and their access to controller, action or route. Configuration stored in one config file, really easy to implement.

Practical Zend_ACL + Zend_Auth implementation and best practices

My implementation:

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

}

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

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

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

Question #2

keep it inside Zend_Auth.

after succesfull auth - write identity in storage. $auth->getStorage()->write($result->getIdentity());

the identity - is simply user_id

DB design

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;

some sugar

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

}

for things like this in any view script

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

Questions? :)

Zend ACL allow certain actions

To allow/deny access to certain actions, specify them in the allow/deny methods of Zend_Acl.

The third argument in the Zend_Acl::allow() method will only allow you to set access controls to certain actions on a given controller/resource. For example:

<?php

$acl = new Zend_Acl();

// Roles
$guest = new Zend_Acl_Role('guest');
$user = new Zend_Acl_Role('user');

// Register the roles with the Zend_Acl
$acl->addRole($guest);
$acl->addRole($user, 'guest');

// Resources/Controllers
$indexController = new Zend_Acl_Resource('index');
$profileController = new Zend_Acl_Resource('profile');

// Add resources/controllers to the Zend_Acl
$acl->add($indexController);
$acl->add($profileController);

// Now set limits of access to the resources.
// Guests get access to all the actions in the index controller,
// but to only the login and logout actions in the profile controller.
$acl->allow('guest', 'index');
$acl->allow('guest', 'profile', array('login', 'logout'));

// Users get full access to the profile controller
$acl->allow('user', 'profile');

Zend ACL with Modules and Controller access issues

your are not defining your resource do this in your acl plugin

protected function _init($request) 
{

$this->_module = $request->getModuleName();
$this->_action = $request->getActionName();
$this->_controller = $request->getControllerName();
$this->_currentRole = $this->_getCurrentUserRole();
$this->_resource = $this->_module . ':' . $this->_controller; // <-----
}

Dynamic custom ACL in zend framework?

For dynamic condition-based tests like you are describing, you can use dynamic assertions in your Zend_Acl rules.

For example:

class My_Acl_IsProfileComplete implements Zend_Acl_Assert_Interface
{
protected $user;

public function __construct($user)
{
$this->user = $user;
}

public function assert(Zend_Acl $acl,
Zend_Acl_Role_Interface $role = null,
Zend_Acl_Resource_Interface $resource = null,
$privilege = null)
{
// check the user's profile
if (null === $this->user){
return false;
}
return $this->user->isProfileComplete(); // for example
}
}

Then when defining your Acl object:

$user = Zend_Auth::getInstance()->getIdentity();
$assertion = new My_Acl_Assertion_IsProfileComplete($user);
$acl->allow($role, $resource, $privilege, $assertion);

Of course, some of the details depend upon the specifics of what you need to check and what you can use in your depend upon what you store in your Zend_Auth::setIdentity() call - only a user Id, a full user object, etc. And the roles, resources, and privileges are completely app-specific. But hopefully this gives the idea.

Also, since the assertion object requires a user object at instantiation, this dynamic rule cannot be added at Bootstrap. But, you can create the core Acl instance with static rules during bootstrap and then register a front controller plugin (to run at preDispatch(), say) that adds the dynamic assertion. This way, the Acl is fully populated by the time you get to your controllers where presumably you would be checking them.

Just thinking out loud.



Related Topics



Leave a reply



Submit