Tying the ACL to Controllers/Action in Zend Framework 1.5
I have been using the PHP application framework 'Zend Framework' a lot lately. The 1.5 release in particular is very feature packed.
Zend Framework has built in classes for creating Access Control Lists, or ACL's. These ACL's allow for roles (users / groups) and resources (any resource a role may want to access). They also have the concept of privileges, which are what action you want to take on the resource (e.g. add / delete / edit etc).
If you are making an MVC application your first thought is probably like mine. How can we make the ACL automatically apply to controllers and actions? The answer I have found is by writing a custom Controller Plugin. By using a Controller Plugin, we can automatically apply our ACL without having to add any ACL related code in our actual Controllers.
This allows us to do the following
- Assign our users to arbitrary groups
- Allow groups to access only specific Controller and Action combinations, e.g. /news/add
- Do all of this seamlessly. No code is needed in each controller class. Only one line is added to the boot strap
Here is my plugin class file.
class Lsdev_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
{
private function getAcl()
{
$acl = new Zend_Acl();
// Define our possible user groups including who they inherit from
$acl->addRole(new Zend_Acl_Role('guest'));
$acl->addRole(new Zend_Acl_Role('customer'), array('guest'));
$acl->addRole(new Zend_Acl_Role('staff'), array('guest'));
$acl->addRole(new Zend_Acl_Role('super'), array('staff'));
$acl->addRole(new Zend_Acl_Role('admin'));
// Define our controllers as resources.
$acl->add(new Zend_Acl_Resource('index')); // IndexController
// Define our group access rules. Syntax is $acl->allow('User Group', 'Controller', 'Action');
$acl->allow('guest', 'index', 'index');
$acl->allow('customer', 'index', 'add');
$acl->allow('staff', 'index', 'edit');
$acl->allow('super', 'index', 'delete');
// The admin group is special. It can always access anything
$acl->allow('admin');
return $acl;
}
// Check to see if a user can access the Controller and Action they are trying to
private function check()
{
$acl = $this->getAcl();
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
$user = $auth->getIdentity();
$acl->addRole(new Zend_Acl_Role($user->username, array($user->group)));
} else {
$user = new ArrayObject();
$user->group = 'guest';
}
$request = $this->getRequest()->getParams();
try {
return $acl->isAllowed($user->group, $request['controller'], $request['action']);
} catch (Zend_Acl_Exception $e) {
return false;
}
}
// Run this class during Zend_Controller_Front's routeShutdown event
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
$allowed = $this->check();
// Here is where you would redirect to an 'unauthorised' page. For now just display a message
if ($allowed) $msg = "you are authorised";
else $msg = "you are not authorised. Bad bad!";
$this->getResponse()->appendBody($msg);
}
}
To use it, all I have to do is add the following line to my boot strapper.
Obviously I also had to previously set up my Zend_Auth controllers. I have done this by using a database table called 'users'.
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`username` char(32) NOT NULL,
`password` char(32) NOT NULL,
`group` varchar(20) NOT NULL,
`fullname` char(32) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
);
You will notice the 'group' column. This directly applies to the ACL and can be 'guest', 'customer', 'staff', 'super', or 'admin'. If a user is logged in, that user is added to the ACL under the group that they belong to.
This is just a mock-up I put together yesterday as a proof of concept. I'm very happy with it though. The next thing I'm working on is a way to do the following.
- Define the ACL in the database, including caching the result for performance.
- Make a tool that will automatically populate the ACL database based on the currently defined controllers / actions. It should be able to also update the current list, defaulting access to false for any new controller / action.
- Make an admin panel to administrate the ACL.
Basically the getAcl() function above is just a place-holder for something a lot better, but at the moment it fully works.

on September 24th, 2008 at 1:09 am
brilliant!
on August 24th, 2009 at 1:36 pm
Pretty cool man . Logic is helpful to newbie .
on December 30th, 2009 at 1:40 am
Very nice blog, your article is interesting, i have bookmarked it for future referrence