Zend Framework 2 ACL setup in 5 minutes – tutorial

March 13, 2013

In this tutorial you will learn how to setup zend framework 2 acl and check if user has access for current route.

If you don`t have testing project , download one from here.

Add file module.acl.roles in application/config/

Tip: In fact role resources may be zend framework 2 routes.

return array(
	'guest'=> array(
		'home',
		'login',
		'register'
	),
	'admin'=> array(
		'admin',
		'add-user',
		'delete-user'
	),
);

Code in application/module.php

public function onBootstrap(MvcEvent $e) {
	$this -> initAcl($e);
	$e -> getApplication() -> getEventManager() -> attach('route', array($this, 'checkAcl'));
}

public function initAcl(MvcEvent $e) {

	$acl = new \Zend\Permissions\Acl\Acl();
	$roles = include __DIR__ . '/config/module.acl.roles.php';
	$allResources = array();
	foreach ($roles as $role => $resources) {

		$role = new \Zend\Permissions\Acl\Role\GenericRole($role);
		$acl -> addRole($role);

		$allResources = array_merge($resources, $allResources);

		//adding resources
		foreach ($resources as $resource) {
             // Edit 4
             if(!$acl ->hasResource($resource))
			    $acl -> addResource(new \Zend\Permissions\Acl\Resource\GenericResource($resource));
		}
		//adding restrictions
		foreach ($allResources as $resource) {
			$acl -> allow($role, $resource);
		}
	}
	//testing
	//var_dump($acl->isAllowed('admin','home'));
	//true

	//setting to view
	$e -> getViewModel() -> acl = $acl;

}

public function checkAcl(MvcEvent $e) {
	$route = $e -> getRouteMatch() -> getMatchedRouteName();
	//you set your role
	$userRole = 'guest';

	if (!$e -> getViewModel() -> acl -> isAllowed($userRole, $route)) {
		$response = $e -> getResponse();
		//location to page or what ever
		$response -> getHeaders() -> addHeaderLine('Location', $e -> getRequest() -> getBaseUrl() . '/404');
		$response -> setStatusCode(404);

	}
}

Edit 1

I am assuming that all the routes that are accessed in the app are already added to the acl config file.

If not replace :

if (!$e -> getViewModel() -> acl -> isAllowed($userRole, $route)) {

to

if ($e -> getViewModel() -> acl ->hasResource($route) && !$e -> getViewModel() -> acl -> isAllowed($userRole, $route)) {

 

Edit 2

If access inheritance is not needed just replace this :

//$allResources = array_merge($resources, $allResources);
foreach ($allResources as $resource) {
// with
foreach ($resources as $resource) {

 

Edit 3

But what about acl from the database someone will say ?

In the zend framework 2 startup tutorial they say – “Don`t go heavy in onBootstrap”

With that said , I think that an array file will work for most of the cases.

Add this method where acl check is

    public function getDbRoles(MvcEvent $e){
        // I take it that your adapter is already configured
        $dbAdapter = $e->getApplication()->getServiceManager()->get('Zend\Db\Adapter\Adapter');
        $results = $dbAdapter->query('SELECT * FROM acl');
        // making the roles array
        $roles = array();
        foreach($results as $result){
            $roles[$result['user_role']][] = $result['resource'];
        }
        return $roles;
    }

Then replace in initAcl

// this
$roles = include __DIR__ . '/config/module.acl.roles.php';
// with
$roles = $this->getDbRoles($e);

Simple as that.
Suggestions or problems ? Write a comment.

tags: , , ,
posted in Guides, how to ?, php, Zend Framework 2 by Ivan Gospodinow

Follow comments via the RSS Feed | Leave a comment | Trackback URL

52 Comments to "Zend Framework 2 ACL setup in 5 minutes – tutorial"

  1. user1 wrote:

    Hi, great tutorial!

    Everything works fine, but when i go to route, that guest role does not have, i get an exception:

    Fatal error: Uncaught exception ‘Zend\Permissions\Acl\Exception\InvalidArgumentException’ with message ‘Resource ‘success’ not found’ in

    Acl\Acl->isAllowed(‘guest’, ‘success’) #2 [internal function]: Application\Module->checkAcl(Object(Zend\Mvc\MvcEvent))

    Something is not good about this line:

    if (!$e -> getViewModel() -> acl -> isAllowed($userRole, $route)) {

  2. user1 wrote:

    Oh, i just found solution :)

    Replace line

    if (!$e -> getViewModel() -> acl -> isAllowed($userRole, $route)) {

    to

    if (!$e -> getViewModel() -> acl ->hasResource($route) || !$e -> getViewModel() -> acl -> isAllowed($userRole, $route)) {

  3. Ivan Gospodinow wrote:

    Hello and thank you user1 ,

    yes you are right.The code from tutorial assumes that you have all the routes inserted in the acl (which is a good thing).That means you are controlling all the routes with roles.

  4. Andrey wrote:

    Hello.
    Thank you for great tutorial, but what about roles with different permissions?
    Looks like in your code each role is allowed also for resource listed in all previous roles in array.

  5. Ivan Gospodinow wrote:

    Hello Andrey ,

    thank you for your question.

    I created an Edit 2 for your case.
    By replacing those 2 lines of code , the roles will not inherit resources from one another.

  6. Dominic Watson wrote:

    Ctrl + F

    “-> ”
    Replace
    “->”

    ” ->”
    Replace
    “->”

    NO! Naughty! Bad Ivan! :P

    Nice and easy tutorial though :) makes me feel silly for thinking anything is difficult. I guess it’s getting used to the EventManager

  7. sandeep wrote:

    Hello,

    It,s working fine.
    but the function form controller is accessable by ajax , this only prents the view rendering.

  8. Tahmina Khatoon wrote:

    Thanks for nice tutorial, It is working fine everywhere except pagination with search. Would you please help me to set permission for following route.

    ‘people’ => array(
    ‘type’ => ‘segment’,
    ‘options’ => array(
    ‘route’ => ‘/people[/:action][/:id][/page/:page][/order_by/:order_by][/:order][/search_by/:search_by]‘,
    ‘constraints’ => array(
    ‘action’ => ‘(?!\bpage\b)(?!\border_by\b)(?!\bsearch_by\b)[a-zA-Z][a-zA-Z0-9_-]*’,
    ‘id’ => ‘[0-9]+’,
    ‘page’ => ‘[0-9]+’,
    ‘order_by’ => ‘[a-zA-Z][a-zA-Z0-9_-]*’,
    ‘order’ => ‘ASC|DESC’,
    ),
    ‘defaults’ => array(
    ‘controller’ => ‘WebApp\Controller\People’,
    ‘action’ => ‘index’,
    ),
    ),
    ),

    I have allowed ‘people’ for guest but it is not working with order_by and search_by parameters.

    Please help me.

    Thanks in advance

  9. Ivan Gospodinow wrote:

    Hello Tahmina ,

    in my example I am using route name as an acl resource.
    In your case you can do the following :

    $routeName = $e->getRouteMatch()->getMatchedRouteName();
    $routeParams = $e->getRouteMatch()-getParams();

    where route params are you root constraints – for example :
    $customResource = $routeName . $routeParams['controller'] . $routeParams['action'];

    Than you check like this :
    $acl -> isAllowed($userRole, $customResource);

    Simple as that.

  10. mhor wrote:

    (Noob question here) Just want to ask, how do you pass the role (line 39)?

    Tried it already but it always gives me a 404. But when I specify the //, it works normally

  11. Ivan Gospodinow wrote:

    Hello mhor ,

    two reasons to give you 404
    1. Resource is not added to the acl
    2. User is not allowed to access the resource.

    To your other question, you can easy access the session at that point and get the user credentials.If session does not exist you can assume that user is guest.

  12. Ivan Gospodinow wrote:

    Hello sandeep,

    it dont matter if the request is ajax.
    You should specify resouce(route) and user to access it in the acl.
    Module.php is executed every time.

  13. Prakash wrote:

    Hi,

    Thanks for this great tutorial.

    What in case, if i have to implement Zend2 ACL using Database define roles and access.

    Can you please help to implement acl using database. i search many links on google but no success on it.

    Thanks…

  14. Ivan Gospodinow wrote:

    Hello Prakash ,

    thank you for your question. I added edit3 to the article to give some idea of how to do it with a database.

  15. S.G. wrote:

    Hi Ivan,
    thanks for your sharing.

    I am confused about the “function checkAcl()” above.
    I had try to set myself file “module.acl.roles.php”, but my ACL seems not work.
    Would you explain the function checkAcl() more detail, and the purpose of line 39, “$userRole = ‘guest’;” ?

    Appriciate

  16. Ivan Gospodinow wrote:

    Hello S.G. ,

    basically checkAcl is called when route is initialised. We need this because acl roles are based on routes/resources (in other word – links).Then what we do is : get the current route name (you can add up action , controller and/or parameters) as a string and ask the acl class if user with role (exp. guest) can access this string (exp. login).

    Simple enought for you?

  17. Kathiravan wrote:

    Hello Ivan,

    i have three types of users admin, guest and user, have a page like login and register. here the three users will access the above pages
    return array(
    ‘guest’=> array(
    ‘add-user’,
    ‘login’
    ),
    ‘admin’=> array(
    ‘user-list’,
    ‘login’
    ),
    ‘user’=> array(
    ‘add-user’,
    ‘login’
    ),
    );

    but it shows the error Uncaught exception ‘Zend\Permissions\Acl\Exception\InvalidArgumentException’ with message ‘Resource id ‘login’ already exists in the ACL’ everything works fine except this plz help.

  18. Ivan Gospodinow wrote:

    Hello Kathiravan ,

    please take a look at Edit 4. Made it for you.

  19. Kathiravan wrote:

    Hi Ivan,

    There is no edit 4 displayed here.

  20. Kathiravan wrote:

    Hello Ivan,

    Sorry by mistake i posted the above comment Thanks its working now.

  21. Kumar wrote:

    Hi Ivan,

    Nice Tutorial.
    As I am a Beginner,
    Can You tell me the use of get Application() and get EventManager()?
    Thank You

  22. Ivan Gospodinow wrote:

    Hello Kumar ,

    getApplication return the Zend Mvc Application object, which is instantiated in index.php. It is basically the whole application.
    getEventManager is the object which can store and trigger events. See EventManager::attach() and EventManager::trigger();

  23. Kumar wrote:

    Thanks Ivan

  24. Kumar wrote:

    Can I know about get ServiceLocator(), viewhelpermanager ?

  25. Ivan Gospodinow wrote:

    Hello Kumar,

    you should not know about getServiceLocator in any view or view helper!

  26. Grisou wrote:

    To handle also allow and deny permissions I change this:

    Configs acl:
    // true to allow, false to deny
    return array(
    ‘guest’=> array(
    ‘home’ => true,
    ‘login’ => true,
    ‘register’ => true
    ),
    ‘admin’=> array(
    ‘admin’ => true,
    ‘add-user’ => true,
    ‘delete-user’ => true,
    ‘login’ => false,
    ‘register’ => false
    ),
    );

    And into Module.php:
    public function initAcl(MvcEvent $e)
    {
    $acl = new Acl();
    $acl->deny();
    $roles = include __DIR__ . ‘/config/module.acl.roles.php’;
    $allResources = array();
    foreach ($roles as $role => $resources) {

    $role = new \Zend\Permissions\Acl\Role\GenericRole($role);
    $acl->addRole($role);

    $allResources = array_merge($allResources, $resources);

    //adding resources
    foreach ($resources as $resourceName => $resourceAllowed ) {
    if(!$acl->hasResource($resourceName)) {
    $acl->addResource(new \Zend\Permissions\Acl\Resource\GenericResource($resourceName));
    }
    }

    //adding restrictions
    foreach ($allResources as $resourceName => $isResourceAllowed ) {

    if ( $isResourceAllowed ) {
    $acl->allow($role, $resourceName);
    } else {
    $acl->deny($role, $resourceName);
    }
    }
    }

    //setting to view
    $e->getViewModel()->acl = $acl;

    }

  27. bartek wrote:

    Hi, can it be done without redirection? I am developing restful application, and there are no restrictions available.
    Regards

  28. Ivan Gospodinow wrote:

    Hello Bartek ,
    you can change the layout with one that dispatches json/xml to tell the page that user do not have permissions to access it.

  29. Pragnesh Karia wrote:

    Hey ,

    Thanks for sharing a wonderful script.

    Can you please explain more in terms of ,

    “SELECT * FROM acl ORDER BY role_privileges DESC”

    what will be ACL table schema?

    what is the use of setstatuscode = 303 ?

    i want to redirect the to 403 access forbidden page (403.phtml)

    $response -> getHeaders() -> addHeaderLine(‘Location’, $e -> getRequest() -> getBaseUrl() . ‘/404′);
    $response -> setStatusCode(303);

    Any help would be appreciated.

  30. Ivan Gospodinow wrote:

    Hello Pragnesh Karia,

    you are right , we do not need role_privileges.I will fix that.
    For your database schema , you need at least user_role and resource. See the config array in the post.
    I will update the code to 404. I agree that its the right way!

    Thank you for you appinion.

  31. Pragnesh Karia wrote:

    Hello Ivan,

    Any more updates on the above , your help is highly appreciated.

  32. Dennis wrote:

    The “redirect on error” logic is missing the following part:
    $response->sendHeaders();
    $e->stopPropagation();

    Otherwise, the restricted controller/action is still called.

  33. Matteo wrote:

    I need to print some links in the view, according to the user’s permissions. How can I do it? I tried using $acl = $this->acl but in the view I get a null. How can I do it? There is another method of verifying user permissions in the view?

  34. kourosh wrote:

    Hello Ivan.

    Thanks for nice article, i have a little problem, the application dont redirect to route after granted permission, it just stop and look at me :|

  35. Ivan Gospodinow wrote:

    Hello Matteo,
    the best way will be to create navigation for the links.
    If you prefer the lazy way, you can add the links as resources in the acl and then check from the view (not a good way to do it) with $this->layout()->acl->isAllowed(‘user_role’, $linkl);

    @BTW,
    you can add an menu item in your current navigation and add sub pages. Then you can render only the sub pages of this item, and the acl will work by default.

  36. Ivan Gospodinow wrote:

    Hello kourosh,

    can you paste some code? The acl tutorial, only redirects if person does not have permission to access the current page. The opposite, why to redirect if he can see the page? Right ?

  37. Matteo wrote:

    I thought about the navigation, but I need to add many links scattered around the page, and it seemed too complex. I think I’ll try the dirty way :-)

    thank you very much

  38. Matteo wrote:

    another question:
    If I wrote this:
    echo $this->layout()->acl->isAllowed($user->role,$this->url(‘user’, array(‘action’=>’index’)));

    I get an error: Resource ‘/user’ not found

    where am I doing wrong?

  39. Ivan Gospodinow wrote:

    Hello Matteo,

    quote from above ‘add the links as resources’.
    So you should add the link as resource and add the user role permission.
    Is it clear to you ?

  40. Matteo wrote:

    sorry, is not so clear.
    We have just add a resource to use it with side menu. Is not enought? I need to add in some other way?

  41. Ivan Gospodinow wrote:

    Hello Matteo,

    in your code you are asking the acl object, if the user with role(some role) have access to url (for example /user) but to this moment you never told the acl object, that resource ‘/user’ exists and that user role has access to it.

    @TODO
    $acl -> addResource(new \Zend\Permissions\Acl\Resource\GenericResource(‘/user’));
    $acl -> allow(‘user_role’ , ‘/user’);

    Is it now more clear?

  42. Matteo wrote:

    I’m sorry, but in module.acl.roles I’ve just added this role

    $admin = array(‘/’,'homes’,'auth’,'success’,'user’);

    return array(‘user’=>$user,’admin’=>$admin,’guest’=>$guest);

    I need to repeat it?

  43. Ivan Gospodinow wrote:

    Hello Matteo,

    you do not have to repeat it.
    If you want user to have access to url ‘/user’, then from your example:
    add to $user = ['/user'];

    Then check the in the view:
    $this->layout()->acl ->hasResource($url) && $this->layout()->acl-> isAllowed(‘user’, $url)

  44. Matteo wrote:

    Dear Ivan,
    thanks for your patience.

    Sorry but, I haven’t shown all the file module.acl.roles.php.
    This file contains:

    $guest = array(‘homes’,'auth’);
    $utente = array(‘homes’,'auth’,'success’,'DownloadFile’,'utente:show’,'utente:upload’);
    $admin = array(‘homes’,'auth’,'success’,'compagnia’,'utente’,'DownloadFile’);
    $superadmin = array(‘homes’,'auth’,'success’,'compagnia’,'utente’,'DownloadFile’);

    return array(‘superadmin’=>$superadmin,’utente’=>$utente,’admin’=>$admin,’guest’=>$guest);

    The menu display pages correctly according to the role.
    When I try to call the link from view with this syntax:

    if($this->layout()->acl->isAllowed($dato["ruolo"],$this->url(‘utente’, array(‘action’=>’index’))))

    I get the error:

    Resource ‘/utente’ not found

    If I duplicate “/utente” with the slash in the permits, it works. How so? Can I avoid duplicate all the permits? If I leave only “/utente” do not work the action

    thank you very much

  45. Ivan Gospodinow wrote:

    Hello Matteo,

    in this case I understand what you need to ask.
    Then your check will be with the route name, not the whole url.

    Just do:
    if($this->layout()->acl->isAllowed($dato["ruolo"], ‘utente’)) {
    echo $this->url(‘utente’); // assuming that index action is by default.
    // or whatever here.
    }

    And dont forget to test if the resource is existing like I posted before:
    $this->layout()->acl ->hasResource(‘utente’)

  46. Matteo wrote:

    thank you a lot! now it work! :-)

  47. Andy wrote:

    Hi,

    This is a excellent, crisp and really good understandable tutorial, thank you for that !!

    I am just on the search how to enhance this to be able to ask for certain privileges in a view without moving the whole part again into a View Helper or similar.

    So, somehow centralize the functionality for checkACL and let this be accessible from other locations as well.

    Any idea for that ?

  48. lodi wrote:

    Hi I get the following error

    Fatal error: Uncaught exception ‘Zend\Permissions\Acl\Exception\InvalidArgumentException’ with message ‘Resource ‘users’ not found’ in /home/lodi/git/reporting/vendor/zendframework/zendframework/library/Zend/Permissions/Acl/Acl.php on line 292

    any pointers ?

    Regards

  49. Ivan Gospodinow wrote:

    Dear lodi,
    check Edit 1 from the post above.

  50. Ivan Gospodinow wrote:

    Hello Andy,

    the way I am showing it in the post is: $e -> getViewModel() -> acl = $acl;
    This means that you can access it in any view.
    The right way to do this is: create ACL factory via the service manager and then use it to check wherever needed.

  51. lodi wrote:

    Hi Ivan thanks for the reply.

    I changed the code as per edit 1 and still get the same error.

    For my clarity the module.acl.roles file do I put it in the module/Application folder or in the top level config folder ?

    Also you mention the acl config file? not sure if I need to create the file or if it is the file that comes with the framework.

    Really trying to get this to work.

    Regards

    L

  52. Ivan Gospodinow wrote:

    Hello lodi,

    can you paste some code?

Leave Your Comment

 
 
Powered by Wordpress and MySQL. Theme by Shlomi Noach, openark.org