Zend ACL – Implementation

Zend ACL is the Zend Framework’s solution for authorization. It is indeed a flexible and standard method to insure that the users gain access to only what they are authorized.

Key objects when dealing with Zend_ACL are

  1. Role : a ‘group’ of users.. example ‘guest’ or ‘admin’
  2. Resource: Typically the ‘controller’
  3. Privilege : Typically the ‘action’

By making the Zend Resource and Privilege correspond to the Controller and Action respectively, we can automate the privilege checking process(the request object gives us access to the controller name and action name). Remember that while authentication is a one-time affair, authorization needs to be checked on every page fetch.

We therefore create a ‘front plugin’ and code the ‘preDispatch’ function (automatically gets triggered on EVERY request) to check whether the controller and action corresponding to the request are within the access level of the role assigned to the user. If not, we modify the controller/action to display the auth/login page.

Once this system is setup, it is completely self sustaining.. i.e. only the database tables need to be updated in case controllers/actions are added/removed. The following describes the approach that I use in my projects to automate Authorization.

Start off by creating a table of ALL controllers and actions in your application (Note that missing even a single controller or action could lead to accessibility problems)

Controller Action
index index
auth login
auth logout
main index
main view
main update

Next, identify the Roles that you would like to assign.. Roles/groups that are typically used are ‘guest’, ‘author’,’admin’ etc. The idea is to control access using these roles and then assign users to these roles. This makes management easy (by avoiding per-user privileges).

For the sake of simplicity, let us assume that the roles in our sample app are ‘guest’ and ‘admin’. By default, all users are granted ‘guest’ access.

User Table

id userid password user_role
1 test1 blah guest
2 test2 asdf admin

Our user table has the structure as shown above (the password field can be skipped if you use a third party like LDAP  for authentication). After successful authentication (using Zend_Auth), the user_role field is stored in a session variable for easy global access.

Its now time to focus on our main ACL table. In all my applications, I create a database table named myacl with the following fields:

myacl

acl_role_name acl_resource_name acl_privilege_name

For populating this table, we will use the table of controllers and actions that we developed earlier. Then, carefully assign roles to the Resources/Controllers and Privileges/Actions. Our security criteria is "Everyone should have access to all the areas except the main controller’s index, view and update actions, which requires the Admin role".

The role assignment below captures this scenario:

acl_role_name acl_resource_name acl_privilege_name
guest index index
guest auth login
guest auth logout
guest error error
admin main index
admin main view
admin main update

 

Note that the guest role name(our default role) is granted access to error (the error controller/error action automatically handles errors in the zend framework and we need all to have access to the displayed error page)

The next important step is to define "role inheritance".  In our example, if a user is an ‘admin’ user, he/she should automatically inherit all guest privileges. In order to make this easily programmable, we will create a table named  acl_role:

acl_role

acl_role_id acl_role_name acl_inherits create_order
1 guest (null) 1
2 admin guest 2

When creating Zend_Acl_Role objects, the object corresponding to the guest object must already be created so that the admin object can inherit from it. The "create_order" field helps us in automating this process (It is possible to inherit from multiple roles but we will not consider that in this example). Also, note that acl roles must be unique within the scope of the application.

With this base, we can now code our custom ACL object.

We create it as a singleton object in the appropriate directory. MyAcl::getInstance() will retrieve an instance of our acl object for use in the plugin (next step)

  1. Loop through all the rows in myacl table joined with acl_role (the order of selection is by the create_order field) – You can also create a view on your database to return this join
  2. Note that the database parameters have been set up in our index.php file using Zend_Db::create. Zend_Db_Table.setDefaultAdapter($db) is also assumed to have been executed in the index file.
  3. Establish the Zend_Acl_Reso
    urce and Zend_Acl_Role objects with the appropriate inheritance
  4. A second pass through the acl’s uses the ‘Allow’ command to grant privileges as defined in the myacl table

<?php
 class MyAcl extends Zend_Acl
  {
    protected static $_instance=null;
  private function __construct()
  {

  }
  protected function _initialize()
  {
 //singleton pattern..
 //the db adapter has been set in the index..use it here!
    $db = Zend_Db_Table::getDefaultAdapter();

$roles = $db->fetchAll("select * from myacl inner join acl_role on

acl_role.acl_role_name=myacl.acl_role_name order by create_order");

 foreach ($roles as $role)
    {
 if (!$this->has($role['acl_resource_name']))
      {
        $this->add(new Zend_Acl_Resource($role['acl_resource_name']));
      }
 if (!$this->hasRole($role['acl_role_name']))
      {
        $inherit = $role['acl_inherits'];
 if ($inherit['acl_inherits']!=null)
          $this->addRole(new Zend_Acl_Role($role['acl_role_name']), $inherit['acl_inherits']);
 else
          $this->addRole(new Zend_Acl_Role($role['acl_role_name']));
      }
    }
 //start with a clean slate..deny everything
    $this->deny(null);
 foreach ($roles as $role)
    {
      $this->allow($role['acl_role_name'], $role['acl_resource_name'], $role['acl_privilege_name']);
    }
  }
  public static function getInstance()
  {
 if (self::$_instance==null)
    {
      self::$_instance=new self();
      self::$_instance->_initialize();
    }
 return self::$_instance;
  }
  }
?>

 

We next code the front plugin that consumes our custom acl class. The main points are interest are

  1. We create an instance and store our object in the session. Doing a database lookup and rebuilding the acl for every request could potentially slow down your app.
  2. The request object gives us access to the controller name and action name
  3. We retrieve the users role from session namespace ‘UserInfo‘ (that is set by the authentication module after successful lookup)
  4. Default role assigned is ‘guest’
  5. We use the isAllowed() function to check permissions and redirect to the login page if not allowed.

 


<?php
class MyAclPlugin extends Zend_Controller_Plugin_Abstract
{
    private $_defaultRole='guest';
  private $_authController=array('controller'=>'auth','action'=>'login');

  public function preDispatch(Zend_Controller_Request_Abstract $request)
  {
 //check for version in session..
    $sess_acl = new Zend_Session_Namespace('aclNamespace');
 if (!isset($sess_acl->acl))
    {
      $acl = MyAcl::getInstance();
      $sess_acl->acl = $acl;  //push into session for next time!
    }
 else //found in session..reuse
    {
      $acl = $sess_acl->acl;
    }

    $auth = Zend_Auth::getInstance();
 if ($auth->hasIdentity())
    {
 //get the logged in users role from session..
      $session = new Zend_Session_Namespace('UserInfo');
      $role = $session->userRole;
    }
 else
      $role=$this->_defaultRole;

    $resource = $request->getControllerName();
    $privilege = $request->getActionName();

 if(!$acl->has($resource))
    {
      $resource=error;
      $privilege=error;
    }
 //now do the actual check!
 if (!$acl->isAllowed($role, $resource, $privilege))
    {
      $request->setControllerName($this->_authController['controller']);
      $request->setActionName($this->_authController['action']);
    }
  }
}
?>

 

Just to give you a complete picture of how this plugin is actually invoked, here is my index.php file


<?php
set_include_path(PATH_SEPARATOR.get_include_path()
.PATH_SEPARATOR.'../application/models'
.PATH_SEPARATOR.'../application/include');

include_once "Zend/Loader.php";
Zend_Loader::registerAutoLoad();
try
{
 //get the ini file
    $config = new Zend_Config_Ini('../application/config.ini', 'general');
    Zend_Registry::set('config', $config);

 //get a database handle for later use
    $db = Zend_Db::factory($config->db);
    Zend_Db_Table::setDefaultAdapter($db);
    Zend_Registry::set('db', $config);

    $auth = Zend_Auth::getInstance();
    $auth->setStorage(new Zend_Auth_Storage_Session());

 //now, set the controller
    $controller = Zend_Controller_Front::getInstance();
    $controller->setControllerDirectory('../application/controllers');
    $controller->registerPlugin(new MyAclPlugin());
    $controller->throwExceptions(true);
    $controller->dispatch();
}
catch(Exception $ex)
{
 header('Content-Type: text/html');
 echo 'An unexpected error occurred';
 echo '<h2>'.$ex->getMessage().'</h2>';
}
?>

					
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s