Basic ACL Tutorial For Zend Framework 2

7 May 2015| Post by Graham

AuthorGraham

Being unable to find a good tutorial of how ACL works in ZF2, I decided to write one myself.

Step 1 – Create The Module

The files you will need to create are as follows:

{{app-dir}}/modules/Acl/Module.php

[cc lang=”php”] getApplication();
$application->getEventManager()->attachAggregate($application->getServiceManager()->get(‘AclListener’));
return $this;
}

public function getConfig() {
return include __DIR__ . ‘/config/module.config.php’;
}

public function getAutoloaderConfig() {
return array(
‘ZendLoaderStandardAutoloader’ => array(
‘namespaces’ => array(
__NAMESPACE__ => __DIR__ . ‘/src/’ . __NAMESPACE__,
),
),
);
}
}
[/cc]

This file is a pretty standard ZF2 Module.php file, the magic happens when we created an “onBootstrap” method, this attaches the ACL event listener to the application so it can listen to individual requests.

{{app-dir}}/modules/Acl/config/module.config.php

[cc lang=”php”] array(
‘factories’ => array(
‘AclListener’ => function($sm) {
return new AclListener($sm->get(‘AclService’), $sm->get(‘ZendAuthenticationAuthenticationService’));
},
‘AclService’ => function($sm) {
$config = $sm->get(‘config’);
$service = new AclService(new Acl);
if (!empty($config[‘acl’])) {
$service->setup($config[‘acl’]);
}
return $service;
}
)
),
);
[/cc]

Here we are registering two service factories, I’m aware that using inline functions (closures) isn’t necessarily the best way to register these, but for demonstration purposes I haven’t created ServiceFactory classes to save time.

Here is a good resource for how / why you should use service factory classes to initiate your ZF2 services http://www.masterzendframework.com/tutorial/zf2-factory-interface-closure-migration

{{app-dir}}/modules/Acl/src/Acl/Listener/Acl.php

[cc lang=”php”] aclService = $aclService;
$this->authService = $authService;
}

public function attach(EventManagerInterface $eventManager) {
$this->listeners[] = $eventManager->attach(MvcEvent::EVENT_ROUTE, array($this, ‘checkAcl’), -1000);
}

public function checkAcl(MvcEvent $e) {
$role = !$this->getAuthService()->hasIdentity() ? AclService::USER_GUEST : $this->getAuthService()->getIdentity()->getRole();
if (!$this->getAclService()->isAllowed($role, $e->getRouteMatch()->getMatchedRouteName())) {
$e->getRouteMatch()->setParam(‘controller’, ‘UsersControllerIndex’)
->setParam(‘action’, ‘not-allowed’);
}
return $this;
}

private function getAclService() {
return $this->aclService;
}

private function getAuthService() {
return $this->authService;
}

}
[/cc]

This file extends Zend’s “ZendEventManagerAbstractListenerAggregate” class, allowing us to attach multiple events to the $eventManager instance passed through to the “attach” method.

In this case we want to listen to the MvcEvent:EVENT_ROUTE (route) event which is fired after a route has been matched.

The “checkAcl” method first checks to see if the user is logged in, if not then the role is set to ‘guest’. If the user is logged in then it will return the “role” from the User object, you may need to change the first line depending on how your user object is returned from the “getIdentity” method of Zend’s authentication service. For example it may be returned as an array if you are not using Doctrine2 ORM.

It then does a basic check to see if the user is allowed to see the current resource, if not it forwards them to the “not-allowed” action of the “user” controller. The beauty of this is that the URL of the page doesn’t change for the user.

{{app-dir}}/modules/Acl/src/Acl/Service/Acl.php

[cc lang=”php”] setAcl($acl);
}

public function setAcl(Acl $acl) {
$this->acl = $acl;
return $this;
}

public function getAcl() {
return $this->acl;
}

public function setup(array $config) {
$acl = $this->getAcl();
foreach ($config as $role => $resources) {
if (!$acl->hasRole($role)) {
$acl->addRole(new Role($role));
}
foreach ($resources as $resource) {
if (!$acl->hasResource($resource)) {
$acl->addResource(new Resource($resource));
}
$acl->deny($role, $resource);
}
}
}

public function isAllowed($role, $resource) {
return (!($this->getAcl()->hasResource($resource) && $this->getAcl()->hasRole($role))) || $this->getAcl()->isAllowed($role, $resource);
}

}
[/cc]

This ACL service class acts as a wrapper for Zend’s built-in “ZendPermissionsAclAcl” class. If you look back at {{app-dir}}/modules/Acl/config/module.config.php then you will notice that when this class (“AclService”) is initialised it checks the merged configuration of each module for a key called “acl”, it then loops through each of the roles and resources and adds them to Zend’s native ACL instance.

The “isAllowed” method is created for convenience, firstly it checks to see if the ACL instance has the resource and the role; If this evaluates to true then it checks to see if the ACL instance will allow the resource to be accessed by that particular user role.

Step 2 – Include the Module

The next step is simply to include the module you have added above into the Application. This can be done by adding it into the list of modules found at {{app-dir}}/config/application.config.php.

Step 3 – Using the Module

In one of your modules “module.config.php” you need to add code similar to the following:

[cc lang=”php”] ‘acl’ => array(
‘guest’ => array(
‘account/posts’,
)
)
[/cc]

The way this is structured is role => array of resources. This list is a list of resources you want to deny access to, in this example I am denying the ‘guest’ user access to the ‘account/posts’ route.

Conclusion

Thank you for reading, I hope it has helped you a little bit with how Zend’s native ACL system works. I know the code isn’t polished but hopefully it will be a good starting point for someone looking to implement ACL into their application.

Please leave any comments with improvements you would like to suggest!

Scroll