My Zend_Acl Implementation
Introduction
It seems everyone, myself included, has a bit of a hard time first grasping Zend_Acl.
For the time being, I’ve settled on a simple solution. It’s party based on the solution given in the Zend Framework in Action book. I hope you get some use out of it.
The Roles
My solution revolves around Zend_Config and my main application’s INI file. Just like in ZFiA, I configure my roles such as:
acl.roles.guest = null
acl.roles.member = guest
acl.roles.admin = member
Each role inherits from the right hand side. In this case, Guest inherits from no one, Member inherits from Guest, and Admin inherits from Member. This allows Admin to have all abilities Guest and Member have.
In the users table of my database, I have a column titled role with a default value of member. This makes it so anyone who does not have an account or is not logged in is a Guest and all logged in users are Members. Users who are Admins will have their admin status set manually through the Database software (or a form if I ever decide to make one).
The ACLs
The solution given in ZFiA was to specify the accessible Actions through the init() function of each Controller. I thought this was clever, but not centralized. I would have preferred to have my ACLs specified in the same place for organizational purposes.
I liked how ZFiA specified the Roles in the INI file and set off to figure out how I could easily specify my ACLs in the INI as well.
In the end, I settled on this structure:
acl.resources.allow.error.all = guest
acl.resources.allow.index.all = guest
acl.resources.allow.auth.all = guest
acl.resources.allow.user.register = guest
acl.resources.allow.user.profile = member
I think the format is pretty self-explanatory:
- acl: The ACL section of the INI file
- resources: The resources sub-section of the INI file
- allow: The permission field. Can either be Allow or Deny
- user: The controller. Can either be a specified controller or “all”
- all: The Action. Can either be a specified action or “all”
In this case, all roles can access all actions of the Error, Index, and Auth Controllers, but only Members can access the Profile section of the User Controller. This makes sense as you need an account in order to edit its profile.
The Code
Finally, some code is needed to actually put this to work. I needed two classes: My own ACL class that extended Zend_Acl and a Controller Helper that checked the ACLs on each request. Again, these solutions are based off of the ZFiA solutions (which looks like they were subsequently based off of various Zend blog posts).
Here’s my ACL class:
class MyAcl extends Zend_Acl {
private $_noAuth;
private $_noAcl;
public function __construct() {
$config = Zend_Registry::get('config');
$roles = $config->acl->roles;
$resources = $config->acl->resources;
$this->_addRoles($roles);
$this->_addResources($resources);
}
public function _addRoles($roles) {
foreach ($roles as $name => $parents) {
if (!$this->hasRole($name)) {
if (e mpty($parents)) {
$parents = null;
} else {
$parents = explode(',', $parents);
}
$this->addRole(new Zend_Acl_Role($name), $parents);
}
}
}
public function _addResources($resources) {
foreach ($resources as $permissions => $controllers) {
foreach ($controllers as $controller => $actions) {
if ($controller == 'all') {
$controller = null;
} else {
if (!$this->has($controller)) {
$this->add(new Zend_Acl_Resource($controller));
}
}
foreach ($actions as $action => $role) {
if ($action == 'all') {
$action = null;
}
if ($permissions == 'allow') {
$this->allow($role, $controller, $action);
}
if ($permissions == 'deny') {
$this->deny($role, $controller, $action);
}
}
}
}
}
}
This class performs two main actions: It reads the roles from the INI file and adds them with their parents accordingly.
The second action is my own implementation. It reads the acl.resource section of the INI file and applies the ACLs accordingly. If the ACL contained the word “all”, it sets the action to null (meaning, all actions or all controllers). I could have probably specified null in the INI to begin with, but using the keyword all gives it more meaning to me.
Here’s the Controller Helper:
class AclHelper extends Zend_Controller_Action_Helper_Abstract {
protected $_action;
protected $_auth;
protected $_acl;
protected $_controllerName;
public function __construct(Zend_View_Interface $view = null, array $options = array()) {
$this->_auth = Zend_Auth::getInstance();
$this->_acl = $options['acl'];
}
public function init() {
$this->_action = $this->getActionController();
$controller = $this->_action->getRequest()->getControllerName();
}
public function preDispatch() {
$role = 'guest';
if ($this->_auth->hasIdentity()) {
$user = $this->_auth->getIdentity();
if (is_object($user)) {
$role = $this->_auth->getIdentity()->role;
}
}
$request = $this->_action->getRequest();
$controller = $request->getControllerName();
$action = $request->getActionName();
$module = $request->getModuleName();
$this->_controllerName = $controller;
$resource = $controller;
$privilege = $action;
if (!$this->_acl->has($resource)) {
$resource = null;
}
if (!$this->_acl->isAllowed($role, $resource, $privilege)) {
$request->setModuleName('default');
$request->setControllerName('auth');
$request->setActionName('login');
$request->setDispatched(false);
}
}
}
Nothing too fancy here. If the user is not logged in, they are considered a Guest. If they are logged in, it pulls their Role attribute.
If the user has not been granted access to the requested page, they are redirected to a login page.
Adding it to the Bootstrap
Finally, here is how I integrated this solution into my Application. In bootstrap.php, I did the following:
$acl = new MyAcl();
$aclHelper = new AclHelper(null, array('acl' => $acl));
Zend_Controller_Action_HelperBroker::addHelper($aclHelper);
Deny by Default and Allow by Default
The solution described above could be considered “Deny by Default”. You must specify all areas where each role has access to. If there is not at least a Guest ACL specified, everyone is denied access and is presented with a login page.
Though I have not tried it yet, you could choose to do an “Allow by Default” method.
First, swap the role inheritance:
acl.roles.admin = null
acl.roles.member = admin
acl.roles.guest = member
Next, grant all access to the Admin:
acl.resources.allow.all.all = admin
This gives Admin access to everything, which is logical. However, it also gives Guests and Members access to everything, too. In order to not do this, you must start denying areas:
acl.resources.deny.admin.all = member
acl.resources.deny.user.profile = guest
This would restrict both Members and Guests from accessing any action in the Admin controller. Likewise, Guests are prevented from accessing the Profile section of the User controller.
Conclusion
I like this solution as it allows me to specify my ACLs in an easy manner and in one central location. I’ll admit that there is a lot of typing and redundancy involved, but to have my rules in one manageable area is key to me.
If you have any comments or questions on this solution, I would love to hear them.
Thanks for posting this. Of note, you have a typo in MyAcl, line 17: “emptyempty”
Hi Greg,
Thanks for your comment. It looks like the
emptyemptytypo is a bug in the Syntax Highlighting plugin I use in Wordpress.Tims Blog » Blog Archive » Sometimes caching (March 6, 2009, 10:52 am).
[...] my zend acl implementation [...]
I have followed this example as well as the one in ZFiA and everything seems fine but as soon as I try to register the ActionHelper with the Zend_Controller_Action_HelperBroker my application crashes and returns an error telling me that I have exceeded my php memory size. I increased my memory size considerably and still the problem persists?
Do you have any suggestions?
I have the same problem of Southpaw, please help
Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 40961 bytes)
did anyone have solve it ?
You should be able to fix that by increasing the memory limit in your php.ini file. It looks like yours is currently set to 32MB. Try 64MB.
Big thank you for this article. I have been looking for quite a while for a complete example of how to implement Zend_Acl.
How I can use this with modules?