How to Use advanced ACL Concepts
Warning: You are browsing the documentation for Symfony 3.x, which is no longer maintained.
Read the updated version of this page for Symfony 7.0 (the current stable version).
ACL support was deprecated in Symfony 3.4 and will be removed in 4.0. Install the Symfony ACL bundle if you want to keep using ACL.
The aim of this article is to give a more in-depth view of the ACL system, and also explain some of the design decisions behind it.
Symfony's object instance security capabilities are based on the concept of an Access Control List. Every domain object instance has its own ACL. The ACL instance holds a detailed list of Access Control Entries (ACEs) which are used to make access decisions. Symfony's ACL system focuses on two main objectives:
- providing a way to efficiently retrieve a large amount of ACLs/ACEs for your domain objects, and to modify them;
- providing a way to make decisions of whether a person is allowed to perform an action on a domain object or not.
As indicated by the first point, one of the main capabilities of Symfony's ACL system is a high-performance way of retrieving ACLs/ACEs. This is extremely important since each ACL might have several ACEs, and inherit from another ACL in a tree-like fashion. Therefore, no ORM is leveraged, instead the default implementation interacts with your connection directly using Doctrine's DBAL.
The ACL system is completely decoupled from your domain objects. They don't even have to be stored in the same database, or on the same server. In order to achieve this decoupling, in the ACL system your objects are represented through object identity objects. Every time you want to retrieve the ACL for a domain object, the ACL system will first create an object identity from your domain object, and then pass this object identity to the ACL provider for further processing.
This is analog to the object identity, but represents a user, or a role in your application. Each role, or user has its own security identity.
For users, the security identity is based on the username. This means that, if for any reason, a user's username was to change, you must ensure its security identity is updated too. The MutableAclProvider::updateUserSecurityIdentity() method is there to handle the update.
The default implementation uses five database tables as listed below. The tables are ordered from least rows to most rows in a typical application:
- acl_security_identities: This table records all security identities (SID) which hold ACEs. The default implementation ships with two security identities: RoleSecurityIdentity and UserSecurityIdentity.
- acl_classes: This table maps class names to a unique ID which can be referenced from other tables.
- acl_object_identities: Each row in this table represents a single domain object instance.
- acl_object_identity_ancestors: This table allows all the ancestors of an ACL to be determined in a very efficient way.
- acl_entries: This table contains all ACEs. This is typically the table with the most rows. It can contain tens of millions without significantly impacting performance.
Access control entries can have different scopes in which they apply. In Symfony, there are basically two different scopes:
- Class-Scope: These entries apply to all objects with the same class.
- Object-Scope: This was the scope solely used in the previous article, and it only applies to one specific object.
Sometimes, you will find the need to apply an ACE only to a specific field of the object. Suppose you want the ID only to be viewable by an administrator, but not by your customer service. To solve this common problem, two more sub-scopes have been added:
- Class-Field-Scope: These entries apply to all objects with the same class, but only to a specific field of the objects.
- Object-Field-Scope: These entries apply to a specific object, and only to a specific field of that object.
For pre-authorization decisions, that is decisions made before any secure method (or secure action) is invoked, the proven AccessDecisionManager service is used. The AccessDecisionManager is also used for reaching authorization decisions based on roles. Just like roles, the ACL system adds several new attributes which may be used to check for different permissions.
|Whether someone is allowed to view the domain object.
|VIEW, EDIT, OPERATOR, MASTER, or OWNER
|Whether someone is allowed to make changes to the domain object.
|EDIT, OPERATOR, MASTER, or OWNER
|Whether someone is allowed to create the domain object.
|CREATE, OPERATOR, MASTER, or OWNER
|Whether someone is allowed to delete the domain object.
|DELETE, OPERATOR, MASTER, or OWNER
|Whether someone is allowed to restore a previously deleted domain object.
|UNDELETE, OPERATOR, MASTER, or OWNER
|Whether someone is allowed to perform all of the above actions.
|OPERATOR, MASTER, or OWNER
|Whether someone is allowed to perform all of the above actions, and in addition is allowed to grant any of the above permissions to others.
|MASTER, or OWNER
|Whether someone owns the domain object. An owner can perform any of the above actions and grant master and owner permissions.
Attributes are used by the AccessDecisionManager, just like roles. Often, these attributes represent in fact an aggregate of integer bitmasks. Integer bitmasks on the other hand, are used by the ACL system internally to efficiently store your users' permissions in the database, and perform access checks using extremely fast bitmask operations.
The above permission map is by no means static, and theoretically could be completely replaced at will. However, it should cover most problems you encounter, and for interoperability with other bundles, you are encouraged to stick to the meaning envisaged for them.
Post authorization decisions are made after a secure method has been invoked, and typically involve the domain object which is returned by such a method. After invocation providers also allow to modify, or filter the domain object before it is returned.
Due to current limitations of the PHP language, there are no post-authorization capabilities built into the core Security component. However, there is an experimental JMSSecurityExtraBundle which adds these capabilities. See its documentation for further information on how this is accomplished.
The ACL class provides two methods for determining whether a security identity
has the required bitmasks,
isFieldGranted(). When the ACL
receives an authorization request through one of these methods, it delegates
this request to an implementation of
This allows you to replace the way access decisions are reached without actually
modifying the ACL class itself.
PermissionGrantingStrategy first checks all your object-scope ACEs. If one
is applicable, the class-scope ACEs will be checked. If none is applicable,
then the process will be repeated with the ACEs of the parent ACL. If no
parent ACL exists, an exception will be thrown.