Skip to content

Commit 837d6c7

Browse files
schmittjohfabpot
authored andcommitted
added some ACL documentation
1 parent 1dbbdbe commit 837d6c7

File tree

3 files changed

+357
-0
lines changed

3 files changed

+357
-0
lines changed

guides/security/acl.rst

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
.. index::
2+
single: Security; Access Control Lists (ACLs)
3+
4+
Access Control Lists (ACLs)
5+
===========================
6+
7+
In complex applications, you will often face the problem that access decisions
8+
cannot only be based on the person (``Token``) who is requesting access, but
9+
also involve a domain object that access is being requested for. This is where
10+
the ACL system comes in.
11+
12+
Imagine you are designing a blog system where your users can comment on your
13+
posts. Now, you want a user to be able to edit his own comments, but not those
14+
of other users; besides, you yourself want to be able to edit all comments.
15+
In this scenario, ``Comment`` would be our domain object that you want to
16+
restrict access to. You could take several approaches to accomplish this using
17+
Symfony2, two basic approaches are (non-exhaustive):
18+
19+
- *Enforce security in your business methods*: Basically, that means keeping
20+
a reference inside each ``Comment`` to all users who have access, and then
21+
compare these users to the provided ``Token``.
22+
- *Enforce security with roles*: In this approach, you would add a role for
23+
each ``Comment`` object, i.e. ``ROLE_COMMENT_1``, ``ROLE_COMMENT_2``, etc.
24+
25+
Both approaches are perfectly valid. However, they couple your authorization
26+
logic to your business code which makes it less reusable elsewhere, and also
27+
increases the difficulty of unit testing. Besides, you could run into
28+
performance issues if many users would have access to a single domain object.
29+
30+
Fortunately, there is a better way, which we will talk about now.
31+
32+
Bootstrapping
33+
-------------
34+
Now, before we finally can get into action, we need to do some bootstrapping.
35+
First, we need to configure the connection the ACL system is supposed to use:
36+
37+
.. configuration_block ::
38+
39+
.. code_block:: yaml
40+
41+
# app/config/security.yml
42+
security.acl:
43+
connection: default
44+
45+
.. code-block:: xml
46+
47+
<!-- app/config/security.xml -->
48+
<acl>
49+
<connection>default</connection>
50+
</acl>
51+
52+
.. code-block:: php
53+
54+
// app/config/security.php
55+
$container->loadFromExtension('security', 'acl', array(
56+
'connection' => 'default',
57+
));
58+
59+
60+
After the connection is configured. We have to import the database structure.
61+
Fortunately, we have a task for this. Simply run the following command:
62+
63+
``php app/console init:acl``
64+
65+
66+
Getting Started
67+
---------------
68+
Coming back to our small example from the beginning, let's implement ACL for it.
69+
70+
Creating an ACL, and adding an ACE
71+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
72+
73+
.. code-block:: php
74+
75+
// BlogController.php
76+
public function addCommentAction(Post $post)
77+
{
78+
$comment = new Comment();
79+
80+
// setup $form, and bind data
81+
// ...
82+
83+
if ($form->isValid()) {
84+
$entityManager = $this->container->get('doctrine.orm.default_entity_manager');
85+
$entityManager->persist($comment);
86+
$entityManager->flush();
87+
88+
// creating the ACL
89+
$aclProvider = $this->container->get('security.acl.provider');
90+
$objectIdentity = ObjectIdentity::fromDomainObject($comment);
91+
$acl = $aclProvider->createAcl($objectIdentity);
92+
93+
// retrieving the security identity of the currently logged-in user
94+
$securityContext = $this->container->get('security.context');
95+
$user = $securityContext->getToken()->getUser();
96+
$securityIdentity = new UserSecurityIdentity($user);
97+
98+
// grant owner access
99+
$acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER);
100+
$aclProvider->updateAcl($acl);
101+
}
102+
}
103+
104+
There are a couple of important implementation decisions in this code snippet. For now,
105+
I only want to highlight two:
106+
107+
First, you may have noticed that ``->createAcl()`` does not accept domain objects
108+
directly, but only implementations of the ``ObjectIdentityInterface``. This
109+
additional step of indirection allows you to work with ACLs even when you have
110+
no actual domain object instance at hand. This will be extremely helpful if you
111+
want to check permissions for a large number of objects without actually hydrating
112+
these objects.
113+
114+
The other interesting part is the ``->insertObjectAce()`` call. In our example,
115+
we are granting the user who is currently logged in owner access to the Comment.
116+
The ``MaskBuilder::MASK_OWNER`` is a pre-defined integer bitmask; don't worry
117+
the mask builder will abstract away most of the technical details, but using
118+
this technique we can store many different permissions in one database row
119+
which gives us a considerable boost in performance.
120+
121+
.. tip::
122+
123+
The order in which ACEs are checked is significant. As a general rule, you
124+
should place more specific entries at the beginning.
125+
126+
Checking Access
127+
~~~~~~~~~~~~~~~
128+
129+
.. code-block:: php
130+
131+
// BlogController.php
132+
public function editCommentAction(Comment $comment)
133+
{
134+
$securityContext = $this->container->get('security.context');
135+
136+
// check for edit access
137+
if (false === $securityContext->vote('EDIT', $comment))
138+
{
139+
throw new AccessDeniedException();
140+
}
141+
142+
// do your editing here
143+
}
144+
145+
In this example, we check whether the user has the ``EDIT`` permission. Internally,
146+
Symfony2 maps the permission to several integer bitmasks, and checks whether the
147+
user has any of them.
148+
149+
.. note::
150+
151+
You can define up to 32 base permissions (depending on your OS PHP might vary
152+
between 30 to 32). In addition, you can also define cumulative permissions.
153+
154+
Cumulative Permissions
155+
----------------------
156+
In our first example above, we only granted the user the ``OWNER`` base
157+
permission. While this effectively also allows the user to perform any operation
158+
such as view, edit, etc. on the domain object, there are cases where we want to
159+
grant these permissions explicitly.
160+
161+
The ``MaskBuilder`` can be used for creating bit masks easily by combining
162+
several base permissions:
163+
164+
.. code-block:: php
165+
166+
$builder = new MaskBuilder();
167+
$builder
168+
->add('view')
169+
->add('edit')
170+
->add('delete')
171+
->add('undelete')
172+
;
173+
$mask = $builder->get(); // int(15)
174+
175+
This integer bitmask can then be used to grant a user the base permissions you
176+
added above:
177+
178+
.. code-block:: php
179+
180+
$acl->insertObjectAce(new UserSecurityIdentity('johannes'), $mask);
181+
182+
The user is now allowed to view, edit, delete, and un-delete objects.

guides/security/acl_advanced.rst

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
.. index::
2+
single: Security; Advanced ACL concepts
3+
4+
Advanced ACL Concepts
5+
=====================
6+
7+
The aim of this chapter is to give a more in-depth view of the ACL system, and
8+
also explain some of the design decisions behind it.
9+
10+
Design Concepts
11+
---------------
12+
Symfony2's object instance security capabilities are based on the concept of
13+
an Access Control List. Every domain object **instance** has its own ACL.
14+
The ACL instance holds a detailed list of Access Control Entries (ACEs) which
15+
are used to make access decisions. Symfony2's ACL system focuses on two main
16+
objectives:
17+
18+
- providing a way to efficiently retrieve a large amount of ACLs/ACEs for
19+
your domain objects, and to modify them
20+
- providing a way to easily make decisions of whether a person is allowed
21+
to perform an action on a domain object or not
22+
23+
As indicated by the first point, one of the main capabilities of Symfony2's
24+
ACL system is a high-performance way of retrieving ACLs/ACEs. This is
25+
extremely important since each ACL might have several ACEs, and inherit
26+
from another ACL in a tree-like fashion. Therefore, we specifically do not
27+
leverage any ORM, but the default implementation interacts with your
28+
connection directly using Doctrine's DBAL.
29+
30+
Object Identities
31+
~~~~~~~~~~~~~~~~~
32+
The ACL system is completely decoupled from your domain objects. They don't even
33+
have to be stored in the same database, or on the same server. In order to
34+
achieve this decoupling, in the ACL system your objects are represented through
35+
object identity objects. Everytime, you want to retrieve the ACL for a domain
36+
object, the ACL system will first create an object identity from your domain
37+
object, and then pass this object identity to the ACL provider for further
38+
processing.
39+
40+
41+
Security Identities
42+
~~~~~~~~~~~~~~~~~~~
43+
This is analog to the object identity, but represents a user, or a role in your
44+
application. Each role, or user has its own security identity.
45+
46+
47+
Database Table Structure
48+
------------------------
49+
The default implementation uses five database tables as listed below. The
50+
tables are ordered from least rows to most rows in a typical application:
51+
52+
- *acl_security_identities*: This table records all security identities
53+
(SID) which hold ACEs. The default implementation ships with two
54+
security identities: ``RoleSecurityIdentity``, and ``UserSecurityIdentity``
55+
- *acl_classes*: This table maps class names to a unique id which can be
56+
referenced from other tables.
57+
- *acl_object_identities*: Each row in this table represents a single
58+
domain object instance.
59+
- *acl_object_identity_ancestors*: This table allows us to determine
60+
all the ancestors of an ACL in a very efficient way.
61+
- *acl_entries*: This table contains all ACEs. This is typically the
62+
table with the most rows. It can contain tens of millions without
63+
significantly impacting performance.
64+
65+
66+
Scope of Access Control Entries
67+
-------------------------------
68+
Access control entries can have different scopes in which they apply. In Symfony2,
69+
we have basically two different scopes:
70+
71+
- Class-Scope: These entries apply to all objects with the same class.
72+
- Object-Scope: This was the scope we solely used in the previous chapter, and
73+
it only applies to one specific object.
74+
75+
Sometimes, you will find the need to apply an ACE only to a specific field of
76+
the object. Let's say you want the ID only to be viewable by an administrator,
77+
but not by your customer service. To solve this common problem, we have added
78+
two more sub-scopes:
79+
80+
- Class-Field-Scope: These entries apply to all objects with the same class, but
81+
only to a specific field of the objects.
82+
- Object-Field-Scope: These entries apply to a specific object, and only to a
83+
specific field of that object.
84+
85+
86+
Pre-Authorization Decisions
87+
---------------------------
88+
For pre-authorization decisions, that is decisions before any method, or secure
89+
action is invoked, we rely on the proven AccessDecisionManager service that is
90+
also used for reaching authorization decisions based on roles. Just like roles,
91+
the ACL system adds several new attributes which may be used to check for
92+
different permissions.
93+
94+
Built-in Permission Map
95+
~~~~~~~~~~~~~~~~~~~~~~~
96+
+------------------+----------------------------+-----------------------------+
97+
| Attribute | Intended Meaning | Integer Bitmasks |
98+
+==================+============================+=============================+
99+
| VIEW | Whether someone is allowed | VIEW, EDIT, OPERATOR, |
100+
| | to view the domain object. | MASTER, or OWNER |
101+
+------------------+----------------------------+-----------------------------+
102+
| EDIT | Whether someone is allowed | EDIT, OPERATOR, MASTER, |
103+
| | to make changes to the | or OWNER |
104+
| | domain object. | |
105+
+------------------+----------------------------+-----------------------------+
106+
| DELETE | Whether someone is allowed | DELETE, OPERATOR, MASTER, |
107+
| | to delete the domain | or OWNER |
108+
| | object. | |
109+
+------------------+----------------------------+-----------------------------+
110+
| UNDELETE | Whether someone is allowed | UNDELETE, OPERATOR, MASTER, |
111+
| | to restore a previously | or OWNER |
112+
| | deleted domain object. | |
113+
+------------------+----------------------------+-----------------------------+
114+
| OPERATOR | Whether someone is allowed | OPERATOR, MASTER, or OWNER |
115+
| | to perform all of the above| |
116+
| | actions. | |
117+
+------------------+----------------------------+-----------------------------+
118+
| MASTER | Whether someone is allowed | MASTER, or OWNER |
119+
| | to perform all of the above| |
120+
| | actions, and in addition is| |
121+
| | allowed to grant | |
122+
| | any of the above | |
123+
| | permissions to others. | |
124+
+------------------+----------------------------+-----------------------------+
125+
| OWNER | Whether someone owns the | OWNER |
126+
| | domain object. An owner can| |
127+
| | perform any of the above | |
128+
| | actions. | |
129+
+------------------+----------------------------+-----------------------------+
130+
131+
Permission Attributes vs. Permission Bitmasks
132+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
133+
Attributes are used by the AccessDecisionManager, just like roles are attributes
134+
used by the AccessDecisionManager. Often, these attributes represent in fact an
135+
aggregate of integer bitmasks. Integer bitmasks on the other hand, are used by
136+
the ACL system internally to efficiently store your users' permissions in the
137+
database, and perform access checks using extremely fast bitmask operations.
138+
139+
Extensibility
140+
~~~~~~~~~~~~~
141+
The above permission map is by no means static, and theoretically could be
142+
completely replaced at will. However, it should cover most problems you encounter,
143+
and for interoperability with other bundles, we encourage you to stick to the
144+
meaning we have envisaged for them.
145+
146+
147+
Post Authorization Decisions
148+
----------------------------
149+
Post authorization decisions are made after a secure method has been invoked, and
150+
typically involve the domain object which is returned by such a method. After
151+
invocation providers also allow to modify, or filter the domain object before it
152+
is returned.
153+
154+
Due to current limitations of the PHP language, there are no post-authorization
155+
capabilities build into the core Security component. However, there is an
156+
experimental SecurityExtraBundle_ which adds these capabilities. See its
157+
documentation for further information on how this is accomplished.
158+
159+
.. _SecurityExtraBundle: https://github.com/schmittjoh/SecurityExtraBundle
160+
161+
162+
Process for Reaching Authorization Decisions
163+
--------------------------------------------
164+
The ACL class provides two methods for determining whether a security identity
165+
has the required bitmasks, ``isGranted`` and ``isFieldGranted``. When the ACL
166+
receives an authorization request through one of these methods, it delegates
167+
this request to an implementation of PermissionGrantingStrategy. This allows you
168+
to replace the way access decisions are reached without actually modifying the
169+
ACL class itself.
170+
171+
The PermissionGrantingStrategy first checks all your object-scope ACEs if none
172+
is applicable, the class-scope ACEs will be checked, if none is applicable, then
173+
the process will be repeated with the ACEs of the parent ACL. If no parent ACL
174+
exists, an exception will be thrown.

guides/security/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ Security
88
users
99
authentication
1010
authorization
11+
acl

0 commit comments

Comments
 (0)