Categories


Loading feed
Loading feed
Loading feed

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:
  1. user controller.
  2. article controller.
Admin Module:
  1. article controller.
  2. quick-link controller.
  3. 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


Wednesday, July 16, 2008
GREAT INFORMATION!
6:55AM PDT · cabernet
Thursday, July 17, 2008
EXCELLENT ARTICLE
2:23AM PDT · bladeofsteel
Sunday, July 20, 2008
VIEW HELPER
9:11PM PDT · dmuir
Monday, July 21, 2008
GREAT ARTICLE.
2:15PM PDT · chriswoodford
Tuesday, July 22, 2008
ACL AND AUTH
12:47PM PDT · paboivin
THANK YOU FOR YOUR FEEDBACK =)
10:59PM PDT · vertigoone
Wednesday, July 23, 2008
ACL AND AUTH 2
12:33PM PDT · paboivin