Creating ACL with database in Zend Framework v1.12

The creation of a secure and powerful ACL (Access control list) it’s one of the most delicate and important pieces for building a sturdy website.  I’ll try to make this task easier sharing the code I used in one of my latest projects. This ACL system works with a MySQL database which grant us total flexibility creating users and roles.

Creating database tables

The first step is to create the necessary tables in database:

Table roles
For storing the roles or groups. Each role have is own privileges. This roles will be assigned to each user, and the users will inherit the role privileges.

CREATE TABLE `roles` (
  `id` tinyint(1) NOT NULL AUTO_INCREMENT,
  `role` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

Now we add some roles to the table:
Anonymous: For non-registered visitors.
Registered: For registered visitors.
Admin: For super users.

INSERT INTO roles (id, role) VALUES (1, 'Anonymous');
INSERT INTO roles (id, role) VALUES (2, 'Registered');
INSERT INTO roles (id, role) VALUES (3, 'Admin');

Table acl
To store all the controllers/actions. Each row means a different action that can be performed by the application.

CREATE TABLE `acl` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `controller` varchar(100) NOT NULL,
  `action` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `controller` (`controller`,`action`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 
  CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC;

Table acl_to_roles
Establish the relation between the roles and actions. In other words: Which actions can perform each role/group.

CREATE TABLE `acl_to_roles` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `acl_id` int(10) NOT NULL,
  `role_id` tinyint(10) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `acl_id` (`acl_id`),
  KEY `role_id` (`role_id`),
  CONSTRAINT `acl_to_roles_ibfk_1` FOREIGN KEY (`acl_id`) 
     REFERENCES `acl` (`id`) ON DELETE CASCADE,
  CONSTRAINT `acl_to_roles_ibfk_2` FOREIGN KEY (`role_id`) 
     REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

Table users
It contains the field role_id, which establish the privileges that each user will inherit from the roles, and the general information about the users: login, password, ETC.

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` tinyint(1) DEFAULT '1',
  `login` varchar(50) DEFAULT NULL,
  `password` varchar(32) DEFAULT NULL,
  `salt` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `login_index` (`login`),
  KEY `password_index` (`password`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 
  CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC;

Create the ACL plugin

Now we need to create the ACL plugin. It will be located in the following path within our library folder: “/library/MyProject/Controller/Plugin/Acl.php

<?php
class MyProject_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
{
	public function preDispatch(Zend_Controller_Request_Abstract $request)
	{
		$auth = Zend_Auth::getInstance();
		//var_dump($auth->getIdentity());
		$authModel=new Application_Model_Auth();
		if (!$auth->hasIdentity()){
			//If user doesn't exist it will get the Guest account from "users" table Id=1
			$authModel->authenticate(array('login'=>'Guest','password'=>'shocks'));
		}

		$request=$this->getRequest();
		$aclResource=new Application_Model_AclResource();
		//Check if the request is valid and controller an action exists. If not redirects to an error page.
		if(	!$aclResource->resourceValid($request)){
			$request->setControllerName('error');
			$request->setActionName('error');
			return;
		}

		$controller = $request->getControllerName();
		$action = $request->getActionName();
		//Check if the requested resource exists in database. If not it will add it
		if(	!$aclResource->resourceExists($controller, $action)){
			$aclResource->createResource($controller,$action);
		}
		//Get role_id
		$role_id=$auth->getIdentity()->role_id;
		$role=Application_Model_Role::getById($role_id);
		$role=$role[0]->role;
		// setup acl
		$acl = new Zend_Acl();
		// add the role
		$acl->addRole(new Zend_Acl_Role($role));
		if($role_id==3){//If role_id=3 "Admin" don't need to create the resources
			$acl->allow($role);
		}else{
			//Create all the existing resources
			$resources=$aclResource->getAllResources();	
			// Add the existing resources to ACL
			foreach($resources as $resource){
				$acl->add(new Zend_Acl_Resource($resource->getController()));
					
			}		
			//Create user AllowedResources
			$userAllowedResources=$aclResource->getCurrentRoleAllowedResources($role_id);				
			
			// Add the user permissions to ACL
			foreach($userAllowedResources as $controllerName =>$allowedActions){
				$arrayAllowedActions=array();
				foreach($allowedActions as $allowedAction){
					$arrayAllowedActions[]=$allowedAction;
				}
				$acl->allow($role, $controllerName,$arrayAllowedActions);
			}
		}
		//Save ACL so it can be used later to check current user permissions
		Zend_Registry::set('acl', $acl);
		//Check if user is allowed to acces the url and redirect if needed
		if(!$acl->isAllowed($role,$controller,$action)){
			$request->setControllerName('error');
			$request->setActionName('access-denied');
			return;
		}
	}
}

Update config.ini and Bootstrap.php files

Add the following line with the path of your plugin to your config.ini or application.ini file:
resources.frontController.plugins.acl = “MyProject_Controller_Plugin_Acl”
Now we need to register the namespace, so we have to add the following function to the Bootstrap.php file.

/* Application/Bootstrap.php */

protected function _initAutoload() {
		// Add autoloader empty namespace
		$autoLoader = Zend_Loader_Autoloader::getInstance ();
		$autoLoader->registerNamespace('MyProject_');
		// Return it so that it can be stored by the bootstrap
		return $autoLoader;
}

Create the model AclResource

We need to create the model AclResource which have methods that are used by the plugin, like getting the Acl resources from database or checking whether resources exist and are valid.

<?php
/* Application/models/AclResource.php */
class Application_Model_AclResource extends Application_Model_Mapper
{
	protected $Model_DbTable='Application_Model_DbTable_Acl';
	protected $Model_DbView=null;
	protected $id;
	protected $controller;
	protected $action;

	public function __set($name, $value)
	{
		$method = 'set' . $name;
		if (('mapper' == $name) || !method_exists($this, $method)) {
			throw new Exception('Error __set() function, Invalid  property');
		}
		$this->$method($value);
	}

	public function __get($name)
	{
		$method = 'get' . $name;
		if (('mapper' == $name) || !method_exists($this, $method)) {
			throw new Exception('Error __get() function, Invalid  property');
		}
		return $this->$method();
	}

	public function setOptions(array $options)
	{
		$methods = get_class_methods($this);
		foreach ($options as $key => $value) {
			$method = 'set' . ucfirst($key);
			if (in_array($method, $methods)) {
				$this->$method($value);
			}
		}
		return $this;
	}
	public function getModel_DbTable(){
		return $this->Model_DbTable;
	}

	public function getModel_DbView(){
		return $this->Model_DbView;
	}

	public function getController(){
		return $this->controller;
	}
	public function setController($text){
		$this->controller = (string) $text;
		return $this;
	}

	public function setAction($text){
		$this->action = (string) $text;
		return $this;
	}

	public function getAction(){
		return $this->action;
	}

	public function setId($text){
		$this->id = (string) $text;
		return $this;
	}

	public function getId(){
		return $this->id;
	}

	public static function getByColumn($column = null,$value = null){
		$mapper=new self();
		$out=$mapper->MapperGetByColumn($column,$value);
		return $out;
	}
	public static function getBySQLCondition(array $conditions ){
		$mapper=new self();
		$out=$mapper->MapperGetBySQLCondition($conditions);
		return $out;
	}

	public static function select($sql=null, $params=null){
		$mapper=new self();
		$out=$mapper->MapperSelect($sql,$params);
		return $out;
	}
	public static function save(array $data){
		$mapper=new self();
		$out=$mapper->MapperSave($data);
		return $out;
	}

	public static function resourceExists($controller=null,$action=null){
		if(!$controller || !$action) throw new Exception("Error resourceExists(), the controller/action is empty");
		$result=self::getBySQLCondition(array('controller=?'=>$controller,'action=?'=>$action));
		if(count($result)){
			return true;
		}
		return false;
	}

	public static function resourceValid($request){
		// Check if controller exists and is valid
		$dispatcher = Zend_Controller_Front::getInstance()->getDispatcher();
		if (!$dispatcher->isDispatchable($request)) {
			return false;
		}
		// Check if action exist and is valid
		$front      = Zend_Controller_Front::getInstance();
		$dispatcher = $front->getDispatcher();
		$controllerClass = $dispatcher->getControllerClass($request);
		$controllerclassName = $dispatcher->loadClass($controllerClass);
		$actionName = $dispatcher->getActionMethod($request);
		$controllerObject = new ReflectionClass($controllerclassName);		
		if(!$controllerObject->hasMethod($actionName)){
			return false;	
		}		
		return true;
	}
	public function createResource($controller=null,$action=null){
		if(!$controller || !$action) throw new Exception("Error resourceExists(), the controller/action is empty");
		$data=array('controller'=>$controller,'action'=>$action);
		return self::save($data);
	}

	public function getCurrentRoleAllowedResources($role_id=null){
		if(!$role_id) throw new Exception("Error getCurrentUserPermissions(), the role_id is empty");
		$db = Zend_Db_Table::getDefaultAdapter();
		$sql='SELECT A.controller,A.action  FROM acl_to_roles ATR INNER JOIN acl A ON A.id=ATR.acl_id WHERE role_id=? ORDER BY A.controller';
		$stmt = $db->query($sql,$role_id);
		$out= $stmt->fetchAll();
		$controller='';
		$resources=array();
		foreach ($out as $value){
			if($value['controller']!=$controller){
				$controller=$value['controller'];
			}
			$resources[$controller][]=$value['action'];
		}
		return $resources;
	}

	public static function getAll(){
		$mapper=new self();
		$out=$mapper->MapperFetchAll();
		return $out;
	}
	public function getAllResources(){
		$mapper=new self();
		$sql='SELECT controller FROM acl GROUP BY controller';
		$out=$mapper->select($sql);
		return $out;
	}

}

Create the Authorization model (Zend_Auth)

Now we need to create a model to authenticate users. In this example I’m using a instance of Zend_Auth to perform that task and a Salt generator to improve the passwords security.

<?php

class Application_Model_Auth extends Application_Model_Mapper
{

	public static function authenticate(array $values)
	{
		if(!count($values)) throw new Exception('Exception trying to authenticate using empty values');
		// Get our authentication adapter and check credentials
		$loginValue=$values['login'];
		$adapter = self::getAuthAdapter('login');
		$adapter->setIdentity($loginValue);
		$adapter->setCredential($values['password']);
		$auth = Zend_Auth::getInstance();
		$result = $auth->authenticate($adapter);
		if ($result->isValid()) {
			$user = $adapter->getResultRowObject();
			$auth->getStorage()->write($user);
			return true;
		}
		return false;
	}

	/**
	 * This function generates a password salt as a string of x (default = 15) characters
	 * ranging from a-zA-Z0-9.
	 * @param $max integer The number of characters in the string
	 */
	public static function generateSalt($max = 15) {
		$characterList = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
		$i = 0;
		$salt = "";
		do {
			$salt .= $characterList{mt_rand(0,strlen($characterList)-1)};
			$i++;
		} while ($i <= $max);
		return $salt;
	}

	public static function logOut(){
		Zend_Auth::getInstance()->clearIdentity();
	}

	private static function getAuthAdapter($loginField='login')
	{
		$dbAdapter = Zend_Db_Table::getDefaultAdapter();
		$authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter);
		$authAdapter->setTableName('users')
		->setIdentityColumn($loginField)
		->setCredentialColumn('password')
		->setCredentialTreatment('MD5(CONCAT(?,salt))');
		return $authAdapter;
	}




}

Create the Mapper (Application_Model_Mapper)

The Mapper is a cunning way to perform all the common database requests. If we need any model to have access to a certain database table we just have to create a class extending the Application_Model_Mapper and follow the steps detailed below.

<?php

class Application_Model_Mapper
{
	protected $_dbTable;

	public function setDbTable($dbTable)
	{
		if (is_string($dbTable)) {
			$dbTable = new $dbTable();
		}
		if (!$dbTable instanceof Zend_Db_Table_Abstract) {
			throw new Exception('Invalid table data gateway provided');
		}
		$this->_dbTable = $dbTable;
		return $this;
	}

	protected function getDbTable($useViewTable=true)
	{
		if (null === $this->_dbTable) {
			if(null === $this->Model_DbTable){
				throw new Exception('There is no Model_DbTable. You must define the Model_DbTable variable on the model in order to get the correct database table.');
			}
			$table=(null !== $this->Model_DbView && $useViewTable)?$this->Model_DbView:$this->Model_DbTable;
			$this->setDbTable($table);
		}
		return $this->_dbTable;
	}

	protected function MapperSave(array $data)
	{
		$currentModel=get_class($this);//Get the class that is extending the mapper
		foreach($data as $index=>$value){
			if(!property_exists($currentModel,$index)){
				unset($data[$index]);//Delete key if not exist on the object
			}
		}
		$id=(isset($data['id']))?$data['id']:null;
		if (null ===$id ) {
			unset($data['id']);
			$out= $this->getDbTable(false)->insert($data);
		} else {
			$this->getDbTable(false)->update($data, array('id = ?' => $id));
			$out=$id;
		}
		return $out;
	}

	protected function MapperGetByColumn($column=null,$value=null,$order=array('id DESC')){
		if(!$column || !$value)throw new Exception("Error -> MapperGetByColumn (The params are empty)");
		$currentModel=get_class($this);//Get the class that extends the mapper
		if(!property_exists ($currentModel,$column))throw new Exception("Error -> getByColumn (The Column '_{$column}' doesn't exist on model '{$currentModel}')");
		$result =$this->getDbTable()->select()->where("$column = ?", $value)->order($order);
		$resultSet=$this->getDbTable()->fetchAll($result);
		$currentModel=get_class($this);//Get the class that extends the mapper
		$entries=array();
		foreach ($resultSet as $row) {
			$data=$row->toArray();
			$entry = new $currentModel();
			$entry->setOptions($data);
			$entries[] = $entry;
		}
		return $entries;
	}

	protected function MapperDeleteByColumn($column=null,$value=null){
		if(!$column || !$value)throw new Exception("Error -> MapperDeleteByColumn (The params are empty)");
		$currentModel=get_class($this);//Get the class that extends the mapper
		if(!property_exists ($currentModel,$column))throw new Exception("Error -> MapperDeleteByColumn (The Column '_{$column}' doesn't exist on model '{$currentModel}')");
		$result =$this->getDbTable()->delete(array("$column=?"=>$value));
		return $result;
	}

	protected function MapperSelect($sql=null, $params = null){
		if(!$sql)throw new Exception("Error -> MapperSelect (The sql is empty)");
		$db = Zend_Db_Table::getDefaultAdapter();
		$stmt = $db->query($sql,$params);
		$resultSet= $stmt->fetchAll();
		$currentModel=get_class($this);//Get the class that extends the mapper
		$entries=array();
		foreach ($resultSet as $data) {
			$entry = new $currentModel();
			$entry->setOptions($data);
			$entries[] = $entry;
		}
		return $entries;

	}

	/*
	 * MapperGetBySQLCondition
	 * @param $conditions must be an array with the WHERE conditions and values like the following:
	 * array(
	 * 		'product_name = ?'=>'Wimax',
	 * 		'id <> ?'=>2
	 * )
	 * @param $OR if this is setted to true the conditions will be joined with an "OR" statement
	 * ex: select * from example WHERE product=1 OR id=2
	 */
	protected function MapperGetBySQLCondition(array $conditions,$use_OR=false,$order=array('id DESC')){
		if(!count($conditions))throw new Exception("Error -> MapperGetBySQLCondition (The conditions are empty)");
		$result =$this->getDbTable()->select();
		$first=true;
		foreach($conditions as $sql=>$value){
			$where=($use_OR && !$first)?'orWhere':'where';
			$result=$result->$where($sql, $value);
			$first=false;
		}
		$result->order($order);
		$resultSet=$this->getDbTable()->fetchAll($result);
		$currentModel=get_class($this);//Get the class that extends the mapper
		$entries=array();
		foreach ($resultSet as $row) {
			$data=$row->toArray();
			$entry = new $currentModel();
			$entry->setOptions($data);
			$entries[] = $entry;
		}
		return $entries;

	}

	protected function MapperGetById($id=null)
	{
		if(!$id)throw new Exception("Error -> MapperGetById (The ID is empty)");
		$result = $this->getDbTable()->find($id);
		$entries   = array();
		if (0 == count($result)) {
			return;
		}
		$row = $result->current()->toArray();
		$currentModel=get_class($this);//Get the class that extends the mapper
		$entry = new $currentModel();
		$entry->setOptions($row);
		$entries[] = $entry;
		return $entries;
	}


	protected function MapperFetchAll($order=array('id DESC'))
	{
		$resultSet = $this->getDbTable()->fetchAll(null,$order);
		$entries   = array();
		$currentModel=get_class($this);//Get the class that extends the mapper
		foreach ($resultSet as $row) {
			$data=$row->toArray();
			$entry = new $currentModel();
			$entry->setOptions($data);
			$entries[] = $entry;
		}
		return $entries;
	}

	public function toArray(){
		$class=get_class($this);
		$properties=get_class_vars ($class);
		$result=array();
		foreach($properties['viewColumns'] as $viewProperty){
			$properties[$viewProperty]=null;
		}
		unset($properties['viewColumns']);
		unset($properties['_dbTable']);
		foreach($properties as $key=> $value){
			$result[$key]=$this->$key;
		}
		return $result;
	}

}

Create the User and Role Models

Finally we need to create the “application/models/User.php” and “application/models/Role.php” models. All models which have access to a certain database table will need variable called “$Model_DbTable”. For security reasons this variable contains the name of the class which contains the real db-table name. All these db access classes are stored in “application/models/DbTable/”.

User model

<?php
/* Application/models/User.php */
class Application_Model_User  extends Application_Model_Mapper
{
	protected $Model_DbTable='Application_Model_DbTable_User';
	protected $Model_DbView;
	protected $id;
	protected $role_id;
	protected $login;
	protected $password;
	protected $salt;
	protected $viewColumns=array();

	public function __set($name, $value)
	{
		if (('mapper' == $name) || !property_exists ($this, $name)) {
			throw new Exception('Error __set() function, Invalid  property');
		}
		$this->$name = (string) $value;
	}

	public function __get($name)
	{
		if (('mapper' == $name) || !property_exists($this, $name)) {
			if(array_key_exists ($name,$this->viewColumns)){
				return $this->viewColumns[$name];
			}
			throw new Exception("Error __get() function, Invalid  property '$name'");
		}
		return $this->$name;
	}

	public function setOptions(array $options)
	{
		foreach ($options as $key => $value) {
			if (property_exists($this, $key)) {
				$this->$key= (string) $value;
			}elseif(in_array($key,$this->viewColumns)){//Check if the property is a view
				$this->viewColumns[$key]=(string)$value;
				unset($this->viewColumns[array_search($key, $this->viewColumns)]);
			}
		}
		return $this;
	}

	public static function getByColumn($column = null,$value = null,$order=null){
		$mapper=new self();
		$out=$mapper->MapperGetByColumn($column,$value,$order);
		return $out;
	}
	public static function getBySQLCondition(array $conditions,$is_OR=false,$order=null ){
		$mapper=new self();
		$out=$mapper->MapperGetBySQLCondition($conditions,$is_OR,$order);
		return $out;
	}
	public static function getAll($order=null){
		$mapper=new self();
		$out=$mapper->MapperFetchAll($order);
		return $out;
	}
	/**
	 *
	 * Create and returns a new user
	 * @param array $data: it requires at least three parameters: $data['role_id',  $data['login'] and  $data['password']
	 * @throws Exception
	 */
	public static function createUser(array $data){
		if(!$data['role_id'] || !$data['login'] || !$data['password'])throw new Exception('role_id, login and password must be provided in the function createUser().');
		$data['id']=self::save($data);
		$data['salt']=Application_Model_Auth::generateSalt();
		$data['password']=md5($data['password'].$data['salt']);
		self::save($data);
		//Return created user:
		return self::getById($data['id']);
	}

	public static function select($sql=null, $params=null){
		$mapper=new self();
		$out=$mapper->MapperSelect($sql,$params);
		return $out;
	}
	public static function save(array $data){
		$mapper=new self();
		$out=$mapper->MapperSave($data);
		return $out;
	}

	public static function getById($id = null){
		$mapper=new self();
		$out=$mapper->MapperGetById($id);
		return $out;
	}
	public static function getCurrentUser(){
		$auth = Zend_Auth::getInstance();
		if (!$auth->hasIdentity()){
			return 0;
		}
		$id= $auth->getIdentity()->id;
		$user=Application_Model_User::getById($id);
		return (count($user))? $user[0]->toArray() : 0;
	}

	public function getViewProperty($column=null){
		return (in_array($column,$this->viewColumns))? $column:null;
	}

	/**
	* Check if the user is allowed to access the provided Controller->action
	**/
	public static function isAllowed($controller, $action){
		//Get Current user role
		$auth = Zend_Auth::getInstance();
		$role=Application_Model_Role::getById($auth->getIdentity()->role_id);
		$acl = Zend_Registry::get('acl');
		return ($acl->isAllowed($role[0]->role, $controller, $action))?true : false;	
	}


Role model

<?php
/* Application/models/Role.php */
class Application_Model_Role  extends Application_Model_Mapper
{
	protected $Model_DbTable='Application_Model_DbTable_Role';
	protected $Model_DbView;
	protected $id;
	protected $role;
	protected $viewColumns=array();

	public function __set($name, $value)
	{
		if (('mapper' == $name) || !property_exists ($this, $name)) {
			throw new Exception('Error __set() function, Invalid  property');
		}
		$this->$name = (string) $value;
	}

	public function __get($name)
	{
		if (('mapper' == $name) || !property_exists($this, $name)) {
			if(array_key_exists ($name,$this->viewColumns)){
				return $this->viewColumns[$name];
			}
			throw new Exception("Error __get() function, Invalid  property '$name'");
		}
		return $this->$name;
	}

	public function setOptions(array $options)
	{
		foreach ($options as $key => $value) {
			if (property_exists($this, $key)) {
				$this->$key= (string) $value;
			}elseif(in_array($key,$this->viewColumns)){//Check if the property is a view
				$this->viewColumns[$key]=(string)$value;
				unset($this->viewColumns[array_search($key, $this->viewColumns)]);
			}
		}
		return $this;
	}


	public static function getByColumn($column = null,$value = null,$order=null){
		$mapper=new self();
		$out=$mapper->MapperGetByColumn($column,$value,$order);
		return $out;
	}
	public static function getBySQLCondition(array $conditions,$is_OR=false,$order=null ){
		$mapper=new self();
		$out=$mapper->MapperGetBySQLCondition($conditions,$is_OR,$order);
		return $out;
	}
	public static function getAll($order=null){
		$mapper=new self();
		$out=$mapper->MapperFetchAll($order);
		return $out;
	}

	public static function select($sql=null, $params=null){
		$mapper=new self();
		$out=$mapper->MapperSelect($sql,$params);
		return $out;
	}
	public static function save(array $data){
		$mapper=new self();
		$out=$mapper->MapperSave($data);
		return $out;
	}

	public static function getById($id = null){
		$mapper=new self();
		$out=$mapper->MapperGetById($id);
		return $out;
	}

}

The DB-Table Classes

As mentioned before, These are the needed db-table classes for the models User.php, Role.php and AclResource.php. All located in “Application/models/DbTable/”.

Application_Model_DbTable_Acl for model AclResource.php

<?php
/* Application/models/DbTable/Acl.php */
class Application_Model_DbTable_Acl extends Zend_Db_Table_Abstract
{
	protected $_name = 'acl';
}

Application_Model_DbTable_User for model User.php

<?php
/* Application/models/DbTable/User.php */
class Application_Model_DbTable_User extends Zend_Db_Table_Abstract
{
	protected $_name = 'users';
}

Application_Model_DbTable_Role for model Role.php

<?php
/* Application/models/DbTable/Role.php */
class Application_Model_DbTable_Role extends Zend_Db_Table_Abstract
{
	protected $_name = 'roles';
}

Create the user “Guest”

Last thing, you will need to add a default user for all the non-registered visitors. Run the following command in any script just once and check the “users” table to be sure that the user “Guest” has been created.

Application_Model_User::createUser( array('role_id' => 1, 'login' => 'Guest', 'password' => 'shocks'));

Comments 42

  1. Thanks for the code/tutorial – it all seems nice and clear

    Would it be possible for you to give some hints/code for Application_Model_Mapper?

  2. Post
    Author

    In this case I’m using a master mapper class which makes easier the database requests. Thus you can retrieve data just calling the following methods:

    Application_Model_AclResource::select,
    Application_Model_AclResource::getBySQLCondition,
    Application_Model_AclResource::getByColumn,
    Application_Model_AclResource::getById

    But the implemmentation of this class is a little bit complex to explain in just a few lines. I would need to create another full post just to explain how it works.
    But don’t worry actually you don’t need to extend the Application_Model_Mapper class. You can use your own database mapper like in the Zend Framework Quickstart example. And then, replace the database calls like:

    Application_Model_Role::getById (Acl.php),
    self::getBySQLCondition (AclResource.php)

    for the calls to your own mapper.

    1. Post
      Author

      Hi Nirankar, I have added the Application_Model_Mapper, the User/Role models and the full db-table implementation. There are not missing parts now, hope this clarifies your confusion.

    1. Post
      Author

      I’m afraid Marcus, I didn’t implement Zend_Navigation in my project. But as far as I know, the Zend_Navigation pages are generated depending on the Action -> Controller. The Access control system provided in this tutorial sets access rules for each Action/Controller. So my guess is both features would be a perfect combination.

    1. Post
      Author

      This is not a project Dalibor, this is an implementation from scrach of a Zend Framework ACL (Access control list) for managing the users access and authentication into your Zend Framework project.
      Login a user is as easy as calling the following function:

      Application_Model_Auth::authenticate(array(‘login’=>user_name, ‘password’=>user_password));

      You just need a form to get the user_name and user_password.

  3. Hey Ander,

    Thanks for taking the time to put this post up, Zend ACL can be a complex task for new comers to the framework and this has really helped. I made some modifications to enable it to work in a modular structure and it works great!

  4. I am having an error with the code. It is saying that Fatal error: Uncaught exception ‘Exception’ with message ‘Error -> MapperGetById (The ID is empty)’. It is connected to the database because it created the to controllers in the ACL table. Not sure why it is not grabbing the Guest user Id. Any help would be greatly appreciated.

    1. Post
      Author

      That’s because you are calling from somewhere the method getById($id) with the variable $id empty. You should check why that variable is empty. I cannot say much more having that little information.

  5. Hi Ander,

    I’m totally new to php and Zend, but not to programming, so i’m using this code to see how it works. I’m having the same problem as Scott.
    The cause seemed to be as simple as not having a quest entry in the users table. This way no role_id is returned because the $auth isn’t present .

    So I changed the code in the Acl.php to:

    //If user doesn’t exist it will get the Guest account from “users” table Id=1
    //$authModel->authenticate(array(‘login’=>’Guest’,’password’=>’shocks’));
    if (!$authModel->authenticate(array(‘login’=>’Guest’,’password’=>’shocks’))) {
    //create user Guest…
    $user = new User();
    $user->createUser(array(‘login’=>’Guest’,’role_id’=>’1′,’password’=>’shocks’));
    }

    In the database I now have an entry: id=1, role_id=1, login=Guest, password=64fcd6bbb4f8b5e10b61f3fba96773f9, salt=gPkpvb7h6QiDyphk

    But the error “Fatal error: Uncaught exception ‘Exception’ with message ‘Error -> MapperGetById (The ID is empty)” persists. The authentication still doesn’t work, at least the
    $role_id = $auth->getIdentity()->role_id; is still empty.
    Can anyone tell me what I’m missing?

    1. Hi Ander,

      I’ve found the “error”…
      The createUser() function creates a password based on:
      $data[‘password’]=md5($data[‘login’].$data[‘id’].$data[‘salt’]);
      And doesn’t use the password “shocks”! So my Guest entry in the database has a different MD5 hashed password as I assumed.

      Thanks for the great example. I’m learning a lot from it…

    2. Post
      Author

      Sorry for the delay Bazzline, I’m just back from my holidays. To be honest I’m glad to see that finally someone has been brave enough to go deeper in my implementation. And yes, you are completely right. I forgot telling that I was using a user called “Guest” for the anonymous visitors.
      I have updated the function createUser() so now you can create the user “Guest” following this two steps:

      Add this roles to your “roles” table:

      INSERT INTO roles (id, role) VALUES (1, 'Anonymous');
      INSERT INTO roles (id, role) VALUES (2, 'Registered');
      INSERT INTO roles (id, role) VALUES (3, 'Admin');
      

      Create the “Guest” user (remember to update the function first):

      Application_Model_User::createUser( array('role_id' => 1, 'login' => 'Guest', 'password' => 'shocks'));
      
    1. Post
      Author
    1. Post
      Author
  6. Sir it gives an error Class ‘MyProject_Controller_Plugin_Acl’ not found in E:\wamp\www\myZend\library\Zend\Application\Resource\Frontcontroller.php on line 117 .

    in my application.ini i add this line resources.frontController.plugins.acl = “MyProject_Controller_Plugin_Acl” . Please help or give me zip it will be more helpful.

    1. Post
      Author

      Have you registered the namespace in the Application/Bootstrap.php file ?

      /* Application/Bootstrap.php */

      protected function _initAutoload() {
      // Add autoloader empty namespace
      $autoLoader = Zend_Loader_Autoloader::getInstance ();
      $autoLoader->registerNamespace('MyProject_');
      // Return it so that it can be stored by the bootstrap
      return $autoLoader;
      }

  7. Estimated thank you very much for the code, the post is old but I am assailed by doubt,
    which is the method to ask what if the user has permission to appeal or not? I hope you’re very well thank you very much in advance

    greetings from chile

    1. Post
      Author

      Hi Grisuno,
      This function checks if the current role of the visitor is allowed to access to the selected page:

              if(!$acl->isAllowed($role,$controller,$action)){
                  $request->setControllerName('error');
                  $request->setActionName('access-denied');
                  return;
              }

      You can find this function in the file: “/library/MyProject/Controller/Plugin/Acl.php
      If the current user is allowed to see this page will depend on the content of the table acl_to_roles.

  8. Hi Ander,
    Thanks fro this tutorial.
    I have been checking your implementation of acl.
    Its well implemented and straight to the point but I have only one issue.
    How do I check if the current user has permission to access a particular action from the controller?
    Thanks.

    1. Post
      Author

      Hi Lawrence, you can implement a method for that using the following query:

      SELECT COUNT(*) FROM acl a JOIN acl_to_roles b ON a.id=b.acl_id WHERE a.controller='$controller' AND a.action='$action' AND b.role_id='$role_id'

      And then replace the following variables:

      • $controller: The controller you want to check
      • $action: The action you want to check
      • $role_id: The Role ID of the user

      If you don’t get results it means that the user is not allowed to access that resource

      1. Post
        Author
  9. I got below error

    Fatal error: Class ‘MyProject_Controller_Plugin_Acl’ not found in /var/www/html/Website/library/Zend/Application/Resource/Frontcontroller.php on line 117

  10. error in MapperGetBySQLCondition line number 111 because generate wrong query “SELECT `roles`.* FROM `roles` WHERE (controller=’index’) AND (action=’index’) ORDER BY `id` DESC” and here Application_Model_AclResource set model on here; please suggest how to execute
    ” Select * from roles inner join acl_to_roles on acl_to_roles.role_id=roles.id inner join acl on acl.id= acl_to_roles.acl_id WHERE (acl.controller=’index’) AND (acl.action=’index’) ORDER BY acl.id DESC”

    because when set new model then show error I did code

    ” $this->modelUsers=new Application_Model_DbTable_Users();
    $userList=$this->modelUsers->getList();”

  11. Hi Ander,

    Very nice tutorial 🙂
    I would like to mentioned a single problem here.
    As per the code you are allowing access to all the resources to admin without creating the resources (you’re creating the resources in else{} part).
    $acl->allow($role);

    But without creating all the existing resources it is showing error for the admin that resource ‘XXX’ not found. Should it work for admin without creating the resources?

    1. Post
      Author

      As far as I remember it should work that way. I just had a look at the Zend Framework 1.12 documentation. Go to that link and look for the following lines:

      // Administrator inherits nothing, but is allowed all privileges
      $acl->allow('administrator');
      

      As you see in the documentation it has been implemented in the same way. Maybe this is a problem with the lines 25-28 on file: “/library/MyProject/Controller/Plugin/Acl.php”:

              //Check if the requested resource exists in database. If not it will add it
              if( !$aclResource->resourceExists($controller, $action)){
                  $aclResource->createResource($controller,$action);
              }
      

      These lines are creating the current resource (controller, action) only if it doesn’t exist already. So for example if you create a controller named “Test” and an action called “copy”, the first time you run it in your browser (ex: http://yourApplication/test/copy) these lines will create the resource on your database. I think you should first test if this part is working properly.

        1. Post
          Author

          I guess debugging is your way to go now Rupali, I added some improvements in the past but I couldn’t test it, matter of time…
          Could you let me know if you find out what was the issue?
          Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *