Zend_Acl part 1: Misconceptions and simple ACLs

Tags:

I’m going to be writing a weekly series of posts on Zend_Acl. This first post will clear up some common misconceptions regarding Zend_Acl, introduce creating ACLs for simple applications, and give some examples on using the ACL in both non-Zend Framework and Zend Framework applications.

Later in the post series I’ll be talking about some more advanced ways of utilizing Zend_Acl, and topics such as database-backed ACLs.

A russian translation of this post is available here, courtesy of Rashad Surkin.

Zend_Acl misconceptions

Many people think that the ACL “resource” and “privilege” are the same as the controller and the action. This is not true!

The resource in Zend_Acl can be anything – a controller, a file, a model…

The privilege, just like the resource, can be anything related to the resource – for example, it could be an action if the resource is a controller, or it could be “read” or “write” if it’s a file or a model.

Later in the series, I’ll show some other ways of using acl resources, privileges and roles.

Building a simple ACL

As mentioned earlier, Zend_Acl constists of resources, privileges, and additionally, roles. Resources can be anything ranging from controllers to files. Privileges are different levels of access on the resource. Roles determine who can access a resource, and with what privileges. Roles can be users, user groups or anything you wish to associate such data with.

The very simplest way you can use Zend_Acl is building the ACL in code. This is often a useful approach for simple cases, where you only have a handful of resources and roles.

class My_Acl extends Zend_Acl {
  public function __construct() {
    //Add a new role called "guest"
    $this->addRole(new Zend_Acl_Role('guest'));
 
    //Add a role called user, which inherits from guest
    $this->addRole(new Zend_Acl_Role('user'), 'guest');
 
    //Add a resource called page
    $this->add(new Zend_Acl_Resource('page'));
 
    //Add a resource called news, which inherits page
    $this->add(new Zend_Acl_Resource('news'), 'page');
 
    //Finally, we want to allow guests to view pages
    $this->allow('guest', 'page', 'view');
 
    //and users can comment news
    $this->allow('user', 'news', 'comment');
  }
}

Now, by creating a new instance of My_Acl, we could perform simple checks against the acl. But what is allowed, and for who?

The guest role gets a privilege called view on the resource page. Since the user role inherits from guest, it also gets this privilege. The news resource inherits from page, so everyone who has privileges on the page resource, has the same privileges on news. In the last line of the constructor, we add a privilege comment for the user role on news. Only the user role has the privilege comment in the news resource – guest does not.

Note: The names used in the example for the roles and resources are just identifiers. They could be 1, 2, or whatever, but it’s often easier to understand identifiers that are human-readable. However, when generating ACLs from databases or other source, it may not be necessary to have human-readable identifiers – the primary key from a database row will do just fine. Again, later in the series I’ll show an example of this.

Using the simple ACL

Using an ACL class is quite simple. You just call the method isAllowed with a role, resource and a privilege. The main difficulty is in determining what is the role, resource and the privilege.

How do we determine, what is the role? This can be a very simple if-clause: if there is a logged-in user, then the role is user, otherwise it is guest.

What about the resource? Depending on the application, this could be guessed from the file name included. For example, with our simple ACL, we could have a file called page.php and a file called news.php. page would check against the page resource, and news would check against the news resource.

And lastly, the privilege? When simply loading the page, you could use the view privilege. The code could later on check for other privileges: for example, the code could check if you have access to the comment privilege and display a comment box.

Here’s a really simple example:

$role = 'guest';
if(isset($_SESSION['auth']))
  $role = 'user';
 
$acl = new My_Acl();
 
if($acl->isAllowed($role, 'news', 'comment')) {
  //Some code here to display a news box
}

As you can see, it’s quite easy to use the ACL even without Zend Framework, and you can easily speed up development of non-ZF apps by using the ACL component as it will save time when you won’t need to implement complex authorization checking code.

The above example may be very simple, but it shows you the basic way ACLs are used.

Using the simple ACL in Zend Framework applications

In Zend Framework applications, the resource and privilege can often be determined from the request object.

Typically, we would create a plugin which checks the acl for us:

class My_Plugin_Acl extends Zend_Controller_Plugin_Abstract {
  private $_acl = null;
 
  public function __construct(Zend_Acl $acl) {
    $this->_acl = $acl;
  }
 
  public function preDispatch(Zend_Controller_Request_Abstract $request) {
    //As in the earlier example, authed users will have the role user
    $role = (Zend_Auth::getInstance()->hasIdentity())
          ? 'user'
          : 'guest';
 
    //For this example, we will use the controller as the resource:
    $resource = $request->getControllerName();
 
    if(!$this->_acl->isAllowed($role, $resource, 'view')) {
      //If the user has no access we send him elsewhere by changing the request
      $request->setModuleName('auth')
              ->setControllerName('auth')
              ->setActionName('login');
    }
  }
}

We could have made the ACL static – instead of passing it in the constructor, we could have just created it in the preDispatch method. However, by passing it as a parameter, we make it easier to reuse this plugin. If we create a different ACL class, this plugin will still work with it without modifications.

Now that we have a plugin, we need to add it to the front controller in the boostrap:

$acl = new My_Acl();
 
//assuming $fc is the front controller
$fc->registerPlugin(new My_Plugin_Acl($acl));

Now every request will be checked against the acl, and anyone without the view privilege would not get through.

Note: if you use the My_Acl class as-is, you may get an infinite loop! The code in the plugin is sending the user to Auth module’s AuthController’s loginAction. It also uses the controller’s name as the resource ID… but My_Acl does not have a resource called auth! So if you want to make it work, you need to add a resource called auth to the ACL, and have it inherit from page, as we already gave guest the necessary privileges to page.

But how do we check for the comment privilege?

You could add some code to the controller, which checks for the required privilege, and then sets a variable in the view to true:

public function someAction() {
  $role = (Zend_Auth::getInstance()->hasIdentity())
        ? 'user'
        : 'guest';
 
  //assuming $this->_acl contains the acl
  $this->view->canComment = $this->_acl->isAllowed($role, 'news', 'comment');
}

It may be a good idea to modify the code so that you won’t need to duplicate the role check. For example, you could store the role in the auth identity, or you could create an action helper which is used to access the acl and contains all relevant data.

In closing

Armed with this knowledge, you should be able to get started with Zend_Acl!

Next week’s post is going to deal with some more advanced Zend_Acl usage scenarios:

  • Dealing with different resource types, such as controllers and files
  • Dealing with different role types, such as users and user groups

You can read the next post in the series here