Access Control Lists (ACL)

Phalcon\Acl provides an easy and lightweight management of ACLs as well as the permissions attached to them. Access Control Lists (ACL) allow an application to control access to its areas and the underlying objects from requests. You are encouraged to read more about the ACL methodology so as to be familiar with its concepts.

In summary, ACLs have roles and resources. Resources are objects which abide by the permissions defined to them by the ACLs. Roles are objects that request access to resources and can be allowed or denied access by the ACL mechanism.

Creating an ACL

This component is designed to initially work in memory. This provides ease of use and speed in accessing every aspect of the list. The Phalcon\Acl constructor takes as its first parameter an adapter used to retrieve the information related to the control list. An example using the memory adapter is below:

  1. <?php
  2. use Phalcon\Acl\Adapter\Memory as AclList;
  3. $acl = new AclList();

By default Phalcon\Acl allows access to action on resources that have not yet been defined. To increase the security level of the access list we can define a deny level as a default access level.

  1. <?php
  2. use Phalcon\Acl;
  3. // Default action is deny access
  4. $acl->setDefaultAction(
  5. Acl::DENY
  6. );

Adding Roles to the ACL

A role is an object that can or cannot access certain resources in the access list. As an example, we will define roles as groups of people in an organization. The Phalcon\Acl\Role class is available to create roles in a more structured way. Let’s add some roles to our recently created list:

  1. <?php
  2. use Phalcon\Acl\Role;
  3. // Create some roles.
  4. // The first parameter is the name, the second parameter is an optional description.
  5. $roleAdmins = new Role('Administrators', 'Super-User role');
  6. $roleGuests = new Role('Guests');
  7. // Add 'Guests' role to ACL
  8. $acl->addRole($roleGuests);
  9. // Add 'Designers' role to ACL without a Phalcon\Acl\Role
  10. $acl->addRole('Designers');

As you can see, roles are defined directly without using an instance.

Adding Resources

Resources are objects where access is controlled. Normally in MVC applications resources refer to controllers. Although this is not mandatory, the Phalcon\Acl\Resource class can be used in defining resources. It’s important to add related actions or operations to a resource so that the ACL can understand what it should to control.

  1. <?php
  2. use Phalcon\Acl\Resource;
  3. // Define the 'Customers' resource
  4. $customersResource = new Resource('Customers');
  5. // Add 'customers' resource with a couple of operations
  6. $acl->addResource(
  7. $customersResource,
  8. 'search'
  9. );
  10. $acl->addResource(
  11. $customersResource,
  12. [
  13. 'create',
  14. 'update',
  15. ]
  16. );

Defining Access Controls

Now that we have roles and resources, it’s time to define the ACL (i.e. which roles can access which resources). This part is very important especially taking into consideration your default access level allow or deny.

  1. <?php
  2. // Set access level for roles into resources
  3. $acl->allow('Guests', 'Customers', 'search');
  4. $acl->allow('Guests', 'Customers', 'create');
  5. $acl->deny('Guests', 'Customers', 'update');

The allow() method designates that a particular role has granted access to a particular resource. The deny() method does the opposite.

Querying an ACL

Once the list has been completely defined. We can query it to check if a role has a given permission or not.

  1. <?php
  2. // Check whether role has access to the operations
  3. // Returns 0
  4. $acl->isAllowed('Guests', 'Customers', 'edit');
  5. // Returns 1
  6. $acl->isAllowed('Guests', 'Customers', 'search');
  7. // Returns 1
  8. $acl->isAllowed('Guests', 'Customers', 'create');

Function based access

Also you can add as 4th parameter your custom function which must return boolean value. It will be called when you use isAllowed() method. You can pass parameters as associative array to isAllowed() method as 4th argument where key is parameter name in our defined function.

  1. <?php
  2. // Set access level for role into resources with custom function
  3. $acl->allow(
  4. 'Guests',
  5. 'Customers',
  6. 'search',
  7. function ($a) {
  8. return $a % 2 === 0;
  9. }
  10. );
  11. // Check whether role has access to the operation with custom function
  12. // Returns true
  13. $acl->isAllowed(
  14. 'Guests',
  15. 'Customers',
  16. 'search',
  17. [
  18. 'a' => 4,
  19. ]
  20. );
  21. // Returns false
  22. $acl->isAllowed(
  23. 'Guests',
  24. 'Customers',
  25. 'search',
  26. [
  27. 'a' => 3,
  28. ]
  29. );

Also if you don’t provide any parameters in isAllowed() method then default behaviour will be Acl::ALLOW. You can change it by using method setNoArgumentsDefaultAction().

  1. <?php
  2. use Phalcon\Acl;
  3. // Set access level for role into resources with custom function
  4. $acl->allow(
  5. 'Guests',
  6. 'Customers',
  7. 'search',
  8. function ($a) {
  9. return $a % 2 === 0;
  10. }
  11. );
  12. // Check whether role has access to the operation with custom function
  13. // Returns true
  14. $acl->isAllowed(
  15. 'Guests',
  16. 'Customers',
  17. 'search'
  18. );
  19. // Change no arguments default action
  20. $acl->setNoArgumentsDefaultAction(
  21. Acl::DENY
  22. );
  23. // Returns false
  24. $acl->isAllowed(
  25. 'Guests',
  26. 'Customers',
  27. 'search'
  28. );

Objects as role name and resource name

You can pass objects as roleName and resourceName. Your classes must implement Phalcon\Acl\RoleAware for roleName and Phalcon\Acl\ResourceAware for resourceName.

Our UserRole class

  1. <?php
  2. use Phalcon\Acl\RoleAware;
  3. // Create our class which will be used as roleName
  4. class UserRole implements RoleAware
  5. {
  6. protected $id;
  7. protected $roleName;
  8. public function __construct($id, $roleName)
  9. {
  10. $this->id = $id;
  11. $this->roleName = $roleName;
  12. }
  13. public function getId()
  14. {
  15. return $this->id;
  16. }
  17. // Implemented function from RoleAware Interface
  18. public function getRoleName()
  19. {
  20. return $this->roleName;
  21. }
  22. }

And our ModelResource class

  1. <?php
  2. use Phalcon\Acl\ResourceAware;
  3. // Create our class which will be used as resourceName
  4. class ModelResource implements ResourceAware
  5. {
  6. protected $id;
  7. protected $resourceName;
  8. protected $userId;
  9. public function __construct($id, $resourceName, $userId)
  10. {
  11. $this->id = $id;
  12. $this->resourceName = $resourceName;
  13. $this->userId = $userId;
  14. }
  15. public function getId()
  16. {
  17. return $this->id;
  18. }
  19. public function getUserId()
  20. {
  21. return $this->userId;
  22. }
  23. // Implemented function from ResourceAware Interface
  24. public function getResourceName()
  25. {
  26. return $this->resourceName;
  27. }
  28. }

Then you can use them in isAllowed() method.

  1. <?php
  2. use UserRole;
  3. use ModelResource;
  4. // Set access level for role into resources
  5. $acl->allow('Guests', 'Customers', 'search');
  6. $acl->allow('Guests', 'Customers', 'create');
  7. $acl->deny('Guests', 'Customers', 'update');
  8. // Create our objects providing roleName and resourceName
  9. $customer = new ModelResource(
  10. 1,
  11. 'Customers',
  12. 2
  13. );
  14. $designer = new UserRole(
  15. 1,
  16. 'Designers'
  17. );
  18. $guest = new UserRole(
  19. 2,
  20. 'Guests'
  21. );
  22. $anotherGuest = new UserRole(
  23. 3,
  24. 'Guests'
  25. );
  26. // Check whether our user objects have access to the operation on model object
  27. // Returns false
  28. $acl->isAllowed(
  29. $designer,
  30. $customer,
  31. 'search'
  32. );
  33. // Returns true
  34. $acl->isAllowed(
  35. $guest,
  36. $customer,
  37. 'search'
  38. );
  39. // Returns true
  40. $acl->isAllowed(
  41. $anotherGuest,
  42. $customer,
  43. 'search'
  44. );

Also you can access those objects in your custom function in allow() or deny(). They are automatically bind to parameters by type in function.

  1. <?php
  2. use UserRole;
  3. use ModelResource;
  4. // Set access level for role into resources with custom function
  5. $acl->allow(
  6. 'Guests',
  7. 'Customers',
  8. 'search',
  9. function (UserRole $user, ModelResource $model) { // User and Model classes are necessary
  10. return $user->getId == $model->getUserId();
  11. }
  12. );
  13. $acl->allow(
  14. 'Guests',
  15. 'Customers',
  16. 'create'
  17. );
  18. $acl->deny(
  19. 'Guests',
  20. 'Customers',
  21. 'update'
  22. );
  23. // Create our objects providing roleName and resourceName
  24. $customer = new ModelResource(
  25. 1,
  26. 'Customers',
  27. 2
  28. );
  29. $designer = new UserRole(
  30. 1,
  31. 'Designers'
  32. );
  33. $guest = new UserRole(
  34. 2,
  35. 'Guests'
  36. );
  37. $anotherGuest = new UserRole(
  38. 3,
  39. 'Guests'
  40. );
  41. // Check whether our user objects have access to the operation on model object
  42. // Returns false
  43. $acl->isAllowed(
  44. $designer,
  45. $customer,
  46. 'search'
  47. );
  48. // Returns true
  49. $acl->isAllowed(
  50. $guest,
  51. $customer,
  52. 'search'
  53. );
  54. // Returns false
  55. $acl->isAllowed(
  56. $anotherGuest,
  57. $customer,
  58. 'search'
  59. );

You can still add any custom parameters to function and pass associative array in isAllowed() method. Also order doesn’t matter.

Roles Inheritance

You can build complex role structures using the inheritance that Phalcon\Acl\Role provides. Roles can inherit from other roles, thus allowing access to supersets or subsets of resources. To use role inheritance, you need to pass the inherited role as the second parameter of the method call, when adding that role in the list.

  1. <?php
  2. use Phalcon\Acl\Role;
  3. // ...
  4. // Create some roles
  5. $roleAdmins = new Role('Administrators', 'Super-User role');
  6. $roleGuests = new Role('Guests');
  7. // Add 'Guests' role to ACL
  8. $acl->addRole($roleGuests);
  9. // Add 'Administrators' role inheriting from 'Guests' its accesses
  10. $acl->addRole($roleAdmins, $roleGuests);

Setup relationships after adding roles

Or you may prefer to add all of your roles together and then define the inheritance relationships afterwards.

  1. <?php
  2. use Phalcon\Acl\Role;
  3. // Create some roles
  4. $roleAdmins = new Role('Administrators', 'Super-User role');
  5. $roleGuests = new Role('Guests');
  6. // Add Roles to ACL
  7. $acl->addRole($roleGuests);
  8. $acl->addRole($roleAdmins);
  9. // Have 'Administrators' role inherit from 'Guests' its accesses
  10. $acl->addInherit($roleAdmins, $roleGuests);

Serializing ACL lists

To improve performance Phalcon\Acl instances can be serialized and stored in APC, session, text files or a database table so that they can be loaded at will without having to redefine the whole list. You can do that as follows:

  1. <?php
  2. use Phalcon\Acl\Adapter\Memory as AclList;
  3. // ...
  4. // Check whether ACL data already exist
  5. if (!is_file('app/security/acl.data')) {
  6. $acl = new AclList();
  7. // ... Define roles, resources, access, etc
  8. // Store serialized list into plain file
  9. file_put_contents(
  10. 'app/security/acl.data',
  11. serialize($acl)
  12. );
  13. } else {
  14. // Restore ACL object from serialized file
  15. $acl = unserialize(
  16. file_get_contents('app/security/acl.data')
  17. );
  18. }
  19. // Use ACL list as needed
  20. if ($acl->isAllowed('Guests', 'Customers', 'edit')) {
  21. echo 'Access granted!';
  22. } else {
  23. echo 'Access denied :(';
  24. }

It’s recommended to use the Memory adapter during development and use one of the other adapters in production.

Events

Phalcon\Acl is able to send events to an EventsManager if it’s present. Events are triggered using the type ‘acl’. Some events when returning boolean false could stop the active operation. The following events are supported:

Event NameTriggeredCan stop operation?
beforeCheckAccessTriggered before checking if a role/resource has accessYes
afterCheckAccessTriggered after checking if a role/resource has accessNo

The following example demonstrates how to attach listeners to this component:

  1. <?php
  2. use Phalcon\Acl\Adapter\Memory as AclList;
  3. use Phalcon\Events\Event;
  4. use Phalcon\Events\Manager as EventsManager;
  5. // ...
  6. // Create an event manager
  7. $eventsManager = new EventsManager();
  8. // Attach a listener for type 'acl'
  9. $eventsManager->attach(
  10. 'acl:beforeCheckAccess',
  11. function (Event $event, $acl) {
  12. echo $acl->getActiveRole();
  13. echo $acl->getActiveResource();
  14. echo $acl->getActiveAccess();
  15. }
  16. );
  17. $acl = new AclList();
  18. // Setup the $acl
  19. // ...
  20. // Bind the eventsManager to the ACL component
  21. $acl->setEventsManager($eventsManager);

Implementing your own adapters

The Phalcon\Acl\AdapterInterface interface must be implemented in order to create your own ACL adapters or extend the existing ones.