views:

646

answers:

1

I have based my application upon the Zend Framework. I am using Zend_Auth for authentication, but I'm not sure if Zend_Acl will work for me because, frankly, the examples I've seen are either too simplistic for my needs or confuse me.

I'm thinking of elements in my application as Resources and these Resources can have have Privileges. Roles containing Resource Privileges are dynamically defined assigned to users. I'm storing this information in normalized tables.

  1. Users have a Role
  2. A Role can have multiple Resources
  3. Resources can have multiple Privileges

Roles are really just collections of Resource Privileges with no hierarchy. An example of a Resource would be 'Page'. Everyone can view the pages, but a authenticated user would need 'add', 'edit', or 'delete' privileges to do anything else with pages.

Does this mesh with Zend ACL? Am I thinking ACL in a way that's going to create problems for me?


My Solution

Typeonerror gets the credit, but here's my specific solution.

I extended Zend_Acl to simplify my usage because I only load the role of the current user:

class My_Acl extends Zend_Acl
{
    protected $_role_id;

    public function setRole($role_id)
    {
        $this->_role_id = $role_id;
        return $this->addRole($role_id);
    }

    public function getRole()
    {
        return $this->_role_id;
    }

    public function deny($resource, $privilege)
    {
        return parent::deny($this->_role_id, $resource, $privilege);
    }

    public function allow($resource, $privilege)
    {
        return parent::allow($this->_role_id, $resource, $privilege);
    }

    public function isAllowed($resource, $privilege)
    {
        return parent::isAllowed($this->_role_id, $resource, $privilege);
    }
}

To populate the the ACL I execute a query which returns resource, privilege, and role_id columns. The role_id column is null in the result set if the user's role does not have that privilege.

$acl = new My_Acl();

$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
    $userInfo = $auth->getStorage()->read();
    $acl->setRole($userInfo->role_id);
} else {
    $acl->setRole('');
}

// QUERY HERE

foreach ($privileges as $privilege) {
    if (!$acl->has($privilege['resource'])) {
        $acl->addResource($privilege['resource']);
    }
    if (is_null($privilege['role_id'])) {
        $acl->deny($privilege['resource'], $privilege['privilege']);
    } else {
        $acl->allow($privilege['resource'], $privilege['privilege']);
    }
}
+4  A: 

That's exactly how it works and I think you're thinking about it in an accurate way. You can add your resources and then add privileges to allow certain user roles to access them. For example, in my CMS, I have "developers", "admins", and "users". In the code below I add general access and then remove some actions and specific methods from certain user's access. Of course this is pretty specific to my application but basically, you'd have to get the user's role from auth->getIdentity() (or similar) and then add your roles/resources from the database.

<?php

/**
 * @author     Benjamin Borowski <[email protected]>
 * @copyright  Copyright (c) Typeoneerror Studios http://typeoneerror.com
 * @version    $Id$
 * @category   Typeoneerror
 * @package    Acl
 */

/**
 * Defines basic roles and resources for an application as
 * well as a Content Management System (CMS).
 *
 * Zend_Acl provides a lightweight and flexible access control list
 * (ACL) implementation for privileges management.
 *
 * {@inheritdoc}
 *
 * @author     Benjamin Borowski <[email protected]>
 * @copyright  Copyright (c) Typeoneerror Studios http://typeoneerror.com
 * @version    $Id$
 * @category   Typeoneerror
 * @package    Acl
 */
class Typeoneerror_Acl extends Zend_Acl
{
    /**
     * Constructor function.
     *
     * Creates basic roles and resources and adds them to Acl.
     *
     * {@inheritdoc}
     *
     * @return Typeoneerror_Acl
     */
    public function __construct()
    {
        //---------------------------------------
        // ROLES
        //---------------------------------------

        $this->_addRole("guest")
             ->_addRole("member", "guest")
             ->_addRole("admin", "member")
             ->_addRole("developer", "admin");

        //---------------------------------------
        // FRONT-END RESOURCES
        //---------------------------------------

        $this->_add("default");

        //---------------------------------------
        // BACK-END RESOURCES
        //---------------------------------------

        $this->_add("cms")
             ->_add("cms:articles", "cms")
             ->_add("cms:auth", "cms")
             ->_add("cms:bug-report", "cms")
             ->_add("cms:calendar", "cms")
             ->_add("cms:categories", "cms")
             ->_add("cms:comments", "cms")
             ->_add("cms:error", "cms")
             ->_add("cms:galleries", "cms")
             ->_add("cms:pages", "cms")
             ->_add("cms:photos", "cms")
             ->_add("cms:tags", "cms")
             ->_add("cms:users", "cms");

        //---------------------------------------
        // GUEST PERMISSIONS
        //---------------------------------------

        $this->allow("guest", "default")
             ->allow("guest", "cms:auth")           // -- guests can attempt to log-in
             ->allow("guest", "cms:error")          // -- guests can break stuff
             ->allow("guest", "cms:bug-report");    // -- guests can report bugs

        //---------------------------------------
        // ADMIN PERMISSIONS
        //---------------------------------------

        $this->allow("admin")
             ->deny("admin", null, "purge")                       // -- admins cannot purge (normally)
             ->deny("admin", "cms:comments", "create");           // -- only devs can create a comment

        //---------------------------------------
        // DEVELOPER PERMISSIONS
        //---------------------------------------

        $this->allow("developer");             // -- unrestricted access

        return $this;
    }

    /**
     * Adds a Resource having an identifier unique to the ACL.
     *
     * @param Zend_Acl_Resource_Interface $resource       The resource to add
     * @param Zend_Acl_Resource_Interface|string $parent  A parent resource it inherits from
     * @return Typeoneerror_Acl                           Reference to Acl class
     */
    protected function _add($resource, $parent = null)
    {
        $this->add(new Zend_Acl_Resource($resource), $parent);

        return $this;
    }

    /**
     * Wrapper for <code>addRole</code>
     *
     * @param Zend_Acl_Resource_Interface $resource        The resource to add
     * @param Zend_Acl_Resource_Interface|string $parents  Parent resources it inherits from
     * @return Typeoneerror_Acl                            Reference to Acl class
     */
    protected function _addRole($role, $parents = null)
    {
        $this->addRole(new Zend_Acl_Role($role, $parents));

        return $this;
    }

}

Edit

Guess I should also explain that I have an Typeoneerror_Controller_Plugin_Acl which is used whenever any resource is requested. Here I create the "tag" that the requested resource makes and check whether the user has access to that tag:

    $controller = $request->controller;
    $action = $request->action;
    $module = (empty($request->module)) ? "default" : $request->module;

    // -- this ends up like "cms:articles" just like my resources
    $resource = $module . ":" . $controller;

    if (!$this->__acl->has($resource))
    {
        $resource = $module;
    }

    // -- the good stuff. check if the user's role can access the resource and action
    if (!$this->__acl->isAllowed($role, $resource, $action))
    {
        //more code 
    }
Typeoneerror
I'm seeing the levels of User, Role, and Resource in your example. I have User, Role, Resource, Privilege. A User has one Role, a Role can have multiple Resources, and a Resource can have multiple Privileges. Am I misunderstanding something in your solution?
Sonny
Sounds about right. The way the users are set up, each user inherits privileges from it's parent. So "admin" gets all of "guest" and "members" privileges. Under admin, I allow "all" and then deny the "purge" action on all resources and deny the "create" action on only the "cms:comments" resourse. So they can still access the "cms:comments->view" or "cms:comments->moderate" actions.
Typeoneerror
Added some code.
Typeoneerror
My model doesn't have Role or Resource inheritance. Are you combining Resources and Privileges? Rather than have a 'Page' resource with 'add', 'edit', or 'delete' privileges you have 'Page:add', 'Page:edit', and 'Page:delete' resources?
Sonny
Not really, no. I'm using different names than you: In my system "Page" would be the resource, but it's tied to a module. "cms:Page" (back-end) would be a different resource than "default:Page" (front-end). The privileges are what I called "actions"; perhaps privs is a better name. Hope that makes sense.
Typeoneerror