@@ -696,6 +696,12 @@ URL pattern. You've seen this already in the first example of this chapter,
696
696
where anything matching the regular expression pattern ``^/admin `` requires
697
697
the ``ROLE_ADMIN `` role.
698
698
699
+ .. caution ::
700
+
701
+ Understanding exactly how ``access_control `` works is **very ** important
702
+ to make sure you application is properly secured. See :ref: `security-book-access-control-explanation `
703
+ below for detailed information.
704
+
699
705
You can define as many URL patterns as you need - each is a regular expression.
700
706
701
707
.. configuration-block ::
@@ -736,32 +742,136 @@ You can define as many URL patterns as you need - each is a regular expression.
736
742
the ``^ ``) would correctly match ``/admin/foo `` but would also match URLs
737
743
like ``/foo/admin ``.
738
744
739
- For each incoming request, Symfony2 tries to find a matching access control
740
- rule (the first one wins). If the user isn't authenticated yet, the authentication
741
- process is initiated (i.e. the user is given a chance to login). However,
742
- if the user *is * authenticated but doesn't have the required role, an
743
- :class: `Symfony\\ Component\\ Security\\ Core\\ Exception\\ AccessDeniedException `
744
- exception is thrown, which you can handle and turn into a nice "access denied"
745
- error page for the user. See :doc: `/cookbook/controller/error_pages ` for
746
- more information.
745
+ .. _security-book-access-control-explanation :
746
+
747
+ Understanding how ``access_control `` works
748
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
749
+
750
+ For each incoming request, Symfony2 checks each ``access_control `` entry
751
+ to find *one * that matches the current request. As soon as it finds a matching
752
+ ``access_control `` entry, it stops - only the **first ** matching ``access_control ``
753
+ is used to enforce access.
754
+
755
+ Each ``access_control `` has several options that configure two different
756
+ things: (a) :ref: `should the incoming request match this access control entry<security-book-access-control-matching-options> `
757
+ and (b) :ref: `once it matches, should some sort of access restriction be enforced<security-book-access-control-enforcement-options> `:
758
+
759
+ .. _security-book-access-control-matching-options :
760
+
761
+ **(a) Matching Options **
762
+
763
+ Symfony2 creates an instance of :class: `Symfony\\ Component\\ HttpFoundation\\ RequestMatcher `
764
+ for each ``access_control `` entry, which determines whether or not a given
765
+ access control should be used on this request. The following ``access_control ``
766
+ options are used for matching:
767
+
768
+ * ``path ``
769
+ * ``ip ``
770
+ * ``host ``
771
+ * ``methods ``
772
+
773
+ Take the following ``access_control `` entries as an example:
747
774
748
- Since Symfony uses the first access control rule it matches, a URL like ``/admin/users/new ``
749
- will match the first rule and require only the ``ROLE_SUPER_ADMIN `` role.
750
- Any URL like ``/admin/blog `` will match the second rule and require ``ROLE_ADMIN ``.
775
+ .. configuration-block ::
776
+
777
+ .. code-block :: yaml
778
+
779
+ # app/config/security.yml
780
+ security :
781
+ # ...
782
+ access_control :
783
+ - { path: ^/user, roles: ROLE_USER_IP, ip: 127.0.0.1 }
784
+ - { path: ^/user, roles: ROLE_USER_HOST, host: symfony.com }
785
+ - { path: ^/user, roles: ROLE_USER_METHOD, methods: [POST, PUT] }
786
+ - { path: ^/user, roles: ROLE_USER }
787
+
788
+ .. code-block :: xml
789
+
790
+ <access-control >
791
+ <rule path =" ^/user" role =" ROLE_USER_IP" ip =" 127.0.0.1" />
792
+ <rule path =" ^/user" role =" ROLE_USER_HOST" host =" symfony.com" />
793
+ <rule path =" ^/user" role =" ROLE_USER_METHOD" method =" POST, PUT" />
794
+ <rule path =" ^/user" role =" ROLE_USER" />
795
+ </access-control >
796
+
797
+ .. code-block :: php
798
+
799
+ 'access_control' => array(
800
+ array('path' => '^/user', 'role' => 'ROLE_USER_IP', 'ip' => '127.0.0.1'),
801
+ array('path' => '^/user', 'role' => 'ROLE_USER_HOST', 'host' => 'symfony.com'),
802
+ array('path' => '^/user', 'role' => 'ROLE_USER_METHOD', 'method' => 'POST, PUT'),
803
+ array('path' => '^/user', 'role' => 'ROLE_USER'),
804
+ ),
805
+
806
+ For each incoming request, Symfony will decided which ``access_control ``
807
+ to use based on the URI, the client's IP address, the incoming host name,
808
+ and the request method. Remember, the first rule that matches is used, and
809
+ if ``ip ``, ``host `` or ``method `` are not specified for an entry, that ``access_control ``
810
+ will match any ``ip ``, ``host `` or ``method ``:
811
+
812
+ +-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
813
+ | **URI ** | **IP ** | **HOST ** | **METHOD ** | ``access_control `` | Why? |
814
+ +-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
815
+ | ``/admin/user `` | 127.0.0.1 | example.com | GET | rule #1 (``ROLE_USER_IP ``) | The URI matches ``path `` and the IP matches ``ip ``. |
816
+ +-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
817
+ | ``/admin/user `` | 127.0.0.1 | symfony.com | GET | rule #1 (``ROLE_USER_IP ``) | The ``path `` and ``ip `` still match. This would also match |
818
+ | | | | | | the ``ROLE_USER_HOST `` entry, but *only * the **first ** |
819
+ | | | | | | ``access_control `` match is used. |
820
+ +-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
821
+ | ``/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 |
822
+ | | | | | | rule (which matches) is used. |
823
+ +-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
824
+ | ``/admin/user `` | 168.0.0.1 | symfony.com | POST | rule #2 (``ROLE_USER_HOST ``) | The second rule still matches. This would also match the |
825
+ | | | | | | third rule (``ROLE_USER_METHOD ``), but only the **first ** |
826
+ | | | | | | matched ``access_control `` is used. |
827
+ +-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
828
+ | ``/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, |
829
+ | | | | | | but the third - ``ROLE_USER_METHOD `` - matches and is used. |
830
+ +-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
831
+ | ``/admin/user `` | 168.0.0.1 | example.com | GET | rule #4 (``ROLE_USER ``) | The ``ip ``, ``host `` and ``method `` prevent the first |
832
+ | | | | | | three entries from matching. But since the URI matches the |
833
+ | | | | | | ``path `` pattern of the ``ROLE_USER `` entry, it is used. |
834
+ +-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
835
+ | ``/foo `` | 127.0.0.1 | symfony.com | POST | matches no entries | This doesn't match any ``access_control `` rules, since its |
836
+ | | | | | | URI doesn't match any of the ``path `` values. |
837
+ +-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
838
+
839
+ .. _security-book-access-control-enforcement-options :
840
+
841
+ **(b) Access Enforcement **
842
+
843
+ Once Symfony2 has decided which ``access_control `` entry matches (if any),
844
+ it then *enforces * access restrictions based on the ``roles `` and ``requires_channel ``
845
+ options:
846
+
847
+ * ``role `` If the user does not have the given role(s), then access is denied
848
+ (internally, an :class: `Symfony\\ Component\\ Security\\ Core\\ Exception\\ AccessDeniedException `
849
+ is thrown);
850
+
851
+ * ``requires_channel `` If the incoming request's channel (e.g. ``http ``)
852
+ does not match this value (e.g. ``https ``), the user will be redirected
853
+ (e.g. redirected from ``http `` to ``https ``, or vice versa).
854
+
855
+ .. tip ::
856
+
857
+ If access is denied, the system will try to authenticate the user if not
858
+ already (e.g. redirect the user to the login page). If the user is already
859
+ logged in, the 403 "access denied" error page will be shown. See
860
+ :doc: `/cookbook/controller/error_pages ` for more information.
751
861
752
862
.. _book-security-securing-ip :
753
863
754
864
Securing by IP
755
865
~~~~~~~~~~~~~~
756
866
757
867
Certain situations may arise when you may need to restrict access to a given
758
- route based on IP. This is particularly relevant in the case of
868
+ path based on IP. This is particularly relevant in the case of
759
869
:ref: `Edge Side Includes<edge-side-includes> ` (ESI), for example. When ESI is
760
870
enabled, it's recommended to secure access to ESI URLs. Indeed, some ESI may
761
- contain some private contents like the current logged in user's information. To
762
- prevent any direct access to these resources from a web browser by guessing the
763
- URL pattern, the ESI route must be secured to be only visible from the trusted
764
- reverse proxy cache.
871
+ contain some private content like the current logged in user's information. To
872
+ prevent any direct access to these resources from a web browser ( by guessing the
873
+ ESI URL pattern) , the ESI route ** must ** be secured to be only visible from
874
+ the trusted reverse proxy cache.
765
875
766
876
Here is an example of how you might secure all ESI routes that start with a
767
877
given prefix, ``/esi ``, from outside access:
@@ -775,28 +885,51 @@ given prefix, ``/esi``, from outside access:
775
885
# ...
776
886
access_control :
777
887
- { path: ^/esi, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }
888
+ - { path: ^/esi, roles: ROLE_NO_ACCESS }
778
889
779
890
.. code-block :: xml
780
891
781
892
<access-control >
782
893
<rule path =" ^/esi" role =" IS_AUTHENTICATED_ANONYMOUSLY" ip =" 127.0.0.1" />
894
+ <rule path =" ^/esi" role =" ROLE_NO_ACCESS" />
783
895
</access-control >
784
896
785
897
.. code-block :: php
786
898
787
899
'access_control' => array(
788
900
array('path' => '^/esi', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'ip' => '127.0.0.1'),
901
+ array('path' => '^/esi', 'role' => 'ROLE_NO_ACCESS'),
789
902
),
790
903
791
- .. _book-security-securing-channel :
904
+ Here is how it works when the path is ``/esi/something `` coming from the
905
+ ``10.0.0.1 `` IP:
906
+
907
+ * The first access control rule does not match and is ignored as the ``path ``
908
+ matches but the ``ip `` does not;
909
+
910
+ * The second access control rule matches (the only restriction being the
911
+ ``path `` and it matches): as the user cannot have the ``ROLE_NO_ACCESS ``
912
+ role as it's not defined, access is denied (the ``ROLE_NO_ACCESS `` role can
913
+ be anything that does not match an existing role, it just serves as a trick
914
+ to always deny access).
915
+
916
+ Now, if the same request comes from ``127.0.0.1 ``:
917
+
918
+ * Now, the first access control rule does match as both the ``path `` and the
919
+ ``ip `` match: access is allowed as the user always has the
920
+ ``IS_AUTHENTICATED_ANONYMOUSLY `` role.
921
+
922
+ * The second access rule is not examined as the first rule matched.
792
923
793
924
.. include :: /book/_security-2012-6431.rst.inc
794
925
926
+ .. _book-security-securing-channel :
927
+
795
928
Securing by Channel
796
929
~~~~~~~~~~~~~~~~~~~
797
930
798
- Much like securing based on IP, requiring the use of SSL is as simple as
799
- adding a new access_control entry :
931
+ You can also require a user to access a URL via SSL; just use the
932
+ `` requires_channel `` argument in any `` access_control `` entries :
800
933
801
934
.. configuration-block ::
802
935
0 commit comments