Skip to content

Commit a9f0078

Browse files
committed
minor #11118 Improved documentation about access controls (Jules Pietri)
This PR was merged into the 3.4 branch. Discussion ---------- Improved documentation about access controls <!-- If your pull request fixes a BUG, use the oldest maintained branch that contains the bug (see https://symfony.com/roadmap for the list of maintained branches). If your pull request documents a NEW FEATURE, use the same Symfony branch where the feature was introduced (and `master` for features of unreleased versions). --> Commits ------- a6b0959 Improved documentation about access controls
2 parents b05009d + a6b0959 commit a9f0078

File tree

8 files changed

+87
-56
lines changed

8 files changed

+87
-56
lines changed

configuration/external_parameters.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ Symfony provides the following env var processors:
286286
# app/config/config.yml
287287
parameters:
288288
env(HEALTH_CHECK_METHOD): 'Symfony\Component\HttpFoundation\Request::METHOD_HEAD'
289+
289290
security:
290291
access_control:
291292
- { path: '^/health-check$', methods: '%env(const:HEALTH_CHECK_METHOD)%' }

security.rst

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ user to be logged in to access this URL:
226226
227227
access_control:
228228
# require ROLE_ADMIN for /admin*
229-
- { path: ^/admin, roles: ROLE_ADMIN }
229+
- { path: '^/admin', roles: ROLE_ADMIN }
230230
231231
.. code-block:: xml
232232
@@ -713,7 +713,7 @@ URL pattern. You saw this earlier, where anything matching the regular expressio
713713
714714
access_control:
715715
# require ROLE_ADMIN for /admin*
716-
- { path: ^/admin, roles: ROLE_ADMIN }
716+
- { path: '^/admin', roles: ROLE_ADMIN }
717717
718718
# the 'path' value can be any valid regular expression
719719
# (this one will match URLs like /api/post/7298 and /api/comment/528491)
@@ -759,11 +759,11 @@ URL pattern. You saw this earlier, where anything matching the regular expressio
759759
],
760760
'access_control' => [
761761
// require ROLE_ADMIN for /admin*
762-
['path' => '^/admin', 'role' => 'ROLE_ADMIN'],
762+
['path' => '^/admin', 'roles' => 'ROLE_ADMIN'],
763763
764764
// the 'path' value can be any valid regular expression
765765
// (this one will match URLs like /api/post/7298 and /api/comment/528491)
766-
['path' => '^/api/(post|comment)/\d+$', 'role' => 'ROLE_USER'],
766+
['path' => '^/api/(post|comment)/\d+$', 'roles' => 'ROLE_USER'],
767767
],
768768
]);
769769
@@ -785,8 +785,8 @@ matches the URL.
785785
# ...
786786
787787
access_control:
788-
- { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
789-
- { path: ^/admin, roles: ROLE_ADMIN }
788+
- { path: '^/admin/users', roles: ROLE_SUPER_ADMIN }
789+
- { path: '^/admin', roles: ROLE_ADMIN }
790790
791791
.. code-block:: xml
792792
@@ -813,8 +813,8 @@ matches the URL.
813813
// ...
814814
815815
'access_control' => [
816-
['path' => '^/admin/users', 'role' => 'ROLE_SUPER_ADMIN'],
817-
['path' => '^/admin', 'role' => 'ROLE_ADMIN'],
816+
['path' => '^/admin/users', 'roles' => 'ROLE_SUPER_ADMIN'],
817+
['path' => '^/admin', 'roles' => 'ROLE_ADMIN'],
818818
],
819819
]);
820820

security/access_control.rst

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ for each ``access_control`` entry, which determines whether or not a given
2222
access control should be used on this request. The following ``access_control``
2323
options are used for matching:
2424

25-
* ``path``
26-
* ``ip`` or ``ips`` (netmasks are also supported)
27-
* ``host``
28-
* ``methods``
25+
* ``path``: a regular expression (without delimiters)
26+
* ``ip`` or ``ips``: netmasks are also supported
27+
* ``host``: a regular expression
28+
* ``methods``: one or many methods
2929

3030
Take the following ``access_control`` entries as an example:
3131

@@ -37,10 +37,11 @@ Take the following ``access_control`` entries as an example:
3737
security:
3838
# ...
3939
access_control:
40-
- { path: ^/admin, roles: ROLE_USER_IP, ip: 127.0.0.1 }
41-
- { path: ^/admin, roles: ROLE_USER_HOST, host: symfony\.com$ }
42-
- { path: ^/admin, roles: ROLE_USER_METHOD, methods: [POST, PUT] }
43-
- { path: ^/admin, roles: ROLE_USER }
40+
- { path: '^/admin', roles: ROLE_USER_IP, ip: 127.0.0.1 }
41+
- { path: '^/admin', roles: ROLE_USER_HOST, host: symfony\.com$ }
42+
- { path: '^/admin', roles: ROLE_USER_METHOD, methods: [POST, PUT] }
43+
# when defining multiple roles, users must have at least one of them (it's like an OR condition)
44+
- { path: '^/admin', roles: [ROLE_MANAGER, ROLE_ADMIN] }
4445
4546
.. code-block:: xml
4647
@@ -57,7 +58,8 @@ Take the following ``access_control`` entries as an example:
5758
<rule path="^/admin" role="ROLE_USER_IP" ip="127.0.0.1"/>
5859
<rule path="^/admin" role="ROLE_USER_HOST" host="symfony\.com$"/>
5960
<rule path="^/admin" role="ROLE_USER_METHOD" methods="POST, PUT"/>
60-
<rule path="^/admin" role="ROLE_USER"/>
61+
<!-- when defining multiple roles, users must have at least one of them (it's like an OR condition) -->
62+
<rule path="^/admin" roles="ROLE_ADMIN, ROLE_MANAGER" />
6163
</config>
6264
</srv:container>
6365
@@ -69,31 +71,32 @@ Take the following ``access_control`` entries as an example:
6971
'access_control' => [
7072
[
7173
'path' => '^/admin',
72-
'role' => 'ROLE_USER_IP',
73-
'ip' => '127.0.0.1',
74+
'roles' => 'ROLE_USER_IP',
75+
'ips' => '127.0.0.1',
7476
],
7577
[
7678
'path' => '^/admin',
77-
'role' => 'ROLE_USER_HOST',
79+
'roles' => 'ROLE_USER_HOST',
7880
'host' => 'symfony\.com$',
7981
],
8082
[
8183
'path' => '^/admin',
82-
'role' => 'ROLE_USER_METHOD',
84+
'roles' => 'ROLE_USER_METHOD',
8385
'methods' => 'POST, PUT',
8486
],
8587
[
8688
'path' => '^/admin',
87-
'role' => 'ROLE_USER',
89+
// when defining multiple roles, users must have at least one of them (it's like an OR condition)
90+
'roles' => ['ROLE_MANAGER', 'ROLE_ADMIN'],
8891
],
8992
],
9093
]);
9194
9295
For each incoming request, Symfony will decide which ``access_control``
9396
to use based on the URI, the client's IP address, the incoming host name,
9497
and the request method. Remember, the first rule that matches is used, and
95-
if ``ip``, ``host`` or ``method`` are not specified for an entry, that ``access_control``
96-
will match any ``ip``, ``host`` or ``method``:
98+
if ``ips``, ``host`` or ``methods`` are not specified for an entry, that
99+
``access_control`` will match any ``ips``, ``host`` or ``methods``:
97100

98101
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
99102
| URI | IP | HOST | METHOD | ``access_control`` | Why? |
@@ -114,9 +117,10 @@ will match any ``ip``, ``host`` or ``method``:
114117
| ``/admin/user`` | 168.0.0.1 | example.com | POST | rule #3 (``ROLE_USER_METHOD``) | The ``ip`` and ``host`` don't match the first two entries, |
115118
| | | | | | but the third - ``ROLE_USER_METHOD`` - matches and is used. |
116119
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
117-
| ``/admin/user`` | 168.0.0.1 | example.com | GET | rule #4 (``ROLE_USER``) | The ``ip``, ``host`` and ``method`` prevent the first |
120+
| ``/admin/user`` | 168.0.0.1 | example.com | GET | rule #4 (``ROLE_MANAGER``) | The ``ip``, ``host`` and ``method`` prevent the first |
118121
| | | | | | three entries from matching. But since the URI matches the |
119-
| | | | | | ``path`` pattern of the ``ROLE_USER`` entry, it is used. |
122+
| | | | | | ``path`` pattern, then the ``ROLE_MANAGER`` (or the |
123+
| |    | | | | ``ROLE_ADMIN``) is used. |
120124
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
121125
| ``/foo`` | 127.0.0.1 | symfony.com | POST | matches no entries | This doesn't match any ``access_control`` rules, since its |
122126
| | | | | | URI doesn't match any of the ``path`` values. |
@@ -142,6 +146,14 @@ options:
142146
does not match this value (e.g. ``https``), the user will be redirected
143147
(e.g. redirected from ``http`` to ``https``, or vice versa).
144148

149+
.. tip::
150+
151+
Behind the scenes, the array value of ``roles`` is passed as the
152+
``$attributes`` argument to each voter in the application with the
153+
:class:`Symfony\\Component\\HttpFoundation\\Request` as ``$subject``. You
154+
can learn how to use your custom attributes by reading
155+
:ref:`security/custom-voter`.
156+
145157
.. tip::
146158

147159
If access is denied, the system will try to authenticate the user if not
@@ -178,8 +190,8 @@ pattern so that it is only accessible by requests from the local server itself:
178190
access_control:
179191
#
180192
# the 'ips' option supports IP addresses and subnet masks
181-
- { path: ^/internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, ::1, 192.168.0.1/24] }
182-
- { path: ^/internal, roles: ROLE_NO_ACCESS }
193+
- { path: '^/internal', roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, ::1, 192.168.0.1/24] }
194+
- { path: '^/internal', roles: ROLE_NO_ACCESS }
183195
184196
.. code-block:: xml
185197
@@ -212,13 +224,13 @@ pattern so that it is only accessible by requests from the local server itself:
212224
'access_control' => [
213225
[
214226
'path' => '^/internal',
215-
'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
227+
'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY',
216228
// the 'ips' option supports IP addresses and subnet masks
217229
'ips' => ['127.0.0.1', '::1'],
218230
],
219231
[
220232
'path' => '^/internal',
221-
'role' => 'ROLE_NO_ACCESS',
233+
'roles' => 'ROLE_NO_ACCESS',
222234
],
223235
],
224236
]);
@@ -263,7 +275,10 @@ key:
263275
access_control:
264276
-
265277
path: ^/_internal/secure
266-
allow_if: "'127.0.0.1' == request.getClientIp() or has_role('ROLE_ADMIN')"
278+
# the 'role' and 'allow-if' options work like an OR expression, so
279+
# access is granted if the expression is TRUE or the user has ROLE_ADMIN
280+
roles: 'ROLE_ADMIN'
281+
allow_if: "'127.0.0.1' == request.getClientIp() or request.header.has('X-Secure-Access')"
267282
268283
.. code-block:: xml
269284
@@ -276,8 +291,11 @@ key:
276291
https://symfony.com/schema/dic/services/services-1.0.xsd">
277292
278293
<config>
294+
<!-- the 'role' and 'allow-if' options work like an OR expression, so
295+
access is granted if the expression is TRUE or the user has ROLE_ADMIN -->
279296
<rule path="^/_internal/secure"
280-
allow-if="'127.0.0.1' == request.getClientIp() or has_role('ROLE_ADMIN')"/>
297+
role="ROLE_ADMIN"
298+
allow-if="'127.0.0.1' == request.getClientIp() or request.header.has('X-Secure-Access')" />
281299
</config>
282300
</srv:container>
283301
@@ -286,13 +304,22 @@ key:
286304
'access_control' => [
287305
[
288306
'path' => '^/_internal/secure',
289-
'allow_if' => '"127.0.0.1" == request.getClientIp() or has_role("ROLE_ADMIN")',
307+
// the 'role' and 'allow-if' options work like an OR expression, so
308+
// access is granted if the expression is TRUE or the user has ROLE_ADMIN
309+
'roles' => 'ROLE_ADMIN',
310+
'allow_if' => '"127.0.0.1" == request.getClientIp() or request.header.has('X-Secure-Access')',
290311
],
291312
],
292313
293-
In this case, when the user tries to access any URL starting with ``/_internal/secure``,
294-
they will only be granted access if the IP address is ``127.0.0.1`` or if
295-
the user has the ``ROLE_ADMIN`` role.
314+
In this case, when the user tries to access any URL starting with
315+
``/_internal/secure``, they will only be granted access if the IP address is
316+
``127.0.0.1`` or a secure header, or if the user has the ``ROLE_ADMIN`` role.
317+
318+
.. note::
319+
320+
Internally ``allow_if`` triggers the built-in
321+
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\ExpressionVoter`
322+
as like it was part of the attributes defined in the ``roles`` option.
296323

297324
Inside the expression, you have access to a number of different variables
298325
and functions including ``request``, which is the Symfony
@@ -343,7 +370,7 @@ the user will be redirected to ``https``:
343370
'access_control' => [
344371
[
345372
'path' => '^/cart/checkout',
346-
'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
373+
'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY',
347374
'requires_channel' => 'https',
348375
],
349376
],

security/api_key_authentication.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ If you have defined ``access_control``, make sure to add a new entry:
370370
# ...
371371
372372
access_control:
373-
- { path: ^/api, roles: ROLE_API }
373+
- { path: '^/api', roles: ROLE_API }
374374
375375
.. code-block:: xml
376376
@@ -393,7 +393,7 @@ If you have defined ``access_control``, make sure to add a new entry:
393393
'access_control' => [
394394
[
395395
'path' => '^/api',
396-
'role' => 'ROLE_API',
396+
'roles' => 'ROLE_API',
397397
],
398398
],
399399
]);

security/force_https.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ to use HTTPS then you could use the following configuration:
1818
# ...
1919
2020
access_control:
21-
- { path: ^/secure, roles: ROLE_ADMIN, requires_channel: https }
21+
- { path: '^/secure', roles: ROLE_ADMIN, requires_channel: https }
2222
2323
.. code-block:: xml
2424
@@ -46,7 +46,7 @@ to use HTTPS then you could use the following configuration:
4646
'access_control' => [
4747
[
4848
'path' => '^/secure',
49-
'role' => 'ROLE_ADMIN',
49+
'roles' => 'ROLE_ADMIN',
5050
'requires_channel' => 'https',
5151
],
5252
],
@@ -66,7 +66,7 @@ role:
6666
# ...
6767
6868
access_control:
69-
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
69+
- { path: '^/login', roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
7070
7171
.. code-block:: xml
7272
@@ -97,7 +97,7 @@ role:
9797
'access_control' => [
9898
[
9999
'path' => '^/login',
100-
'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
100+
'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY',
101101
'requires_channel' => 'https',
102102
],
103103
],

security/form_login_setup.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ all URLs (including the ``/login`` URL), will cause a redirect loop:
283283
284284
# ...
285285
access_control:
286-
- { path: ^/, roles: ROLE_ADMIN }
286+
- { path: '^/', roles: ROLE_ADMIN }
287287
288288
.. code-block:: xml
289289
@@ -307,7 +307,7 @@ all URLs (including the ``/login`` URL), will cause a redirect loop:
307307
308308
// ...
309309
'access_control' => [
310-
['path' => '^/', 'role' => 'ROLE_ADMIN'],
310+
['path' => '^/', 'roles' => 'ROLE_ADMIN'],
311311
],
312312
313313
Adding an access control that matches ``/login/*`` and requires *no* authentication
@@ -321,8 +321,8 @@ fixes the problem:
321321
322322
# ...
323323
access_control:
324-
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
325-
- { path: ^/, roles: ROLE_ADMIN }
324+
- { path: '^/login', roles: IS_AUTHENTICATED_ANONYMOUSLY }
325+
- { path: '^/', roles: ROLE_ADMIN }
326326
327327
.. code-block:: xml
328328
@@ -347,8 +347,8 @@ fixes the problem:
347347
348348
// ...
349349
'access_control' => [
350-
['path' => '^/login', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
351-
['path' => '^/', 'role' => 'ROLE_ADMIN'],
350+
['path' => '^/login', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
351+
['path' => '^/', 'roles' => 'ROLE_ADMIN'],
352352
],
353353
354354
3. Be Sure check_path Is Behind a Firewall

security/multiple_guard_authenticators.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ the solution is to split the configuration into two separate firewalls:
108108
authenticators:
109109
- AppBundle\Security\LoginFormAuthenticator
110110
access_control:
111-
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
112-
- { path: ^/api, roles: ROLE_API_USER }
113-
- { path: ^/, roles: ROLE_USER }
111+
- { path: '^/login', roles: IS_AUTHENTICATED_ANONYMOUSLY }
112+
- { path: '^/api', roles: ROLE_API_USER }
113+
- { path: '^/', roles: ROLE_USER }
114114
115115
.. code-block:: xml
116116
@@ -168,8 +168,8 @@ the solution is to split the configuration into two separate firewalls:
168168
],
169169
],
170170
'access_control' => [
171-
['path' => '^/login', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
172-
['path' => '^/api', 'role' => 'ROLE_API_USER'],
173-
['path' => '^/', 'role' => 'ROLE_USER'],
171+
['path' => '^/login', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
172+
['path' => '^/api', 'roles' => 'ROLE_API_USER'],
173+
['path' => '^/', 'roles' => 'ROLE_USER'],
174174
],
175175
]);

security/voters.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
.. index::
22
single: Security; Data Permission Voters
33

4+
.. _security/custom-voter:
5+
46
How to Use Voters to Check User Permissions
57
===========================================
68

@@ -19,7 +21,8 @@ How Symfony Uses Voters
1921
In order to use voters, you have to understand how Symfony works with them.
2022
All voters are called each time you use the ``isGranted()`` method on Symfony's
2123
authorization checker or call ``denyAccessUnlessGranted`` in a controller (which
22-
uses the authorization checker).
24+
uses the authorization checker), or by
25+
:ref:`access controls <security-access-control-enforcement-options>`.
2326

2427
Ultimately, Symfony takes the responses from all voters and makes the final
2528
decision (to allow or deny access to the resource) according to the strategy defined

0 commit comments

Comments
 (0)