Zend_Acl and MVC Integration Part II (Advanced Use)
By Aldemar Bernal
In part one we saw how to setup our Zend_Acl instance and how to attach it to the MVC environment (by using the Front Controller Plugin), but what about setting another action for the denied access, or how does an article be edited only by its owner?, this, and some more is what we are going to see in the following examples.
As I said in part one, this article is based on the following Zend Framework Proporsal (link), by this moment this proposal is in the laboratory.
1. Using Modules
Let's begin talking about modules, we were using devzone as an example for this article, what about if we create an admin module to handle the approval of articles and have some others tasks like changing the quick links or categories?, then we will have to redefine our ACL resources model:
Default Module:- user controller.
- article controller.
- article controller.
- quick-link controller.
- category controller.
Based on this new resources model, we create a Zend_Acl instance that reflects this.
Note: Remember that this code and the creation of the Zend_Acl object must be executed before calling the Front Controller dispatch method (at bootstrap time).
/** Creating Roles */
require_once 'Zend/Acl/Role.php';
$myAcl->addRole(new Zend_Acl_Role('guest'))
->addRole(new Zend_Acl_Role('writer'), 'guest')
->addRole(new Zend_Acl_Role('admin'), 'writer');
/** Creating resources */
require_once 'Zend/Acl/Resource.php';
/** Default module */
$myAcl->add(new Zend_Acl_Resource('user'))
->add(new Zend_Acl_Resource('article'));
/** Admin module */
$myAcl->add(new Zend_Acl_Resource('admin'))
->add(new Zend_Acl_Resource('admin:article', 'admin'))
->add(new Zend_Acl_Resource('admin:quick-link', 'admin'))
->add(new Zend_Acl_Resource('admin:category', 'admin'));
/** Creating permissions */
$myAcl->allow('guest', 'user')
->deny('guest', 'article')
->allow('guest', 'article', 'view')
->allow(array('writer', 'admin'), 'article', array('add', 'edit'))
->allow('admin', 'admin');
/** Setting up the front controller */
require_once 'Zend/Controller/Front.php';
$front = Zend_Controller_Front::getInstance();
$front->setControllerDirectory(array('default' => 'path/to/default/controllers',
'admin' => 'path/to/admin/controllers'));
/** Registering the Plugin object */
require_once 'Zend/Controller/Plugin/Acl.php';
$front->registerPlugin(new Zend_Controller_Plugin_Acl($myAcl, 'guest'));
/** Dispatching the front controller */
$front->dispatch();
Notice that as we did before, we created a resource for every controller, but in the case of the admin module, we created a resource for the module and one for every controller inside that module in the format 'module:controller' and making them children of the admin resource, also we allowed admin role to be the only one that can access all the admin module.
2. Using Roles
Once an user has login the application, that user must have a role, in our example that role can be either 'guest, 'writer' or 'admin', so, how can we change the current ACL role in our component? First at all, you must store this role into a variable which is in the session scope, in that way, once an user logs in, you must store that user role into the session, then, in the next request, you will take that session variable to setup the Front Controller Plugin at bootstrap time.
user controller
class UserController extends Zend_Controller_Action
{
protected $_application;
public function init()
{
require_once 'Zend/Session/Namespace.php';
$this->_application = new Zend_Session_Namespace('myApplication');
}
public function loginAction()
{
... Validation code
if ($valid) {
/** Setting role into session */
$this->_application->currentRole = $user->role;
$this->_application->loggedUser = $user->username;
}
}
public function logoutAction()
{
$this->_application->currentRole = 'guest';
$this->_application->loggedUser = null;
}
}
bootstrap
/** Loading application from session */
require_once 'Zend/Session/Namespace.php';
$application = new Zend_Session_Namespace('myApplication');
if (!isset($application->currentRole)) {
$application->currentRole = 'guest';
}
/** Setting up the front controller */
require_once 'Zend/Controller/Front.php';
$front = Zend_Controller_Front::getInstance();
$front->setControllerDirectory('path/to/controllers');
/** Registering the Plugin object */
require_once 'Zend/Controller/Plugin/Acl.php';
$front->registerPlugin(new Zend_Controller_Plugin_Acl($myAcl, $application->currentRole));
/** Dispatching the front controller */
$front->dispatch();
3. Setting the denied error action
Maybe some of you (hope nobody =D) just don't like the idea of having the access denied action in the error controller, or just want to name it some other way. This can be done by calling the setErrorPage method of the Front Controller Plugin.
/** Setting up the front controller */
require_once 'Zend/Controller/Front.php';
$front = Zend_Controller_Front::getInstance();
$front->setControllerDirectory('path/to/controllers');
/** Setting default access denied action */
require_once 'Zend/Controller/Plugin/Acl.php';
$aclPlugin = new Zend_Controller_Plugin_Acl($myAcl, 'guest');
$aclPlugin->setErrorPage('goaway', 'my-error-controller', 'my-module');
/** Registering the Plugin object */
$front->registerPlugin($aclPlugin);
/** Dispatching the front controller */
$front->dispatch();
setErrorPage method can be called just passing the action name, and the controller and module will remain 'error' and 'default', also can be called passing the action and the controller or passing all the three parameters.
4. Using the action helper
Finally we'll see one of the most important parts of this proposal, we've seen so far in our devzone example, that we can allow only writers and admins to edit an article, but wait, there is still a missing piece, if I am a writer and I can access article/edit/:id, that means that I can not only edit my articles but others too, big doh!, that's not really good, is it?, so, what do we do now? manage it using the Action Helper, that means that you will be able to access our ACL inside any controller and not only at bootstrap time.
So, the first thing we do is to register not only our Front Controller Plugin but also our Action Helper in the Controller Action Helper Broker.
bootstrap
/** Loading application from session */
require_once 'Zend/Session/Namespace.php';
$application = new Zend_Session_Namespace('myApplication');
if (!isset($application->loggedUser)) {
$application->loggedUser = null;
}
/** Setting up the front controller */
require_once 'Zend/Controller/Front.php';
$front = Zend_Controller_Front::getInstance();
$front->setControllerDirectory('path/to/controllers');
/** Registering the Plugin object */
require_once 'Zend/Controller/Plugin/Acl.php';
$front->registerPlugin(new Zend_Controller_Plugin_Acl($myAcl, $application->currentRole));
/** Registering the Action Helper object */
require_once 'Zend/Controller/Action/Helper/Acl.php';
require_once 'Zend/Controller/Action/HelperBroker.php';
Zend_Controller_Action_HelperBroker::addHelper(new Zend_Controller_Action_Helper_Acl());
/** Dispatching the front controller */
$front->dispatch();
And after registering the Helper, we can use it inside every controller we have, so, let's only give edit access to the owner or any admin.
article controller
class ArticleController extends Zend_Controller_Action
{
protected $_acl;
protected $_application;
public function init()
{
/** Get our Action Helper */
$this->_acl = $this->_helper->getHelper('acl');
require_once 'Zend/Session/Namespace.php';
$this->_application = new Zend_Session_Namespace('myApplication');
}
...
public function editAction()
{
/** Load article by id */
$article = new Article($this->_request->id);
/** Validate if the user is the owner or an Admin */
if (($article->author != $this->_application->loggedUser) && ($this->_application->currentRole != 'admin')) {
$this->_acl->denyAccess();
}
...
}
}
Conclusion
Some archaeologists waste their whole life trying to find the missing link (and they will die looking for it =D) and some ZF'ers get older trying to make ACL work properly with their MVC environment (don't worry, you won't die before figuring it out), that said, I hope this proposal can be one of those missing pieces in the world of ACL + MVC.
A final recommendation, follow the Keep It Simple principle, if you don't really need ACL to be dynamic loaded, handwriting it is not a sin, it's just the best way to do it.
For more information about this you can go to:
Zend_Acl & MVC Integration
and here is a small implementation source code of this:
Source Code

Comments
I translated the article into Russian. I hope you are not against.
Translate: http://lobach.info/develop/zf/zend_acl-and-mvc-integration-part-2/
Sometimes you want certain parts of an application to be visible to certain types of users.
Eg. If you're a writer an edit link should only be visible when viewing your own article.
Loved the article. The part 1 was a real help, and part 2 has nearly completed the puzzle for me :-)
The only thing that is missing for me now is how to go from hard-coding the acl like you have, to loading the acl from a database.
I would really like to see an update to the Zend_Acl such that an instance of Zend_Config can be passed to the constructor. That way an entire hierarchy of roles can easily be loaded from a ini file or a database table
Zend_Auth and Zend_ACL?
We should be using Zend_Auth to pull the roles then pass it to ACL no?
Tried to look for an example somewhere and could not find one.
Maybe it's going to be in part 3?
@dmuir: I thought about a view helper but found it useless but you just gave us a clear example, so, I'll add it to the proposal, thx. Stay tuned about non-static Acl, I'll post 2 articles about how to <a href="http://framework.zend.com/wiki/display/ZFPROP/Zend_Acl+dynamic+loading+-+Aldemar+Bernal">dynamic load ACLs</a>.
@chriswoodford: load Acl from a Zend_Config object <a href="http://framework.zend.com/wiki/pages/viewpage.action?pageId=39025&focusedCommentId=39150#comment-39150">was part</a> of the original plan for this proposal but I left it because it didn't fit in this proposal, I'll think later about creating a new proposal for this kind of functionality.
@paboivin: Quoted from <a href="http://framework.zend.com/manual/en/zend.auth.html#zend.auth.introduction">Zend_Auth manual</a>:
"Zend_Auth is concerned only with authentication and not with authorization. Authentication is loosely defined as determining whether an entity actually is what it purports to be (i.e., identification), based on some set of credentials. Authorization, the process of deciding whether to allow an entity access to, or to perform operations upon, other entities is outside the scope of Zend_Auth. For more information about authorization and access control with the Zend Framework, please see Zend_Acl."
btw, there is not part 3 :P, maybe it would be if necessary (view helper explanation).
Architecturally most data model will predicate the scope of identity+authority to be closely contained, e.g. top down from user to role(s). Therefore unfolds an implicit
supply chain of validation from 'who' to 'what.'
Worded differently, Auth validates Id, to which role is attached and hands it of to Acl.
Consider below:
##########################################
public function preDispatch($request)
{
if ($this->_auth->hasIdentity()) {
$role = $this->_auth->getIdentity()->getUser()->role;
} else {
$role = 'guest';
}
.........................
---------------------------
Zend_Acl / Zend_Auth Example Scenario
Tutorials Zend Framework Zend_Acl Zend_Auth
by Cal Evans (editor) | 31 comments | Thursday, February 8, 2007
##########################################
From Cal's article in dev zone this code makes perfect sense. The only problem I was not able to implement auth the way Cal did and retrieve
role from the authorization obj.
I hoping to find out in your article .... maybe in part 3 ;-)
Hope this makes more sense now.
Thanks,
- p