Skip to content

Commit 3638360

Browse files
committed
Merge branch '4.3' into 4.4
* 4.3: Improved documentation about access controls
2 parents 518caf8 + 067e44e commit 3638360

File tree

6 files changed

+115
-49
lines changed

6 files changed

+115
-49
lines changed

security.rst

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ start with ``/admin``, you can:
435435
436436
access_control:
437437
# require ROLE_ADMIN for /admin*
438-
- { path: ^/admin, roles: ROLE_ADMIN }
438+
- { path: '^/admin', roles: ROLE_ADMIN }
439439
440440
# the 'path' value can be any valid regular expression
441441
# (this one will match URLs like /api/post/7298 and /api/comment/528491)
@@ -481,11 +481,11 @@ start with ``/admin``, you can:
481481
],
482482
'access_control' => [
483483
// require ROLE_ADMIN for /admin*
484-
['path' => '^/admin', 'role' => 'ROLE_ADMIN'],
484+
['path' => '^/admin', 'roles' => 'ROLE_ADMIN'],
485485
486486
// the 'path' value can be any valid regular expression
487487
// (this one will match URLs like /api/post/7298 and /api/comment/528491)
488-
['path' => '^/api/(post|comment)/\d+$', 'role' => 'ROLE_USER'],
488+
['path' => '^/api/(post|comment)/\d+$', 'roles' => 'ROLE_USER'],
489489
],
490490
]);
491491
@@ -503,10 +503,10 @@ the list and stops when it finds the first match:
503503
504504
access_control:
505505
# matches /admin/users/*
506-
- { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
506+
- { path: '^/admin/users', roles: ROLE_SUPER_ADMIN }
507507
508508
# matches /admin/* except for anything matching the above rule
509-
- { path: ^/admin, roles: ROLE_ADMIN }
509+
- { path: '^/admin', roles: ROLE_ADMIN }
510510
511511
.. code-block:: xml
512512
@@ -533,8 +533,8 @@ the list and stops when it finds the first match:
533533
// ...
534534
535535
'access_control' => [
536-
['path' => '^/admin/users', 'role' => 'ROLE_SUPER_ADMIN'],
537-
['path' => '^/admin', 'role' => 'ROLE_ADMIN'],
536+
['path' => '^/admin/users', 'roles' => 'ROLE_SUPER_ADMIN'],
537+
['path' => '^/admin', 'roles' => 'ROLE_ADMIN'],
538538
],
539539
]);
540540

security/access_control.rst

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ for each ``access_control`` entry, which determines whether or not a given
2424
access control should be used on this request. The following ``access_control``
2525
options are used for matching:
2626

27-
* ``path``
28-
* ``ip`` or ``ips`` (netmasks are also supported)
29-
* ``port``
30-
* ``host``
31-
* ``methods``
27+
* ``path``: a regular expression (without delimiters)
28+
* ``ip`` or ``ips``: netmasks are also supported
29+
* ``port``: an integer
30+
* ``host``: a regular expression
31+
* ``methods``: one or many methods
3232

3333
Take the following ``access_control`` entries as an example:
3434

@@ -40,11 +40,12 @@ Take the following ``access_control`` entries as an example:
4040
security:
4141
# ...
4242
access_control:
43-
- { path: ^/admin, roles: ROLE_USER_IP, ip: 127.0.0.1 }
44-
- { path: ^/admin, roles: ROLE_USER_PORT, ip: 127.0.0.1, port: 8080 }
45-
- { path: ^/admin, roles: ROLE_USER_HOST, host: symfony\.com$ }
46-
- { path: ^/admin, roles: ROLE_USER_METHOD, methods: [POST, PUT] }
47-
- { path: ^/admin, roles: ROLE_USER }
43+
- { path: '^/admin', roles: ROLE_USER_IP, ip: 127.0.0.1 }
44+
- { path: '^/admin', roles: ROLE_USER_PORT, ip: 127.0.0.1, port: 8080 }
45+
- { path: '^/admin', roles: ROLE_USER_HOST, host: symfony\.com$ }
46+
- { path: '^/admin', roles: ROLE_USER_METHOD, methods: [POST, PUT] }
47+
# when defining multiple roles, users must have at least one of them (it's like an OR condition)
48+
- { path: '^/admin', roles: [ROLE_MANAGER, ROLE_ADMIN] }
4849
4950
.. code-block:: xml
5051
@@ -62,7 +63,8 @@ Take the following ``access_control`` entries as an example:
6263
<rule path="^/admin" role="ROLE_USER_PORT" ip="127.0.0.1" port="8080"/>
6364
<rule path="^/admin" role="ROLE_USER_HOST" host="symfony\.com$"/>
6465
<rule path="^/admin" role="ROLE_USER_METHOD" methods="POST, PUT"/>
65-
<rule path="^/admin" role="ROLE_USER"/>
66+
<!-- when defining multiple roles, users must have at least one of them (it's like an OR condition) -->
67+
<rule path="^/admin" roles="ROLE_ADMIN, ROLE_MANAGER" />
6668
</config>
6769
</srv:container>
6870
@@ -74,28 +76,29 @@ Take the following ``access_control`` entries as an example:
7476
'access_control' => [
7577
[
7678
'path' => '^/admin',
77-
'role' => 'ROLE_USER_IP',
78-
'ip' => '127.0.0.1',
79+
'roles' => 'ROLE_USER_IP',
80+
'ips' => '127.0.0.1',
7981
],
8082
[
8183
'path' => '^/admin',
82-
'role' => 'ROLE_USER_PORT',
84+
'roles' => 'ROLE_USER_PORT',
8385
'ip' => '127.0.0.1',
8486
'port' => '8080',
8587
],
8688
[
8789
'path' => '^/admin',
88-
'role' => 'ROLE_USER_HOST',
90+
'rolse' => 'ROLE_USER_HOST',
8991
'host' => 'symfony\.com$',
9092
],
9193
[
9294
'path' => '^/admin',
93-
'role' => 'ROLE_USER_METHOD',
95+
'roles' => 'ROLE_USER_METHOD',
9496
'methods' => 'POST, PUT',
9597
],
9698
[
9799
'path' => '^/admin',
98-
'role' => 'ROLE_USER',
100+
// when defining multiple roles, users must have at least one of them (it's like an OR condition)
101+
'roles' => ['ROLE_MANAGER', 'ROLE_ADMIN'],
99102
],
100103
],
101104
]);
@@ -127,9 +130,10 @@ if ``ip``, ``port``, ``host`` or ``method`` are not specified for an entry, that
127130
| ``/admin/user`` | 168.0.0.1 | 80 | example.com | POST | rule #4 (``ROLE_USER_METHOD``) | The ``ip`` and ``host`` don't match the first two entries, |
128131
| | | | | | | but the third - ``ROLE_USER_METHOD`` - matches and is used. |
129132
+-----------------+-------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
130-
| ``/admin/user`` | 168.0.0.1 | 80 | example.com | GET | rule #5 (``ROLE_USER``) | The ``ip``, ``host`` and ``method`` prevent the first |
133+
| ``/admin/user`` | 168.0.0.1 | 80 | example.com | GET | rule #4 (``ROLE_MANAGER``) | The ``ip``, ``host`` and ``method`` prevent the first |
131134
| | | | | | | three entries from matching. But since the URI matches the |
132-
| | | | | | | ``path`` pattern of the ``ROLE_USER`` entry, it is used. |
135+
| | | | | | | ``path`` pattern, then the ``ROLE_MANAGER`` (or the |
136+
| | | | | | | ``ROLE_ADMIN``) is used. |
133137
+-----------------+-------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
134138
| ``/foo`` | 127.0.0.1 | 80 | symfony.com | POST | matches no entries | This doesn't match any ``access_control`` rules, since its |
135139
| | | | | | | URI doesn't match any of the ``path`` values. |
@@ -155,6 +159,14 @@ options:
155159
does not match this value (e.g. ``https``), the user will be redirected
156160
(e.g. redirected from ``http`` to ``https``, or vice versa).
157161

162+
.. tip::
163+
164+
Behind the scenes, the array value of ``roles`` is passed as the
165+
``$attributes`` argument to each voter in the application with the
166+
:class:`Symfony\\Component\\HttpFoundation\\Request` as ``$subject``. You
167+
can learn how to use your custom attributes by reading
168+
:ref:`security/custom-voter`.
169+
158170
.. tip::
159171

160172
If access is denied, the system will try to authenticate the user if not
@@ -191,8 +203,8 @@ pattern so that it is only accessible by requests from the local server itself:
191203
access_control:
192204
#
193205
# the 'ips' option supports IP addresses and subnet masks
194-
- { path: ^/internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, ::1, 192.168.0.1/24] }
195-
- { path: ^/internal, roles: ROLE_NO_ACCESS }
206+
- { path: '^/internal', roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, ::1, 192.168.0.1/24] }
207+
- { path: '^/internal', roles: ROLE_NO_ACCESS }
196208
197209
.. code-block:: xml
198210
@@ -225,13 +237,13 @@ pattern so that it is only accessible by requests from the local server itself:
225237
'access_control' => [
226238
[
227239
'path' => '^/internal',
228-
'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
240+
'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY',
229241
// the 'ips' option supports IP addresses and subnet masks
230242
'ips' => ['127.0.0.1', '::1'],
231243
],
232244
[
233245
'path' => '^/internal',
234-
'role' => 'ROLE_NO_ACCESS',
246+
'roles' => 'ROLE_NO_ACCESS',
235247
],
236248
],
237249
]);
@@ -276,7 +288,10 @@ key:
276288
access_control:
277289
-
278290
path: ^/_internal/secure
279-
allow_if: "'127.0.0.1' == request.getClientIp() or is_granted('ROLE_ADMIN')"
291+
# the 'role' and 'allow-if' options work like an OR expression, so
292+
# access is granted if the expression is TRUE or the user has ROLE_ADMIN
293+
roles: 'ROLE_ADMIN'
294+
allow_if: "'127.0.0.1' == request.getClientIp() or request.header.has('X-Secure-Access')"
280295
281296
.. code-block:: xml
282297
@@ -290,8 +305,11 @@ key:
290305
291306
<config>
292307
<!-- ... -->
308+
<!-- the 'role' and 'allow-if' options work like an OR expression, so
309+
access is granted if the expression is TRUE or the user has ROLE_ADMIN -->
293310
<rule path="^/_internal/secure"
294-
allow-if="'127.0.0.1' == request.getClientIp() or is_granted('ROLE_ADMIN')"/>
311+
role="ROLE_ADMIN"
312+
allow-if="'127.0.0.1' == request.getClientIp() or request.header.has('X-Secure-Access')" />
295313
</config>
296314
</srv:container>
297315
@@ -303,14 +321,23 @@ key:
303321
'access_control' => [
304322
[
305323
'path' => '^/_internal/secure',
306-
'allow_if' => '"127.0.0.1" == request.getClientIp() or is_granted("ROLE_ADMIN")',
324+
// the 'role' and 'allow-if' options work like an OR expression, so
325+
// access is granted if the expression is TRUE or the user has ROLE_ADMIN
326+
'roles' => 'ROLE_ADMIN',
327+
'allow_if' => '"127.0.0.1" == request.getClientIp() or request.header.has('X-Secure-Access')',
307328
],
308329
],
309330
]);
310331
311-
In this case, when the user tries to access any URL starting with ``/_internal/secure``,
312-
they will only be granted access if the IP address is ``127.0.0.1`` or if
313-
the user has the ``ROLE_ADMIN`` role.
332+
In this case, when the user tries to access any URL starting with
333+
``/_internal/secure``, they will only be granted access if the IP address is
334+
``127.0.0.1`` or a secure header, or if the user has the ``ROLE_ADMIN`` role.
335+
336+
.. note::
337+
338+
Internally ``allow_if`` triggers the built-in
339+
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\ExpressionVoter`
340+
as like it was part of the attributes defined in the ``roles`` option.
314341

315342
Inside the expression, you have access to a number of different variables
316343
and functions including ``request``, which is the Symfony
@@ -420,7 +447,7 @@ the user will be redirected to ``https``:
420447
'access_control' => [
421448
[
422449
'path' => '^/cart/checkout',
423-
'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
450+
'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY',
424451
'requires_channel' => 'https',
425452
],
426453
],

security/force_https.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ access control:
2323
# ...
2424
2525
access_control:
26-
- { path: ^/secure, roles: ROLE_ADMIN, requires_channel: https }
27-
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
26+
- { path: '^/secure', roles: ROLE_ADMIN, requires_channel: https }
27+
- { path: '^/login', roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
2828
# catch all other URLs
29-
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
29+
- { path: '^/', roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
3030
3131
.. code-block:: xml
3232
@@ -62,7 +62,7 @@ access control:
6262
'access_control' => [
6363
[
6464
'path' => '^/secure',
65-
'role' => 'ROLE_ADMIN',
65+
'roles' => 'ROLE_ADMIN',
6666
'requires_channel' => 'https',
6767
],
6868
[

security/form_login_setup.rst

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ and your generated code may be slightly different:
3131
3232
Choose a name for the controller class (e.g. SecurityController) [SecurityController]:
3333
> SecurityController
34-
34+
3535
Do you want to generate a '/logout' URL? (yes/no) [yes]:
3636
> yes
3737
@@ -424,3 +424,39 @@ deal with this low level session variable. However, the
424424
can be used to read (like in the example above) or set this value manually.
425425

426426
.. _`MakerBundle`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html
427+
=======
428+
// ...
429+
'access_control' => [
430+
['path' => '^/login', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
431+
['path' => '^/', 'roles' => 'ROLE_ADMIN'],
432+
],
433+
434+
3. Be Sure check_path Is Behind a Firewall
435+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
436+
437+
Next, make sure that your ``check_path`` URL (e.g. ``/login``) is behind
438+
the firewall you're using for your form login (in this example, the single
439+
firewall matches *all* URLs, including ``/login``). If ``/login``
440+
doesn't match any firewall, you'll receive a ``Unable to find the controller
441+
for path "/login"`` exception.
442+
443+
4. Multiple Firewalls Don't Share the Same Security Context
444+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
445+
446+
If you're using multiple firewalls and you authenticate against one firewall,
447+
you will *not* be authenticated against any other firewalls automatically.
448+
Different firewalls are like different security systems. To do this you have
449+
to explicitly specify the same :ref:`reference-security-firewall-context`
450+
for different firewalls. But usually for most applications, having one
451+
main firewall is enough.
452+
453+
5. Routing Error Pages Are not Covered by Firewalls
454+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
455+
456+
As routing is done *before* security, 404 error pages are not covered by
457+
any firewall. This means you can't check for security or even access the
458+
user object on these pages. See :doc:`/controller/error_pages`
459+
for more details.
460+
461+
.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle
462+
>>>>>>> 3.4

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
- App\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)