Subscribe to RSS Subscribe to Comments

Matt Stone, that’s me

Using the Zend Framework ACL Library in Codeigniter

To kick my blog off good and proper I thought a tutorial on implementing the Zend Framework ACL library into a Code Igniter application would be a nice start.

In this post I will explain how to build simple access control functionality for your Code Igniter application using the Zend Framework ACL library. To follow this tutorial you will need a working installation of Code Igniter and a database. You will also need to download the Zend Framework.

Code Igniter and Access Control Lists

A lot of Code Igniter users have complained that the framework doesn’t come bundled with an access control component unlike other heavyweight frameworks such as Symfony and CakePHP. Personally I don’t see this as a major issue as the developers behind Code Igniter have deliberately kept it lean with unnecessary libraries being left out in favour of a solid and very usable core and not every web app needs access control.

In my current job access control was an essential requirement and, being the sole person responsible for choosing code igniter over other frameworks, it’s down to me to implement some form of access control into our app.

I started by looking at rolling my own but decided that instead of reinventing the wheel it would be far better to go with a tried and trusted solution hence my reason for opting for the Zend Framework ACL implementation.

Zend ACL

The Zend ACL implementation provides a flexible approach to Access Control Lists with a simple querying interface supplying either true or false. It’s up to the developer to decide how to implement their permissions but I’ll give an example based on the application (a CMS) I’m currently developing.

Getting Started

To start with I’ll link to the blog post that got me started… A very good primer for getting your ACL up and running using the Zend Framework and a database can be found at Jani’s blog it helped me no end and formed the basis for this write up. Now on to the nitty gritty…

Download the Zend framework and unzip it to your code igniter install in /system/libraries. You can leave the whole framework there if you want but if it’s just the ACL stuff you need you can remove everything except Acl.php, Exception.php and the Acl folder.

After that create a file called Acl.php in /system/application/libraries, open it up in your favourite editor and pop this code in it…

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
require_once BASEPATH .'libraries/Zend/Acl.php';
class Acl extends Zend_Acl {
 
	function __construct() {
		$CI = &get_instance();
		$this->acl = new Zend_Acl();
 
		$CI->db->order_by('ParentId', 'ASC'); //Get the roles
		$query = $CI->db->get('user_roles');
		$roles = $query->result();
 
		$CI->db->order_by('parentId', 'ASC'); //Get the resources
		$query = $CI->db->get('user_resources');
		$resources = $query->result();
 
		$query = $CI->db->get('user_permissions'); //Get the permissions
		$permissions = $query->result();
 
		foreach ($roles as $roles) { //Add the roles to the ACL
			$role = new Zend_Acl_Role($roles->id);
			$roles->parentId != null ?
				$this->acl->addRole($role,$roles->parentId): 
				$this->acl->addRole($role);
		}
 
		foreach($resources as $resources) { //Add the resources to the ACL
			$resource = new Zend_Acl_Resource($resources->id);
			$resources->parentId != null ?
				$this->acl->add($resource, $resources->parentId):
				$this->acl->add($resource);
		}
 
		foreach($permissions as $perms) { //Add the permissions to the ACL
			$perms->read == '1' ? 
				$this->acl->allow($perms->role, $perms->resource, 'read') : 
				$this->acl->deny($perms->role, $perms->resource, 'read');
			$perms->write == '1' ? 
				$this->acl->allow($perms->role, $perms->resource, 'write') : 
				$this->acl->deny($perms->role, $perms->resource, 'write');
			$perms->modify == '1' ? 
				$this->acl->allow($perms->role, $perms->resource, 'modify') : 
				$this->acl->deny($perms->role, $perms->resource, 'modify');
			$perms->publish == '1' ? 
				$this->acl->allow($perms->role, $perms->resource, 'publish') : 
				$this->acl->deny($perms->role, $perms->resource, 'publish');
			$perms->delete == '1' ? 
				$this->acl->allow($perms->role, $perms->resource, 'delete') : 
				$this->acl->deny($perms->role, $perms->resource, 'delete');
		}
		$this->acl->allow('3'); //Change this to whatever id your adminstrators group is
	}
	/*
	 * Methods to query the ACL.
	 */
 
	function can_read($role, $resource) {
		return $this->acl->isAllowed($role, $resource, 'read')? TRUE : FALSE;
	}
	function can_write($role, $resource) {
		return $this->acl->isAllowed($role, $resource, 'write')? TRUE : FALSE;
	}
	function can_modify($role, $resource) {
		return $this->acl->isAllowed($role, $resource, 'modify')? TRUE : FALSE;
	}
	function can_delete($role, $resource) {
		return $this->acl->isAllowed($role, $resource, 'delete')? TRUE : FALSE;
	}
        function can_publish($role, $resource) {
		return $this->acl->isAllowed($role, $resource, 'publish')? TRUE : FALSE;
	}
}

IMPORTANT! The final line in the constructor allows all permissions for the role with an ID of 3. This is purely for demonstration purposes. I’m assuming you’ll create an admin role in your database before deploying your app so make sure this ID corresponds with your admin role.

The Database Tables

Next we need to create the database tables we’ll be using for the Acl. For that we only need 3 tables, user_permissions, user_resources and user_roles. The sql for those tables is as follows…

CREATE TABLE `user_resources` (
       `id` INT NOT NULL AUTO_INCREMENT
     , `name` VARCHAR(255)
     , `description` VARCHAR(255)
     , `parentId` INT DEFAULT NULL
     , PRIMARY KEY (`id`)
);
 
CREATE TABLE `user_roles` (
       `id` INT NOT NULL AUTO_INCREMENT
     , `name` VARCHAR(255) NOT NULL
     , `description` VARCHAR(255)
     , `parentId` INT DEFAULT NULL
     , PRIMARY KEY (`id`)
);
 
CREATE TABLE `user_permissions` (
       `id` INT NOT NULL AUTO_INCREMENT
     , `role` INT
     , `resource` INT
     , `read` BOOLEAN DEFAULT FALSE
     , `write` BOOLEAN DEFAULT FALSE
     , `modify` BOOLEAN DEFAULT FALSE
     , `delete` BOOLEAN DEFAULT FALSE
     , `publish` BOOLEAN DEFAULT FALSE
     , `description` VARCHAR(255)
     , PRIMARY KEY (`id`)
);

The user_resources table holds the data for the resources we want to protect through the ACL. Typically this will be admin controllers for any area of your site and perhaps restricted areas of your site such as user profiles or forums. Both the description and parentId fields are optional, the description is there purely for an admin backend to the ACL, the parentId allows you to inherit permissions from a resource, for example you could have separate CMS and media library controllers and you would like the media library to inherit permissions from the CMS so for the media library you would insert the id of the CMS resource into the parent field for the media library.

The user_roles table works in the same way as the resources table. Roles can inherit from other roles allowing you to build permissions with a minimal number of groups. For example you could have a CMS users role which has all permissions except for publish and delete, you could then create a CMS editors role which inherits from the CMS users role but adds extra permissions for deleting and publishing content.

The final table, user_permisssions, is what builds the permissions in the acl. If needs be you can add as many boolean columns for whatever permissions your application requires, just remember to add a method in your ACL library for querying that permission! The description field is, like the other tables, optional but for the purposes of an admin backend you’ll probably want to use it!

We can now start by populating our tables…

INSERT INTO `user_resources` (`id`, `name`, `description`, `parentId`) VALUES
(1, 'test', 'Acl Test Controller', NULLL)
 
INSERT INTO `user_roles` (`id`, `name`, `description`, `parentId`) VALUES
(1, 'test', 'Acl Test Role', NULLL)
 
INSERT INTO `user_permissions` (`id` ,`role` ,`resource` ,`read` ,`write` ,`modify` ,`delete` ,`publish`)
VALUES (NULL , '1', '1', '1', '1', '0', '0', '0');

Testing the ACL

We’re now ready to test out the ACL. Create a controller test.php in /system/application/controllers with the following code.

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 class Test extends Controller {
 function  Test() {
  parent::Controller()
  $this->load->library('Acl');
  define('ROLE', '1');
  define('RESOURCE', '1');
 }
 function index() {
  if (!$this->acl->can_read(ROLE, RESOURCE)) {
   die("You do not have permissions to read this resource");
  }
  echo "You have permission to read this resource!";
 }
 function write() {
  if (!$this->acl->can_write(ROLE, RESOURCE)) {
   die("You do not have permissions to write to this resource");
  }
  echo "You have permission to write to this resource!";
 }
 function modify() {
  if (!$this->acl->can_modify(ROLE, RESOURCE)) {
   die("You do not have permissions to modify this resource");
  }
  echo "You have permission to modify this resource!";
 }
 function delete() {
  if (!$this->acl->can_delete(ROLE, RESOURCE)) {
   die("You do not have permissions to delete this resource");
  }
  echo "You have permission to delete this resource!";
 }
 function publish() {
  if (!$this->acl->can_publish(ROLE, RESOURCE)) {
   die("You do not have permissions to publish this resource");
  }
  echo "You have permission to publish this resource!";
 }
}

For the purposes of this article I’m going to assume your working on your local machine, so browse to http://localhost/test. You should see the message “You have permission to read this resource”. Next try http://localhost/test/delete. You should see the message “You do not have permission to delete this resource”. Now go and try it on all the methods in your test controller.

Congratulations! You’ve successfully got Code Igniter and Zend ACL to play nicely together.

Note: If you get error messages about required files not being found in the include path you may have to modify the include paths in your Zend framework files. Open them up and, wherever you find an include or require, modify it to…

BASEPATH.'/libraries/REST OF THE FILE PATH';

Summary

While the above article explains how to get access control working in code igniter it doesn’t provide a model for obtaining roles and resources, the above example having the role and resource id hardcoded into the controller. This is an exercise I feel that is best left to the developer. Personally I use the excellent modular extensions for organising my application. I then have a separate module model which has a method get_resource_id which allows me to define the resource.

The users id is held in their session and I have a users model which, again, has to method to define their role from their session.

It’s also worth noting that if you try and check the ACL without either the role or resource being valid you’ll get an exception error so whichever way you decide to implement this it’s best to write some basic checks before querying the ACL.

The End

Ok, I hope some people have found this useful, let the comments commence!

Thank you for listening.

Comments

  1. _Undefined
    December 1st, 2008 | 6:48 pm

    Informative and useful, thanks.

  2. December 15th, 2008 | 6:11 pm

    Thank you very much.

  3. December 15th, 2008 | 6:42 pm

    A PHP Error was encountered

    Severity: Warning

    Message: require_once(Zend/Acl/Resource/Interface.php) [function.require-once]: failed to open stream: No such file or directory

    Filename: Zend/Acl.php

    Line Number: 26

    I try to use function set_include_path point to BASEPATH .’libraries/ but still have this error :(
    May be need something link Zend_Loader()

  4. December 16th, 2008 | 9:28 am

    You shouldn’t need Zend_Loader(), i’ve had no need to use it.

    Have you tried setting the include path in an .htaccess file?

    Create a text file called .htaccess in your code igniter root (where you find the index.php and license.txt files) and add the following:

    php_value include_path “/PATH/TO/CODE IGNITER/LIBRARIES”

    For example, I use xampp and windows XP for my development machine so I would add:

    php_value_include_path “C:\xampp\htdocs\system\libraries\”

    Try that and let me know how you get on.

    Cheers
    Matt

  5. December 21st, 2008 | 1:04 pm

    maybe 9px font size in the comments is a little small

  6. December 22nd, 2008 | 5:59 pm

    @matt: No need to set the include path through the .htaccess.

    <?php
    set_include_path(‘/path/to/code/igniter/libraries/’ . PATH_SEPARATOR . get_include_path());

    And true, the Zend_Loader is not needed. Any autoload function that conforms to the underscore namespace separator plus each namespace as a folder will work. In fact you could easily use the function below:

    <?php
    function _my_autoload($classname)
    {
    $classfile = LIBRARY_BASE_DIRECTORY . str_replace(‘_’, ‘/’, $classname) . ‘.php’;
    if( file_exists($classfile) ) include $classfile;
    }

  7. December 23rd, 2008 | 9:58 am

    class Acl extends Zend_Acl {
    ….
    $this->acl = new Zend_Acl();
    }

    WTF ?!?

  8. Willian
    March 30th, 2009 | 6:49 pm

    Muito obrigado, por compartilhar seu conhecimento conosco!!!
    Tudo de bom!!!
    Valew

  9. March 31st, 2009 | 9:11 pm

    You should try to not get so excited while you code. You got $perms everywhere.

  10. April 7th, 2009 | 8:11 am

    I install Acl but occur error :

    A PHP Error was encountered
    Severity: Notice

    Message: Undefined property: Test::$db

    Filename: libraries/Acl.php

    Line Number: 9

    Fatal error: Call to a member function order_by() on a non-object in E:\Projects\work\CodeIgniter171\system\application\libraries\Acl.php on line 9

    ———————————————————

    This is error line:

    9 – $CI->db->order_by(‘ParentId’, ‘ASC’); //Get the roles

    Please help me

    Many thanks

  11. April 7th, 2009 | 8:42 am

    I fixed the db bug, because i didnt autoload database in config file yet :D

  12. neo
    April 14th, 2009 | 11:32 am

    Parse error: syntax error, unexpected T_VARIABLE in C:\wamp\www\test_ci_connect\trunk\system\application\controllers\test.php on line 5

    getting this error!! plz help

  13. April 28th, 2009 | 11:04 am

    [...] Using the Zend Framework ACL … (tags: zend codeigniter webdev acl zend_acl zendframework) [...]

  14. April 30th, 2009 | 1:02 pm

    Excellent article, just what I was looking for.

  15. naeem
    May 28th, 2009 | 4:47 am

    i dont know what you have write in your articale i just know that im new user to codeigniter and when every im trying to get some data from database im having an error and i dont know how to solve it. it almost take 3 days but unable to solve it… please help me and please reply to my email.
    the error is given bilow.
    Fatal error: Call to a member function get() on a non-object in C:\wamp\www\codeigniter\system\application\controllers\blog.php on line 18

    thanks in advance

  16. Kalebarkab
    June 9th, 2009 | 8:29 pm

    I want to find good pop music. Help me please.

  17. June 26th, 2009 | 2:40 pm

    Thanks, this helped a lot. I was able to successfully get my ACL working.

  18. July 16th, 2009 | 11:15 am

    I am very interested in this topic.

  19. August 10th, 2009 | 11:00 pm

    “Creating a custom Membership”
    I am completely new to ASP.
    its correct
    Regards – thanks in advance

  20. Anandhi
    August 31st, 2009 | 1:19 pm

    Hi am newbie to zend framework.. i just included the coding.. i am not getting any error but just getting the blank page.. can any1 tell me where i have gone wrong.. can give the complete source code??

  21. OSX BOY
    September 19th, 2009 | 10:36 pm

    Fatal error: Uncaught exception ‘Zend_Acl_Role_Registry_Exception’ with message ‘Parent Role id ’0′ does not exist’ in /Library/WebServer/chapeldesign/marques/system/libraries/Zend/Acl/Role/Registry.php:94 Stack trace: #0 /Library/WebServer/chapeldesign/marques/system/libraries/Zend/Acl.php(142): Zend_Acl_Role_Registry->add(Object(Zend_Acl_Role), ’0′) #1 /Library/WebServer/chapeldesign/marques/application/modules/admin/libraries/Acl.php(39): Zend_Acl->addRole(Object(Zend_Acl_Role), ’0′) #2 /Library/WebServer/chapeldesign/marques/application/libraries/Loader.php(635): Acl->__construct() #3 /Library/WebServer/chapeldesign/marques/application/libraries/Loader.php(540): CI_Loader->_ci_init_class(‘Acl’, ”, NULL, NULL, false) #4 /Library/WebServer/chapeldesign/marques/application/libraries/Loader.php(258): CI_Loader->_ci_load_class(‘Acl’, NULL, NULL, false) #5 /Library/WebServer/chapeldesign/marques/application/libraries/Loader.php(764) : eval()’d code(999): CI_Loader->library(‘Acl’) #6 /Library/WebServer/chapeldesign/marques/ in /Library/WebServer/chapeldesign/marques/system/libraries/Zend/Acl/Role/Registry.php on line 94

    Seems odd?

  22. OSX BOY
    September 19th, 2009 | 10:51 pm

    Ah found it

    parentId not null when imported into the DB

  23. October 21st, 2009 | 11:01 am

    Nice Blogs
    -Thanks-

  24. November 16th, 2009 | 8:34 pm

    really nice and useful article!

    I read a little the comments and i feel to remember the following (i remember ‘cuse i find this is a very exellent work dear Matt!)

    if you find any troubles about classes not found: don’t forget to set the right PATH in every require statament (i guess someone miss require statament, which is not only before classes declaration!!!!)

    don’t forget to load CI databases!!! for a fast test add it as autoloaded

    PS: im sorry about my bad english.

    Thanks again!!!

  25. bluego78
    March 2nd, 2010 | 9:22 am

    nothing… i got a white page! when i do $this->load->library(‘Acl’); all seems to work but response with a blank page!

    My constructor is:

    function Site(){
    parent::Controller();
    $this->load->library(‘Acl’);
    define(‘ROLE’, ’1′);
    define(‘RESOURCE’, ’1′);
    if (!$this->acl->can_read(ROLE, RESOURCE);) {
    die(“You do not have permissions to read this resource”);
    }
    echo “You have permission to read this resource!”;
    }

    Please help me!
    :-(

  26. March 8th, 2010 | 7:49 pm

    Increddible. http://www.matttsone.me is the shit.

  27. May 4th, 2010 | 4:15 pm

    Niksdype say: It is remarkable, very amusing idea

    _____________
    lavetra
    buyonline uk
    7

  28. August 30th, 2010 | 8:02 pm

    @bluego78 I had the same problem, I fixed it by putting this line of code before the require_once for Zend/Acl.php

    Here it is:
    ini_set(‘include_path’,ini_get(‘include_path’).PATH_SEPARATOR.BASEPATH.’libraries/’);

    Not sure if it will fix it for you, but worked for me.

Leave a reply

Based on FluidityTheme Redesigned by Kaushal Sheth Sponsored by Send Flowers