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.
|
About Joe: Joe Topjian is the owner of Terrarum -- a system administration company that specializes in Linux Infrastructure and Automation. More information about Terrarum can be found here.
|

Valeeum said,
Nice one. Would love to see how we would be able to add the assertion interface in this sort of centralized manner.
Add A Comment