Skip to content

Commit

Permalink
bug symfony#6495 Revert "minor symfony#6478 Replace reference to the …
Browse files Browse the repository at this point in the history
…"request" service (gerryvdm)" (WouterJ)

This PR was merged into the 2.7 branch.

Discussion
----------

Revert "minor symfony#6478 Replace reference to the "request" service (gerryvdm)"

This reverts commit 139e2e7, reversing
changes made to 351f796.

Similair to symfony#6490 but then for 2.7

Commits
-------

8da5a30 Revert "minor symfony#6478 Replace reference to the "request" service (gerryvdm)"
  • Loading branch information
wouterj committed Apr 20, 2016
2 parents f5bcba6 + 8da5a30 commit dd1ca33
Show file tree
Hide file tree
Showing 135 changed files with 9,915 additions and 1,811 deletions.
4 changes: 2 additions & 2 deletions best_practices/forms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ form in its own PHP class::

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class PostType extends AbstractType
{
Expand All @@ -36,7 +36,7 @@ form in its own PHP class::
;
}

public function setDefaultOptions(OptionsResolverInterface $resolver)
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Post'
Expand Down
176 changes: 159 additions & 17 deletions best_practices/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,16 @@ Authorization (i.e. Denying Access)
-----------------------------------

Symfony gives you several ways to enforce authorization, including the ``access_control``
configuration in :doc:`security.yml </reference/configuration/security>` and
using :ref:`isGranted <best-practices-directly-isGranted>` on the ``security.context``
configuration in :doc:`security.yml </reference/configuration/security>`, the
:ref:`@Security annotation <best-practices-security-annotation>` and using
:ref:`isGranted <best-practices-directly-isGranted>` on the ``security.authorization_checker``
service directly.

.. best-practice::

* For protecting broad URL patterns, use ``access_control``;
* Check security directly on the ``security.context`` service whenever
* Whenever possible, use the ``@Security`` annotation;
* Check security directly on the ``security.authorization_checker`` service whenever
you have a more complex situation.

There are also different ways to centralize your authorization logic, like
Expand All @@ -93,21 +95,132 @@ with a custom security voter or with ACL.
* For restricting access to *any* object by *any* user via an admin
interface, use the Symfony ACL.

.. _best-practices-directly-isGranted:
.. _checking-permissions-without-security:
.. _best-practices-security-annotation:

The @Security Annotation
------------------------

Manually Checking Permissions
-----------------------------
For controlling access on a controller-by-controller basis, use the ``@Security``
annotation whenever possible. It's easy to read and is placed consistently
above each action.

If you cannot control the access based on URL patterns, you can always do
the security checks in PHP:
In our application, you need the ``ROLE_ADMIN`` in order to create a new post.
Using ``@Security``, this looks like:

.. code-block:: php
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
// ...
/**
* Displays a form to create a new Post entity.
*
* @Route("/new", name="admin_post_new")
* @Security("has_role('ROLE_ADMIN')")
*/
public function newAction()
{
// ...
}
Using Expressions for Complex Security Restrictions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If your security logic is a little bit more complex, you can use an `expression`_
inside ``@Security``. In the following example, a user can only access the
controller if their email matches the value returned by the ``getAuthorEmail``
method on the ``Post`` object:

.. code-block:: php
use AppBundle\Entity\Post;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
/**
* @Route("/{id}/edit", name="admin_post_edit")
* @Security("user.getEmail() == post.getAuthorEmail()")
*/
public function editAction(Post $post)
{
// ...
}
Notice that this requires the use of the `ParamConverter`_, which automatically
queries for the ``Post`` object and puts it on the ``$post`` argument. This
is what makes it possible to use the ``post`` variable in the expression.

This has one major drawback: an expression in an annotation cannot easily
be reused in other parts of the application. Imagine that you want to add
a link in a template that will only be seen by authors. Right now you'll
need to repeat the expression code using Twig syntax:

.. code-block:: html+jinja

{% if app.user and app.user.email == post.authorEmail %}
<a href=""> ... </a>
{% endif %}

The easiest solution - if your logic is simple enough - is to add a new method
to the ``Post`` entity that checks if a given user is its author:

.. code-block:: php
// src/AppBundle/Entity/Post.php
// ...
class Post
{
// ...
/**
* Is the given User the author of this Post?
*
* @return bool
*/
public function isAuthor(User $user = null)
{
return $user && $user->getEmail() == $this->getAuthorEmail();
}
}
Now you can reuse this method both in the template and in the security expression:

.. code-block:: php
use AppBundle\Entity\Post;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
/**
* @Route("/{id}/edit", name="admin_post_edit")
* @Security("post.isAuthor(user)")
*/
public function editAction(Post $post)
{
// ...
}
.. code-block:: html+jinja

{% if post.isAuthor(app.user) %}
<a href=""> ... </a>
{% endif %}

.. _best-practices-directly-isGranted:
.. _checking-permissions-without-security:
.. _manually-checking-permissions:

Checking Permissions without @Security
--------------------------------------

The above example with ``@Security`` only works because we're using the
:ref:`ParamConverter <best-practices-paramconverter>`, which gives the expression
access to the a ``post`` variable. If you don't use this, or have some other
more advanced use-case, you can always do the same security check in PHP:

.. code-block:: php
/**
* @Route("/{id}/edit", name="admin_post_edit")
*/
Expand All @@ -121,7 +234,16 @@ the security checks in PHP:
}
if (!$post->isAuthor($this->getUser())) {
throw new AccessDeniedException();
$this->denyAccessUnlessGranted('edit', $post);
// or without the shortcut:
//
// use Symfony\Component\Security\Core\Exception\AccessDeniedException;
// ...
//
// if (!$this->get('security.authorization_checker')->isGranted('edit', $post)) {
// throw $this->createAccessDeniedException();
// }
}
// ...
Expand Down Expand Up @@ -192,13 +314,23 @@ To enable the security voter in the application, define a new service:
tags:
- { name: security.voter }
Now, you can use the voter with the ``security.context`` service:
Now, you can use the voter with the ``@Security`` annotation:

.. code-block:: php
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
/**
* @Route("/{id}/edit", name="admin_post_edit")
* @Security("is_granted('edit', post)")
*/
public function editAction(Post $post)
{
// ...
}
// ...
You can also use this directly with the ``security.authorization_checker`` service or
via the even easier shortcut in a controller:

.. code-block:: php
/**
* @Route("/{id}/edit", name="admin_post_edit")
Expand All @@ -207,9 +339,16 @@ Now, you can use the voter with the ``security.context`` service:
{
$post = // query for the post ...
if (!$this->get('security.context')->isGranted('edit', $post)) {
throw new AccessDeniedException();
}
$this->denyAccessUnlessGranted('edit', $post);
// or without the shortcut:
//
// use Symfony\Component\Security\Core\Exception\AccessDeniedException;
// ...
//
// if (!$this->get('security.authorization_checker')->isGranted('edit', $post)) {
// throw $this->createAccessDeniedException();
// }
}
Learn More
Expand All @@ -230,4 +369,7 @@ If your company uses a user login method not supported by Symfony, you can
develop :doc:`your own user provider </cookbook/security/custom_provider>` and
:doc:`your own authentication provider </cookbook/security/custom_authentication_provider>`.

.. _`ParamConverter`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
.. _`@Security annotation`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/security.html
.. _`expression`: http://symfony.com/doc/current/components/expression_language/introduction.html
.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle
75 changes: 47 additions & 28 deletions book/controller.rst
Original file line number Diff line number Diff line change
Expand Up @@ -421,22 +421,26 @@ method is just a helper method that generates the URL for a given route.
Redirecting
~~~~~~~~~~~

To redirect the user's browser to another page of your app, use the ``generateUrl()``
method in combination with another helper method called
:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::redirect`
which takes a URL as an argument::
If you want to redirect the user to another page, use the ``redirectToRoute()`` method::

public function indexAction()
{
return $this->redirect($this->generateUrl('homepage'));
return $this->redirectToRoute('homepage');

// redirectToRoute is equivalent to using redirect() and generateUrl() together:
// return $this->redirect($this->generateUrl('homepage'));
}

By default, the ``redirect()`` method performs a 302 (temporary) redirect. To
perform a 301 (permanent) redirect, modify the second argument::
.. versionadded:: 2.6
The ``redirectToRoute()`` method was introduced in Symfony 2.6. Previously (and still now), you
could use ``redirect()`` and ``generateUrl()`` together for this (see the example above).

By default, the ``redirectToRoute()`` method performs a 302 (temporary) redirect. To
perform a 301 (permanent) redirect, modify the third argument::

public function indexAction()
{
return $this->redirect($this->generateUrl('homepage'), 301);
return $this->redirectToRoute('homepage', array(), 301);
}

To redirect to an *external* site, use ``redirect()`` and pass it the external URL::
Expand All @@ -450,12 +454,16 @@ For more information, see the :doc:`Routing chapter </book/routing>`.

.. tip::

The ``redirect()`` method is simply a shortcut that creates a ``Response``
object that specializes in redirecting the user. It's equivalent to::
The ``redirectToRoute()`` method is simply a shortcut that creates a
``Response`` object that specializes in redirecting the user. It's
equivalent to::

use Symfony\Component\HttpFoundation\RedirectResponse;

return new RedirectResponse($this->generateUrl('homepage'));
public function indexAction()
{
return new RedirectResponse($this->generateUrl('homepage'));
}

.. index::
single: Controller; Rendering templates
Expand Down Expand Up @@ -520,12 +528,15 @@ need::

$mailer = $this->get('mailer');

What other services exist? To list all services, use the ``container:debug``
What other services exist? To list all services, use the ``debug:container``
console command:

.. code-block:: bash
$ php app/console container:debug
$ php app/console debug:container
.. versionadded:: 2.6
Prior to Symfony 2.6, this command was called ``container:debug``.

For more information, see the :doc:`/book/service_container` chapter.

Expand Down Expand Up @@ -649,12 +660,14 @@ For example, imagine you're processing a form submission::
if ($form->isValid()) {
// do some sort of processing

$request->getSession()->getFlashBag()->add(
$this->addFlash(
'notice',
'Your changes were saved!'
);

return $this->redirect($this->generateUrl(...));
// $this->addFlash is equivalent to $this->get('session')->getFlashBag()->add

return $this->redirectToRoute(...);
}

return $this->render(...);
Expand Down Expand Up @@ -750,7 +763,7 @@ headers and content that's sent back to the client::
use Symfony\Component\HttpFoundation\Response;

// create a simple Response with a 200 status code (the default)
$response = new Response('Hello '.$name, 200);
$response = new Response('Hello '.$name, Response::HTTP_OK);

// create a JSON-response with a 200 status code
$response = new Response(json_encode(array('name' => $name)));
Expand Down Expand Up @@ -816,24 +829,30 @@ The target controller method might look something like this::
Just like when creating a controller for a route, the order of the arguments of
``fancyAction()`` doesn't matter: the matching is done by name.

Checking the Validity of a CSRF Token inside Controller
-------------------------------------------------------
.. _checking-the-validity-of-a-csrf-token:

You may sometimes want to use :ref:`CSRF protection <forms-csrf>` in a controller where
you don't have a Symfony form.
Validating a CSRF Token
-----------------------

If, for example, you're doing a DELETE action, you can use the
:method:`Symfony\\Component\\Form\\Extension\\Csrf\\CsrfProvider\\CsrfProviderInterface::isCsrfTokenValid`
Sometimes, you want to use CSRF protection in an action where you don't want to
use the Symfony Form component. If, for example, you're doing a DELETE action,
you can use the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::isCsrfTokenValid`
method to check the CSRF token::

$csrf = $this->container->get('form.csrf_provider');
if ($this->isCsrfTokenValid('token_id', $submittedToken)) {
// ... do something, like deleting an object
}

$intention = 'authenticate';
$token = $csrf->generateCsrfToken($intention);
.. versionadded:: 2.6
The ``isCsrfTokenValid()`` shortcut method was introduced in Symfony 2.6.
It is equivalent to executing the following code:

if (!$csrf->isCsrfTokenValid($intention, $token)) {
// CSRF token invalid! Do something, like redirect with an error.
}
.. code-block:: php
use Symfony\Component\Security\Csrf\CsrfToken;
$this->get('security.csrf.token_manager')
->isTokenValid(new CsrfToken('token_id', 'TOKEN'));
Final Thoughts
--------------
Expand Down
Loading

0 comments on commit dd1ca33

Please sign in to comment.