Categories


Loading feed
Loading feed
Loading feed

Zend_Acl / Zend_Auth Example Scenario


Zend_Acl / Zend_Auth example scenario

by Simon Mundy

Hi there all

After submitting the initial example of how Zend_Auth and Zend_Acl could be implemented Gavin pointed out areas that weren't really addressed in my proof of concept and it could potentially confuse newcomers to the way MVC is utilised. I'd like to clarify that post to a) Address those concerns and b) see if there's any constructive criticism of the process that could benefit everyone.

Requirements

Demonstrate a web environment where 'public' (i.e. non-authenticated) users and 'member' users have access restrictions, and to what context they may visit those resources. In a lot of ways this broad concept relates very well to small-medium sites of a lot of Zend developers (in my opinion). For purpose of clarity we will assume this is a SIG group for Mac Users to discuss all things Mac OS X-related. The site has 3 areas (home, news, tutorials) that are for the general public. Members can also view a discussion forum, community newsletter and support request area for members to share common problems.

Site layout
-------------
Expressed as :controller/:action notation:-

/home

/news/index
     /view
     /email

/tutorials/index
          /view

/forum/index
      /category
      /view
      /add
      /update
      /reply
      /search
      /report - report abuse, etc.

/support/index
        /view
        /search
        /submit
        /confirmation - 
        /comment - add comment

/login/index - handles form processing and auth processing

/logout/index - destroys current auth instance

/error/noroute - handles all 404s
      /failure - handles 'Site error' messages
      /privileges - handles 'You are not privileged...' messages

/admin - a cms to handle all site management

This loosely illustrates the site functionality and content - for the sake of brevity we'll assume that the general concepts and operations of these site functions are understood and familiar. What we're interested in is how to handle user authentication and then access, but at least this gives us some 'real world' understanding of what is required.

Access rules:

Three types of user 'roles' have been identified for the site:-

  • guest (not authenticated) - Guests can access 'home', 'news' and 'tutorials' only. Guests attempting to access member-only content will be asked to authenticate.
  • member (authenticated) - Access all top-level controllers. Can update forum posts but only those authored by themselves. Not allowed access to admin section. Access to 'admin' will result in 'privileges' error message.
  • admin (authenticated) - Unrestricted access.

Application layout

Using the 'Conventional' layout that Gavin outlines in http://framework.zend.com/wiki/display/ZFDEV/Choosing+Your+Application%27s+Directory+Layout

The bootstrap is located inside /htdocs/index.php

Bootstrap

The bootstrap takes care of the usual suspects - Db, View, Config, Log, Router - and stores them inside the Zend_Front_Controller so that they can be accessed via each controller using the getInvokeArg() method. This negates the need for an extra registry object and (hopefully) makes the dependencies somewhat easier to track.

To satisfy the needs of the Access rules, we create a subclassed instance of Zend_Acl like so:

class MyAcl extends Zend_Acl
{
    public function __construct(Zend_Auth $auth)
    {
        parent::__construct();

        $roleGuest = new Zend_Acl_Role('guest');

$this->add(new Zend_Acl_Resource('home'));
$this->add(new Zend_Acl_Resource('news'));
$this->add(new Zend_Acl_Resource('tutorials'));
$this->add(new Zend_Acl_Resource('forum'));
$this->add(new Zend_Acl_Resource('support'));
$this->add(new Zend_Acl_Resource('admin'));

        $this->addRole(new Zend_Acl_Role('guest')); 
        $this->addRole(new Zend_Acl_Role('member'), 'guest');
        $this->addRole(new Zend_Acl_Role('admin'), 'member');

        // Guest may only view content
        $this->allow('guest', 'home');
        $this->allow('guest', 'news');
        $this->allow('guest', 'tutorials');
        $this->allow('member', 'forum');
        $this->deny('member', 'forum', 'update'); // Remove specific privilege
        $this->allow('member', 'support');
        $this->allow('admin'); // unrestricted access

        // Add authoring ACL check
        $this->allow('member', 'forum', 'update', new MyAcl_Forum_Assertion($auth));
        // NOTE: Dependency on auth object to allow getIdentity() for authenticated user object
    }
}

...and then this is added to the bootstrap. The final index.php file looks something like:

Index.php
<?php

// Initialise configuration / environment
$config = new Zend_Config(new Zend_Config_Ini('../application/config/config.ini', 'live'));

// Create sitemap from .ini using structure from example
$sitemap = new Zend_Config(new Zend_Config_Ini('../application/config/sitemap.ini', 'live'));

// Create db object and enable/disable debugging
$db = Zend_Db::factory($config->db->connection, $config->db->asArray());
...etc...

// Create auth object
$auth = Zend_Auth::getInstance();

// Create acl object
$acl = new MyAcl($auth); // see 

// Create router and configure (LIFO order for routes)
$router = new Zend_Controller_RewriteRouter;
...add rules...

// Create view and register objects
$view = new My_View;
...init view...

$front = Zend_Controller_Front::getInstance();
$front->throwExceptions(true);
$front->setRouter($router)
      ->setDispatcher(new Zend_Controller_ModuleDispatcher())
      ->registerPlugin(new My_Plugin_Auth($auth, $acl))
      ->registerPlugin(new My_Plugin_Agreement($auth))
      ->registerPlugin(new My_Plugin_View($view))
      ->setControllerDirectory(array('default' => realpath('../application/controllers/default'),
                                     'admin' => realpath('../application/controllers/admin')))
      ->setParam('auth', $auth)
      ->setParam('view', $view)
      ->setParam('config', $config)
      ->setParam('sitemap', $sitemap)
      ->dispatch();

This is a pretty standard (IMO) bootstrap - the areas to note for the purpose of Authentication/Acl are the two first plugins:

Auth.php

The purpose of this plugin is to first determine the 'role' of the current Auth identity. If Zend_Auth::getIdentity() returns false then we don't have a 'role' for the identity, so we assume 'guest'. If a user is authenticated, the Zend_Auth identity would be returned as an object and we would extract the role from this. For simplicity's sake, let's assume that the 'role' is stored in a MySQL database and is returned as a public property from the Identity object (i.e. 'member' or 'admin').

The 'role' is then a one-to-one match against the Acl rules. If we interrogate the Acl and we are allowed to view the current controller (maps to the 'resource' id given to each Acl resource) then the dispatcher continues on its merry way.

If the Acl denies the access, we then determine if the user has a valid identity. If not, we tell the request object that we want to redirect to a new controller (login) to perform a login. At this stage, no request data is required - this will be handled via a form in the LoginController.

If, however, the identity is valid then we know that access if definitely blocked for that user and we send the request to the 'error' controller to display the 'no privleges' error.

I've chosen this strategy as it means that none of the controllers need know anything about the ACL process - they can assume that access to the action has been already approved and need only check action-specific privilege checks (e.g. ensuring they view valid articles, forum threads, etc.)

However a developer could still choose to add further ACL rules if required and reduce the amount of ACL-related 'clutter' in the controllers themselves.

<?php

class My_Plugin_Auth extends Zend_Controller_Plugin_Abstract
{
    private $_auth;
    private $_acl;

    private $_noauth = array('module' => 'default',
                             'controller' => 'login',
                             'action' => 'index');

    private $_noacl = array('module' => 'default',
                            'controller' => 'error',
                            'action' => 'privileges');
   
    public function __construct($auth, $acl)
    {
        $this->_auth = $auth;
        $this->_acl = $acl;
    }

public function preDispatch($request)
{
        if ($this->_auth->hasIdentity()) {
            $role = $this->_auth->getIdentity()->getUser()->role;
        } else {
            $role = 'guest';
        }
       
    $controller = $request->controller;
    $action = $request->action;
    $module = $request->module;
$resource = $controller;
   
    if (!$this->_acl->has($resource)) {
        $resource = null;
    }

        if (!$this->_acl->isAllowed($role, $resource, $action)) {
            if (!$this->_auth->hasIdentity()) {
                $module = $this->_noauth['module'];
                $controller = $this->_noauth['controller'];
                $action = $this->_noauth['action'];

Comments


Thursday, February 8, 2007
CALL TO UNDEFINED METHOD ZEND_AUTH::GETINSTANCE()
12:46PM PST · nfabre
ZEND_AUTH::GETINSTANCE()
2:59PM PST · zend_darby
GOOD NEWS
10:31PM PST · nfabre
Friday, February 9, 2007
TO NEW OR NOT TO NEW
6:22AM PST · zend_darby
Tuesday, February 13, 2007
OUPS ;-)
12:06PM PST · nfabre
Monday, March 12, 2007
HI ALL
2:54AM PDT · quannm0410l
Monday, March 26, 2007
WHERE DOES THIS CODE GO?
5:21PM PDT · jaybill
Monday, April 9, 2007
WHERE IS THE REST?
2:29PM PDT · davetheave
Tuesday, April 10, 2007
THE MISSING PIECES
1:59AM PDT · Simon Mundy [unregistered]
DOESN'T WORK FOR ME...
5:47AM PDT · yell
Friday, April 20, 2007
ZEND_ACL HAS NO CONSTRUCTOR
9:36AM PDT · zend_darby
Tuesday, April 24, 2007
ACL PER PAGE?
10:04AM PDT · shrikeh
Wednesday, June 27, 2007
THIS PLUGIN CAUSES PROBLEMS WITH THE ERRORHANDLER PLUGIN
9:19AM PDT · teejaay
Friday, June 29, 2007
ERROR HANDLER PLUGIN EXCEPTION
7:27AM PDT · frontpost [@] gmail (dot) commm [unregistered]
Tuesday, July 3, 2007
HANDLING 404 PAGE NOT FOUND
5:33PM PDT · htorun
Wednesday, July 11, 2007
FRONTPOST
7:23AM PDT · Anonymous User [unregistered]
FRONTPOST
7:26AM PDT · Anonymous User [unregistered]
FRONTPOST
9:03AM PDT · Anonymous User [unregistered]
ERRORHANDLER & 404
9:03AM PDT · frontpost [unregistered]
Thursday, July 12, 2007
THIS DOES BREAK 404
3:37PM PDT · James [unregistered]
Sunday, July 15, 2007
ERRORHANDLER & 404
8:45AM PDT · htorun
ERRORHANDLER & 404
8:48AM PDT · htorun
ERRORHANDLER & 404
8:53AM PDT · htorun
Monday, August 13, 2007
ERRORHANDLER & 404
7:08PM PDT · Anonymous User [unregistered]
Wednesday, October 3, 2007
A BIT CONFUSED
6:03AM PDT · andrasz
Friday, November 9, 2007
WHY NO TARBALL?
1:28AM PST · mim12345
Wednesday, November 28, 2007
ERRORHANDLER & 404
4:48AM PST · exxbrain
404
4:56AM PST · exxbrain
Monday, January 7, 2008
AN EDITBUTTON IN THE VIEW
2:20AM PST · richardjansen_74
Tuesday, May 6, 2008
RENEWED VERSION
1:05AM PDT · jjsanders
Tuesday, June 17, 2008
ERROR IN THIS EXAMPLE
5:08PM PDT · thedave
Tuesday, July 29, 2008
NOT AFRAID OF BEING A NEWBEE
6:27PM PDT · barryke