Skip to content

Commit 682e86f

Browse files
weaverryanfabpot
authored andcommitted
Furthering @fabpot's changes to the access_control explanations in the security chapter
1 parent a178921 commit 682e86f

File tree

1 file changed

+129
-31
lines changed

1 file changed

+129
-31
lines changed

book/security.rst

Lines changed: 129 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -679,18 +679,6 @@ In this section, you'll focus on how to secure different resources (e.g. URLs,
679679
method calls, etc) with different roles. Later, you'll learn more about how
680680
roles are created and assigned to users.
681681

682-
Securing resources is done by defining access rules under the
683-
``access_control`` section of the security configuration. Each rule is
684-
composed of two parts:
685-
686-
* Settings that describe **which** resource to secure (``path``, ``ip``,
687-
``host``, and ``methods``); these settings are used to create an instance of
688-
:class:`Symfony\\Component\\HttpFoundation\\RequestMatcher` that will be
689-
used to match the current Request object;
690-
691-
* Settings that **enforce** access restrictions (``roles`` and
692-
``requires_channel``) when the rule matches the current Request.
693-
694682
Securing Specific URL Patterns
695683
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
696684

@@ -699,6 +687,12 @@ URL pattern. You've seen this already in the first example of this chapter,
699687
where anything matching the regular expression pattern ``^/admin`` requires
700688
the ``ROLE_ADMIN`` role.
701689

690+
.. caution::
691+
692+
Understanding exactly how ``access_control`` works is **very** important
693+
to make sure you application is properly secured. See :ref:`security-book-access-control-explanation`
694+
below for detailed information.
695+
702696
You can define as many URL patterns as you need - each is a regular expression.
703697

704698
.. configuration-block::
@@ -739,32 +733,136 @@ You can define as many URL patterns as you need - each is a regular expression.
739733
the ``^``) would correctly match ``/admin/foo`` but would also match URLs
740734
like ``/foo/admin``.
741735

742-
For each incoming request, Symfony2 tries to find a matching access control
743-
rule (the first one wins). If the user isn't authenticated yet, the authentication
744-
process is initiated (i.e. the user is given a chance to login). However,
745-
if the user *is* authenticated but doesn't have the required role, an
746-
:class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException`
747-
exception is thrown, which you can handle and turn into a nice "access denied"
748-
error page for the user. See :doc:`/cookbook/controller/error_pages` for
749-
more information.
736+
.. _security-book-access-control-explanation:
737+
738+
Understanding how ``access_control`` works
739+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
740+
741+
For each incoming request, Symfony2 checks each ``access_control`` entry
742+
to find *one* that matches the current request. As soon as it finds a matching
743+
``access_control`` entry, it stops - only the **first** matching ``access_control``
744+
is used to enforce access.
745+
746+
Each ``access_control`` has several options that configure two different
747+
things: (a) :ref:`should the incoming request match this access control entry<security-book-access-control-matching-options>`
748+
and (b) :ref:`once it matches, should some sort of access restriction be enforced<security-book-access-control-enforcement-options>`:
749+
750+
.. _security-book-access-control-matching-options:
751+
752+
**(a) Matching Options**
753+
754+
Symfony2 creates an instance of :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher`
755+
for each ``access_control`` entry, which determines whether or not a given
756+
access control should be used on this request. The following ``access_control``
757+
options are used for matching:
758+
759+
* ``path``
760+
* ``ip``
761+
* ``host``
762+
* ``methods``
763+
764+
Take the following ``access_control`` entries as an example:
765+
766+
.. configuration-block::
767+
768+
.. code-block:: yaml
769+
770+
# app/config/security.yml
771+
security:
772+
# ...
773+
access_control:
774+
- { path: ^/user, roles: ROLE_USER_IP, ip: 127.0.0.1 }
775+
- { path: ^/user, roles: ROLE_USER_HOST, host: symfony.com }
776+
- { path: ^/user, roles: ROLE_USER_METHOD, methods: [POST, PUT] }
777+
- { path: ^/user, roles: ROLE_USER }
778+
779+
.. code-block:: xml
780+
781+
<access-control>
782+
<rule path="^/user" role="ROLE_USER_IP" ip="127.0.0.1" />
783+
<rule path="^/user" role="ROLE_USER_HOST" host="symfony.com" />
784+
<rule path="^/user" role="ROLE_USER_METHOD" method="POST, PUT" />
785+
<rule path="^/user" role="ROLE_USER" />
786+
</access-control>
787+
788+
.. code-block:: php
789+
790+
'access_control' => array(
791+
array('path' => '^/user', 'role' => 'ROLE_USER_IP', 'ip' => '127.0.0.1'),
792+
array('path' => '^/user', 'role' => 'ROLE_USER_HOST', 'host' => 'symfony.com'),
793+
array('path' => '^/user', 'role' => 'ROLE_USER_METHOD', 'method' => 'POST, PUT'),
794+
array('path' => '^/user', 'role' => 'ROLE_USER'),
795+
),
796+
797+
For each incoming request, Symfony will decided which ``access_control``
798+
to use based on the URI, the client's IP address, the incoming host name,
799+
and the request method. Remember, the first rule that matches is used, and
800+
if ``ip``, ``host`` or ``method`` are not specified for an entry, that ``access_control``
801+
will match any ``ip``, ``host`` or ``method``:
802+
803+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
804+
| **URI** | **IP** | **HOST** | **METHOD** | ``access_control`` | Why? |
805+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
806+
| ``/admin/user`` | 127.0.0.1 | example.com | GET | rule #1 (``ROLE_USER_IP``) | The URI matches ``path`` and the IP matches ``ip``. |
807+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
808+
| ``/admin/user`` | 127.0.0.1 | symfony.com | GET | rule #1 (``ROLE_USER_IP``) | The ``path`` and ``ip`` still match. This would also match |
809+
| | | | | | the ``ROLE_USER_HOST`` entry, but *only* the **first** |
810+
| | | | | | ``access_control`` match is used. |
811+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
812+
| ``/admin/user`` | 168.0.0.1 | symfony.com | GET | rule #2 (``ROLE_USER_HOST``) | The ``ip`` doesn't match the first rule, so the second |
813+
| | | | | | rule (which matches) is used. |
814+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
815+
| ``/admin/user`` | 168.0.0.1 | symfony.com | POST | rule #2 (``ROLE_USER_HOST``) | The second rule still matches. This would also match the |
816+
| | | | | | third rule (``ROLE_USER_METHOD``), but only the **first** |
817+
| | | | | | matched ``access_control`` is used. |
818+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
819+
| ``/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, |
820+
| | | | | | but the third - ``ROLE_USER_METHOD`` - matches and is used. |
821+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
822+
| ``/admin/user`` | 168.0.0.1 | example.com | GET | rule #4 (``ROLE_USER``) | The ``ip``, ``host`` and ``method`` prevent the first |
823+
| | | | | | three entries from matching. But since the URI matches the |
824+
| | | | | | ``path`` pattern of the ``ROLE_USER`` entry, it is used. |
825+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
826+
| ``/foo`` | 127.0.0.1 | symfony.com | POST | matches no entries | This doesn't match any ``access_control`` rules, since its |
827+
| | | | | | URI doesn't match any of the ``path`` values. |
828+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
829+
830+
.. _security-book-access-control-enforcement-options:
831+
832+
**(b) Access Enforcement**
833+
834+
Once Symfony2 has decided which ``access_control`` entry matches (if any),
835+
it then *enforces* access restrictions based on the ``roles`` and ``requires_channel``
836+
options:
837+
838+
* ``role`` If the user does not have the given role(s), then access is denied
839+
(internally, an :class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException`
840+
is thrown);
841+
842+
* ``requires_channel`` If the incoming request's channel (e.g. ``http``)
843+
does not match this value (e.g. ``https``), the user will be redirected
844+
(e.g. redirected from ``http`` to ``https``, or vice versa).
845+
846+
.. tip::
750847

751-
Since Symfony uses the first access control rule it matches, a URL like ``/admin/users/new``
752-
will match the first rule and require only the ``ROLE_SUPER_ADMIN`` role.
753-
Any URL like ``/admin/blog`` will match the second rule and require ``ROLE_ADMIN``.
848+
If access is denied, the system will try to authenticate the user if not
849+
already (e.g. redirect the user to the login page). If the user is already
850+
logged in, the 403 "access denied" error page will be shown. See
851+
:doc:`/cookbook/controller/error_pages` for more information.
754852

755853
.. _book-security-securing-ip:
756854

757855
Securing by IP
758856
~~~~~~~~~~~~~~
759857

760858
Certain situations may arise when you may need to restrict access to a given
761-
route based on IP. This is particularly relevant in the case of
859+
path based on IP. This is particularly relevant in the case of
762860
:ref:`Edge Side Includes<edge-side-includes>` (ESI), for example. When ESI is
763861
enabled, it's recommended to secure access to ESI URLs. Indeed, some ESI may
764-
contain some private contents like the current logged in user's information. To
765-
prevent any direct access to these resources from a web browser by guessing the
766-
URL pattern, the ESI route must be secured to be only visible from the trusted
767-
reverse proxy cache.
862+
contain some private content like the current logged in user's information. To
863+
prevent any direct access to these resources from a web browser (by guessing the
864+
ESI URL pattern), the ESI route **must** be secured to be only visible from
865+
the trusted reverse proxy cache.
768866

769867
Here is an example of how you might secure all ESI routes that start with a
770868
given prefix, ``/esi``, from outside access:
@@ -814,14 +912,14 @@ Now, if the same request comes from ``127.0.0.1``:
814912

815913
* The second access rule is not examined as the first rule matched.
816914

817-
.. _book-security-securing-channel:
818-
819915
.. include:: /book/_security-2012-6431.rst.inc
820916

917+
.. _book-security-securing-channel:
918+
821919
Securing by Channel
822920
~~~~~~~~~~~~~~~~~~~
823921

824-
You can also restrict access by requiring the use of SSL; just use the
922+
You can also require a user to access a URL via SSL; just use the
825923
``requires_channel`` argument in any ``access_control`` entries:
826924

827925
.. configuration-block::

0 commit comments

Comments
 (0)