diff --git a/book/routing.rst b/book/routing.rst index cf9ad48ec43..f84a5804abe 100644 --- a/book/routing.rst +++ b/book/routing.rst @@ -830,36 +830,36 @@ be accomplished with the following route configuration: class MainController extends Controller { /** - * @Route("/contact") + * @Route("/news") * @Method("GET") */ - public function contactAction() + public function newsAction() { - // ... display contact form + // ... display your news } /** * @Route("/contact") - * @Method("POST") + * @Method({"GET", "POST"}) */ - public function processContactAction() + public function contactFormAction() { - // ... process contact form + // ... display and process a contact form } } .. code-block:: yaml # app/config/routing.yml - contact: - path: /contact - defaults: { _controller: AppBundle:Main:contact } + news: + path: /news + defaults: { _controller: AppBundle:Main:news } methods: [GET] - contact_process: + contact_form: path: /contact - defaults: { _controller: AppBundle:Main:processContact } - methods: [POST] + defaults: { _controller: AppBundle:Main:contactForm } + methods: [GET, POST] .. code-block:: xml @@ -870,12 +870,12 @@ be accomplished with the following route configuration: xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - - AppBundle:Main:contact + + AppBundle:Main:news - - AppBundle:Main:processContact + + AppBundle:Main:contactForm @@ -886,13 +886,13 @@ be accomplished with the following route configuration: use Symfony\Component\Routing\Route; $collection = new RouteCollection(); - $collection->add('contact', new Route('/contact', array( + $collection->add('news', new Route('/news', array( '_controller' => 'AppBundle:Main:contact', ), array(), array(), '', array(), array('GET'))); - $collection->add('contact_process', new Route('/contact', array( - '_controller' => 'AppBundle:Main:processContact', - ), array(), array(), '', array(), array('POST'))); + $collection->add('contact_form', new Route('/contact', array( + '_controller' => 'AppBundle:Main:contactForm', + ), array(), array(), '', array(), array('GET', 'POST'))); return $collection; diff --git a/book/security.rst b/book/security.rst index 4b2a4ca8de6..69290807f69 100644 --- a/book/security.rst +++ b/book/security.rst @@ -994,10 +994,10 @@ other users. Also, as the admin user, you yourself want to be able to edit To accomplish this you have 2 options: -* :doc:`Voters ` allow you to - use business logic (e.g. the user can edit this post because they were - the creator) to determine access. You'll probably want this option - it's - flexible enough to solve the above situation. +* :doc:`Voters ` allow you to use business logic + (e.g. the user can edit this post because they were the creator) to determine + access. You'll probably want this option - it's flexible enough to solve the + above situation. * :doc:`ACLs ` allow you to create a database structure where you can assign *any* arbitrary user *any* access (e.g. EDIT, VIEW) @@ -1378,7 +1378,7 @@ Learn More from the Cookbook * :doc:`Forcing HTTP/HTTPS ` * :doc:`Impersonating a User ` -* :doc:`/cookbook/security/voters_data_permission` +* :doc:`/cookbook/security/voters` * :doc:`Access Control Lists (ACLs) ` * :doc:`/cookbook/security/remember_me` * :doc:`/cookbook/security/multiple_user_providers` diff --git a/components/console/introduction.rst b/components/console/introduction.rst index 37fe38fac0e..4a55cc29dbe 100644 --- a/components/console/introduction.rst +++ b/components/console/introduction.rst @@ -175,22 +175,18 @@ Verbosity Levels The ``VERBOSITY_VERY_VERBOSE`` and ``VERBOSITY_DEBUG`` constants were introduced in version 2.3 -The console has 5 levels of verbosity. These are defined in the +The console has five verbosity levels. These are defined in the :class:`Symfony\\Component\\Console\\Output\\OutputInterface`: -======================================= ================================== -Mode Value -======================================= ================================== -OutputInterface::VERBOSITY_QUIET Do not output any messages -OutputInterface::VERBOSITY_NORMAL The default verbosity level -OutputInterface::VERBOSITY_VERBOSE Increased verbosity of messages -OutputInterface::VERBOSITY_VERY_VERBOSE Informative non essential messages -OutputInterface::VERBOSITY_DEBUG Debug messages -======================================= ================================== - -You can specify the quiet verbosity level with the ``--quiet`` or ``-q`` -option. The ``--verbose`` or ``-v`` option is used when you want an increased -level of verbosity. +=========================================== ================================== ===================== +Value Meaning Console option +=========================================== ================================== ===================== +``OutputInterface::VERBOSITY_QUIET`` Do not output any messages ``-q`` or ``--quiet`` +``OutputInterface::VERBOSITY_NORMAL`` The default verbosity level (none) +``OutputInterface::VERBOSITY_VERBOSE`` Increased verbosity of messages ``-v`` +``OutputInterface::VERBOSITY_VERY_VERBOSE`` Informative non essential messages ``-vv`` +``OutputInterface::VERBOSITY_DEBUG`` Debug messages ``-vvv`` +=========================================== ================================== ===================== .. tip:: diff --git a/cookbook/map.rst.inc b/cookbook/map.rst.inc index 79b7da5d160..8f5803c797e 100644 --- a/cookbook/map.rst.inc +++ b/cookbook/map.rst.inc @@ -177,7 +177,6 @@ * :doc:`Security Authorization (Denying Access) ` * :doc:`/cookbook/security/voters` - * :doc:`/cookbook/security/voters_data_permission` * :doc:`/cookbook/security/acl` * :doc:`/cookbook/security/acl_advanced` * :doc:`/cookbook/security/force_https` diff --git a/cookbook/security/acl.rst b/cookbook/security/acl.rst index bbb2209db7e..5f1618e658e 100644 --- a/cookbook/security/acl.rst +++ b/cookbook/security/acl.rst @@ -14,7 +14,7 @@ the ACL system comes in. Using ACL's isn't trivial, and for simpler use cases, it may be overkill. If your permission logic could be described by just writing some code (e.g. to check if a Blog is owned by the current User), then consider using - :doc:`voters `. A voter is passed the object + :doc:`voters `. A voter is passed the object being voted on, which you can use to make complex decisions and effectively implement your own ACL. Enforcing authorization (e.g. the ``isGranted`` part) will look similar to what you see in this entry, but your voter diff --git a/cookbook/security/index.rst b/cookbook/security/index.rst index 731c5f8045a..c9a478c927a 100644 --- a/cookbook/security/index.rst +++ b/cookbook/security/index.rst @@ -31,7 +31,6 @@ Authorization (Denying Access) :maxdepth: 2 voters - voters_data_permission acl acl_advanced force_https diff --git a/cookbook/security/voter_interface.rst.inc b/cookbook/security/voter_interface.rst.inc deleted file mode 100644 index 1a3cd989e3c..00000000000 --- a/cookbook/security/voter_interface.rst.inc +++ /dev/null @@ -1,24 +0,0 @@ -.. code-block:: php - - interface VoterInterface - { - public function supportsAttribute($attribute); - public function supportsClass($class); - public function vote(TokenInterface $token, $object, array $attributes); - } - -The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsAttribute` -method is used to check if the voter supports the given user attribute (i.e: -a role like ``ROLE_USER``, an ACL ``EDIT``, etc.). - -The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsClass` -method is used to check if the voter supports the class of the object whose -access is being checked. - -The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::vote` -method must implement the business logic that verifies whether or not the -user has access. This method must return one of the following values: - -* ``VoterInterface::ACCESS_GRANTED``: The authorization will be granted by this voter; -* ``VoterInterface::ACCESS_ABSTAIN``: The voter cannot decide if authorization should be granted; -* ``VoterInterface::ACCESS_DENIED``: The authorization will be denied by this voter. diff --git a/cookbook/security/voters.rst b/cookbook/security/voters.rst index df575951f0e..933eb449d47 100644 --- a/cookbook/security/voters.rst +++ b/cookbook/security/voters.rst @@ -1,209 +1,259 @@ .. index:: - single: Security; Voters + single: Security; Data Permission Voters -How to Implement your own Voter to Blacklist IP Addresses -========================================================= +How to Use Voters to Check User Permissions +=========================================== -The Symfony Security component provides several layers to authorize users. -One of the layers is called a "voter". A voter is a dedicated class that checks -if the user has the rights to connect to the application or access a specific -resource/URL. For instance, Symfony provides a layer that checks if the user -is fully authorized or if it has some expected roles. +In Symfony, you can check the permission to access data by using the +:doc:`ACL module `, which is a bit overwhelming +for many applications. A much easier solution is to work with custom voters, +which are like simple conditional statements. -It is sometimes useful to create a custom voter to handle a specific case not -handled by the framework. In this section, you'll learn how to create a voter -that will allow you to blacklist users by their IP. +.. seealso:: + + Voters can also be used in other ways, like, for example, blacklisting IP + addresses from the entire application: :doc:`/cookbook/security/voters`. + +.. tip:: + + Take a look at the + :doc:`authorization ` + chapter for an even deeper understanding on voters. + +How Symfony Uses Voters +----------------------- + +In order to use voters, you have to understand how Symfony works with them. +All voters are called each time you use the ``isGranted()`` method on Symfony's +authorization checker (i.e. the ``security.authorization_checker`` service). Each +one decides if the current user should have access to some resource. + +Ultimately, Symfony uses one of three different approaches on what to do +with the feedback from all voters: affirmative, consensus and unanimous. + +For more information take a look at +:ref:`the section about access decision managers `. The Voter Interface ------------------- -A custom voter must implement -:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, -which requires the following three methods: +A custom voter needs to implement +:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface` +or extend :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter`, +which makes creating a voter even easier. -.. include:: /cookbook/security/voter_interface.rst.inc +.. code-block:: php -In this example, you'll check if the user's IP address matches against a list of -blacklisted addresses and "something" will be the application. If the user's IP is blacklisted, you'll return + abstract class AbstractVoter implements VoterInterface + { + abstract protected function getSupportedClasses(); + abstract protected function getSupportedAttributes(); + abstract protected function isGranted($attribute, $object, $user = null); + } + +In this example, the voter will check if the user has access to a specific +object according to your custom conditions (e.g. they must be the owner of +the object). If the condition fails, you'll return ``VoterInterface::ACCESS_DENIED``, otherwise you'll return -``VoterInterface::ACCESS_ABSTAIN`` as this voter's purpose is only to deny -access, not to grant access. +``VoterInterface::ACCESS_GRANTED``. In case the responsibility for this decision +does not belong to this voter, it will return ``VoterInterface::ACCESS_ABSTAIN``. -Creating a custom Voter ------------------------ +Creating the custom Voter +------------------------- -To blacklist a user based on its IP, you can use the ``request_stack`` service -and compare the IP address against a set of blacklisted IP addresses: +The goal is to create a voter that checks if a user has access to view or +edit a particular object. Here's an example implementation: .. code-block:: php - // src/AppBundle/Security/Authorization/Voter/ClientIpVoter.php + // src/AppBundle/Security/Authorization/Voter/PostVoter.php namespace AppBundle\Security\Authorization\Voter; - use Symfony\Component\HttpFoundation\RequestStack; - use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; - use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; + use Symfony\Component\Security\Core\User\UserInterface; - class ClientIpVoter implements VoterInterface + class PostVoter extends AbstractVoter { - protected $requestStack; - private $blacklistedIp; + const VIEW = 'view'; + const EDIT = 'edit'; - public function __construct(RequestStack $requestStack, array $blacklistedIp = array()) + protected function getSupportedAttributes() { - $this->requestStack = $requestStack; - $this->blacklistedIp = $blacklistedIp; + return array(self::VIEW, self::EDIT); } - public function supportsAttribute($attribute) + protected function getSupportedClasses() { - // you won't check against a user attribute, so return true - return true; + return array('AppBundle\Entity\Post'); } - public function supportsClass($class) + protected function isGranted($attribute, $post, $user = null) { - // your voter supports all type of token classes, so return true - return true; - } - - public function vote(TokenInterface $token, $object, array $attributes) - { - $request = $this->requestStack->getCurrentRequest(); - if (in_array($request->getClientIp(), $this->blacklistedIp)) { - return VoterInterface::ACCESS_DENIED; + // make sure there is a user object (i.e. that the user is logged in) + if (!$user instanceof UserInterface) { + return false; } - return VoterInterface::ACCESS_ABSTAIN; + switch($attribute) { + case self::VIEW: + // the data object could have for example a method isPrivate() + // which checks the Boolean attribute $private + if (!$post->isPrivate()) { + return true; + } + + break; + case self::EDIT: + // we assume that our data object has a method getOwner() to + // get the current owner user entity for this data object + if ($user->getId() === $post->getOwner()->getId()) { + return true; + } + + break; + } + + return false; } } That's it! The voter is done. The next step is to inject the voter into -the security layer. This can be done easily through the service container. +the security layer. -.. tip:: +To recap, here's what's expected from the three abstract methods: - Your implementation of the methods - :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsAttribute` - and :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsClass` - are not being called internally by the framework. Once you have registered your - voter the ``vote()`` method will always be called, regardless of whether - or not these two methods return true. Therefore you need to call those - methods in your implementation of the ``vote()`` method and return ``ACCESS_ABSTAIN`` - if your voter does not support the class or attribute. +:method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedClasses` + It tells Symfony that your voter should be called whenever an object of one + of the given classes is passed to ``isGranted()``. For example, if you return + ``array('AppBundle\Model\Product')``, Symfony will call your voter when a + ``Product`` object is passed to ``isGranted()``. -.. tip:: +:method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedAttributes` + It tells Symfony that your voter should be called whenever one of these + strings is passed as the first argument to ``isGranted()``. For example, if + you return ``array('CREATE', 'READ')``, then Symfony will call your voter + when one of these is passed to ``isGranted()``. + +:method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::isGranted` + It implements the business logic that verifies whether or not a given user is + allowed access to a given attribute (e.g. ``CREATE`` or ``READ``) on a given + object. This method must return a boolean. + +.. note:: - An :doc:`AbstractVoter ` is - provided to cover the common use cases when implementing security voters. + Currently, to use the ``AbstractVoter`` base class, you must be creating a + voter where an object is always passed to ``isGranted()``. Declaring the Voter as a Service -------------------------------- -To inject the voter into the security layer, you must declare it as a service, -and tag it as a ``security.voter``: +To inject the voter into the security layer, you must declare it as a service +and tag it with ``security.voter``: .. configuration-block:: .. code-block:: yaml - # src/Acme/AcmeBundle/Resources/config/services.yml + # src/AppBundle/Resources/config/services.yml services: - security.access.blacklist_voter: - class: AppBundle\Security\Authorization\Voter\ClientIpVoter - arguments: ["@request_stack", [123.123.123.123, 171.171.171.171]] - public: false + security.access.post_voter: + class: AppBundle\Security\Authorization\Voter\PostVoter + public: false tags: - - { name: security.voter } + - { name: security.voter } .. code-block:: xml - - - - - 123.123.123.123 - 171.171.171.171 - - - + + + + + + + + + .. code-block:: php - // src/Acme/AcmeBundle/Resources/config/services.php - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; + // src/AppBundle/Resources/config/services.php + $container + ->register( + 'security.access.post_voter', + 'AppBundle\Security\Authorization\Voter\PostVoter' + ) + ->setPublic(false) + ->addTag('security.voter') + ; - $definition = new Definition( - 'AppBundle\Security\Authorization\Voter\ClientIpVoter', - array( - new Reference('request_stack'), - array('123.123.123.123', '171.171.171.171'), - ), - ); - $definition->addTag('security.voter'); - $definition->setPublic(false); +How to Use the Voter in a Controller +------------------------------------ - $container->setDefinition('security.access.blacklist_voter', $definition); +The registered voter will then always be asked as soon as the method ``isGranted()`` +from the authorization checker is called. -.. tip:: +.. code-block:: php - Be sure to import this configuration file from your main application - configuration file (e.g. ``app/config/config.yml``). For more information - see :ref:`service-container-imports-directive`. To read more about defining - services in general, see the :doc:`/book/service_container` chapter. + // src/AppBundle/Controller/PostController.php + namespace AppBundle\Controller; -.. _security-voters-change-strategy: + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use Symfony\Component\HttpFoundation\Response; -Changing the Access Decision Strategy -------------------------------------- + class PostController extends Controller + { + public function showAction($id) + { + // get a Post instance + $post = ...; -In order for the new voter to take effect, you need to change the default access -decision strategy, which, by default, grants access if *any* voter grants -access. + $authChecker = $this->get('security.authorization_checker'); -In this case, choose the ``unanimous`` strategy. Unlike the ``affirmative`` -strategy (the default), with the ``unanimous`` strategy, if only one voter -denies access (e.g. the ``ClientIpVoter``), access is not granted to the -end user. + if (false === $authChecker->isGranted('view', $post)) { + throw $this->createAccessDeniedException('Unauthorized access!'); + } -To do that, override the default ``access_decision_manager`` section of your -application configuration file with the following code. + return new Response('

'.$post->getName().'

'); + } + } -.. configuration-block:: +.. versionadded:: 2.6 + The ``security.authorization_checker`` service was introduced in Symfony 2.6. + Prior to Symfony 2.6, you had to use the ``isGranted()`` method of the + ``security.context`` service. - .. code-block:: yaml +It's that easy! - # app/config/security.yml - security: - access_decision_manager: - # strategy can be: affirmative, unanimous or consensus - strategy: unanimous +.. _security-voters-change-strategy: - .. code-block:: xml +Changing the Access Decision Strategy +------------------------------------- - - - - - +Imagine you have multiple voters for one action for an object. For instance, +you have one voter that checks if the user is a member of the site and a second +one checking if the user is older than 18. - .. code-block:: php +To handle these cases, the access decision manager uses an access decision +strategy. You can configure this to suite your needs. There are three +strategies available: - // app/config/security.xml - $container->loadFromExtension('security', array( - // strategy can be: affirmative, unanimous or consensus - 'access_decision_manager' => array( - 'strategy' => 'unanimous', - ), - )); +``affirmative`` (default) + This grants access as soon as there is *one* voter granting access; -That's it! Now, when deciding whether or not a user should have access, -the new voter will deny access to any user in the list of blacklisted IPs. +``consensus`` + This grants access if there are more voters granting access than denying; -Note that the voters are only called, if any access is actually checked. So -you need at least something like +``unanimous`` + This only grants access once *all* voters grant access. + +In the above scenario, both voters should grant access in order to grant access +to the user to read the post. In this case, the default strategy is no longer +valid and ``unanimous`` should be used instead. You can set this in the +security configuration: .. configuration-block:: @@ -211,28 +261,31 @@ you need at least something like # app/config/security.yml security: - access_control: - - { path: ^/, role: IS_AUTHENTICATED_ANONYMOUSLY } + access_decision_manager: + strategy: unanimous .. code-block:: xml - - - - - + + + + + + .. code-block:: php - // app/config/security.xml + // app/config/security.php $container->loadFromExtension('security', array( - 'access_control' => array( - array('path' => '^/', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'), + 'access_decision_manager' => array( + 'strategy' => 'unanimous', ), - )); - -.. seealso:: - - For a more advanced usage see - :ref:`components-security-access-decision-manager`. + )); diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst deleted file mode 100644 index a9d5e2b9076..00000000000 --- a/cookbook/security/voters_data_permission.rst +++ /dev/null @@ -1,228 +0,0 @@ -.. index:: - single: Security; Data Permission Voters - -How to Use Voters to Check User Permissions -=========================================== - -In Symfony, you can check the permission to access data by using the -:doc:`ACL module `, which is a bit overwhelming -for many applications. A much easier solution is to work with custom voters, -which are like simple conditional statements. - -.. seealso:: - - Voters can also be used in other ways, like, for example, blacklisting IP - addresses from the entire application: :doc:`/cookbook/security/voters`. - -.. tip:: - - Take a look at the - :doc:`authorization ` - chapter for an even deeper understanding on voters. - -How Symfony Uses Voters ------------------------ - -In order to use voters, you have to understand how Symfony works with them. -All voters are called each time you use the ``isGranted()`` method on Symfony's -authorization checker (i.e. the ``security.authorization_checker`` service). Each -one decides if the current user should have access to some resource. - -Ultimately, Symfony uses one of three different approaches on what to do -with the feedback from all voters: affirmative, consensus and unanimous. - -For more information take a look at -:ref:`the section about access decision managers `. - -The Voter Interface -------------------- - -A custom voter needs to implement -:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface` -or extend :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter`, -which makes creating a voter even easier. - -.. code-block:: php - - abstract class AbstractVoter implements VoterInterface - { - abstract protected function getSupportedClasses(); - abstract protected function getSupportedAttributes(); - abstract protected function isGranted($attribute, $object, $user = null); - } - -In this example, the voter will check if the user has access to a specific -object according to your custom conditions (e.g. they must be the owner of -the object). If the condition fails, you'll return -``VoterInterface::ACCESS_DENIED``, otherwise you'll return -``VoterInterface::ACCESS_GRANTED``. In case the responsibility for this decision -does not belong to this voter, it will return ``VoterInterface::ACCESS_ABSTAIN``. - -Creating the custom Voter -------------------------- - -The goal is to create a voter that checks if a user has access to view or -edit a particular object. Here's an example implementation: - -.. code-block:: php - - // src/AppBundle/Security/Authorization/Voter/PostVoter.php - namespace AppBundle\Security\Authorization\Voter; - - use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; - use Symfony\Component\Security\Core\User\UserInterface; - - class PostVoter extends AbstractVoter - { - const VIEW = 'view'; - const EDIT = 'edit'; - - protected function getSupportedAttributes() - { - return array(self::VIEW, self::EDIT); - } - - protected function getSupportedClasses() - { - return array('AppBundle\Entity\Post'); - } - - protected function isGranted($attribute, $post, $user = null) - { - // make sure there is a user object (i.e. that the user is logged in) - if (!$user instanceof UserInterface) { - return false; - } - - switch($attribute) { - case self::VIEW: - // the data object could have for example a method isPrivate() - // which checks the Boolean attribute $private - if (!$post->isPrivate()) { - return true; - } - - break; - case self::EDIT: - // we assume that our data object has a method getOwner() to - // get the current owner user entity for this data object - if ($user->getId() === $post->getOwner()->getId()) { - return true; - } - - break; - - return false; - } - } - -That's it! The voter is done. The next step is to inject the voter into -the security layer. - -To recap, here's what's expected from the three abstract methods: - -:method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedClasses` - It tells Symfony that your voter should be called whenever an object of one - of the given classes is passed to ``isGranted()``. For example, if you return - ``array('AppBundle\Model\Product')``, Symfony will call your voter when a - ``Product`` object is passed to ``isGranted()``. - -:method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedAttributes` - It tells Symfony that your voter should be called whenever one of these - strings is passed as the first argument to ``isGranted()``. For example, if - you return ``array('CREATE', 'READ')``, then Symfony will call your voter - when one of these is passed to ``isGranted()``. - -:method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::isGranted` - It implements the business logic that verifies whether or not a given user is - allowed access to a given attribute (e.g. ``CREATE`` or ``READ``) on a given - object. This method must return a boolean. - -.. note:: - - Currently, to use the ``AbstractVoter`` base class, you must be creating a - voter where an object is always passed to ``isGranted()``. - -Declaring the Voter as a Service --------------------------------- - -To inject the voter into the security layer, you must declare it as a service -and tag it with ``security.voter``: - -.. configuration-block:: - - .. code-block:: yaml - - # src/AppBundle/Resources/config/services.yml - services: - security.access.post_voter: - class: AppBundle\Security\Authorization\Voter\PostVoter - public: false - tags: - - { name: security.voter } - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // src/AppBundle/Resources/config/services.php - $container - ->register( - 'security.access.post_voter', - 'AppBundle\Security\Authorization\Voter\PostVoter' - ) - ->setPublic(false) - ->addTag('security.voter') - ; - -How to Use the Voter in a Controller ------------------------------------- - -The registered voter will then always be asked as soon as the method ``isGranted()`` -from the authorization checker is called. - -.. code-block:: php - - // src/AppBundle/Controller/PostController.php - namespace AppBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Symfony\Component\HttpFoundation\Response; - - class PostController extends Controller - { - public function showAction($id) - { - // get a Post instance - $post = ...; - - $authChecker = $this->get('security.authorization_checker'); - - if (false === $authChecker->isGranted('view', $post)) { - throw $this->createAccessDeniedException('Unauthorized access!'); - } - - return new Response('

'.$post->getName().'

'); - } - } - -.. versionadded:: 2.6 - The ``security.authorization_checker`` service was introduced in Symfony 2.6. - Prior to Symfony 2.6, you had to use the ``isGranted()`` method of the - ``security.context`` service. - -It's that easy! diff --git a/cookbook/serializer.rst b/cookbook/serializer.rst index 8da5c70a583..8b2d9b296d8 100644 --- a/cookbook/serializer.rst +++ b/cookbook/serializer.rst @@ -169,7 +169,7 @@ to your class and choose which groups to use when serializing:: $serializer = $this->get('serializer'); $json = $serializer->serialize( $someObject, - 'json', array('groups' => array('group1') + 'json', array('groups' => array('group1')) ); .. _cookbook-serializer-enabling-metadata-cache: diff --git a/redirection_map b/redirection_map index a0083156209..3176d128e9e 100644 --- a/redirection_map +++ b/redirection_map @@ -48,3 +48,4 @@ /cmf/cookbook/creating_a_cms/sonata-admin /cmf/tutorial/sonata-admin /cmf/cookbook/creating_a_cms/the-frontend /cmf/tutorial/the-frontend /cookbook/upgrading /cookbook/upgrade/index +/cookbook/security/voters_data_permission /cookbook/security/voters diff --git a/reference/dic_tags.rst b/reference/dic_tags.rst index e4db9851aaa..f3097e577b5 100644 --- a/reference/dic_tags.rst +++ b/reference/dic_tags.rst @@ -22,6 +22,7 @@ Tag Name Usage `assetic.formula_resource`_ Adds a resource to the current asset manager `assetic.templating.php`_ Remove this service if PHP templating is disabled `assetic.templating.twig`_ Remove this service if Twig templating is disabled +`auto_alias`_ Define aliases based on the value of container parameters `console.command`_ Add a command `data_collector`_ Create a class that collects custom data for the profiler `doctrine.event_listener`_ Add a Doctrine event listener @@ -227,6 +228,123 @@ assetic.templating.twig The tagged service will be removed from the container if ``framework.templating.engines`` config section does not contain ``twig``. +auto_alias +---------- + +.. versionadded:: 2.7 + The ``auto_alias`` tag was introduced in Symfony 2.7. + +**Purpose**: Define aliases based on the value of container parameters + +Consider the following configuration that defines three different but related +services: + +.. configuration-block:: + + .. code-block:: yaml + + services: + app.mysql_lock: + class: AppBundle\Lock\MysqlLock + public: false + app.postgresql_lock: + class: AppBundle\Lock\PostgresqlLock + public: false + app.sqlite_lock: + class: AppBundle\Lock\SqliteLock + public: false + + .. code-block:: xml + + + + + + + + + + + + .. code-block:: php + + $container + ->register('app.mysql_lock', 'AppBundle\Lock\MysqlLock')->setPublic(false) + ->register('app.postgresql_lock', 'AppBundle\Lock\PostgresqlLock')->setPublic(false) + ->register('app.sqlite_lock', 'AppBundle\Lock\SqliteLock')->setPublic(false) + ; + +Instead of dealing with these three services, your application needs a generic +``app.lock`` service that will be an alias to one of these services, depending on +some configuration. Thanks to the ``auto_alias`` option, you can automatically create +that alias based on the value of a configuration parameter. + +Considering that a configuration parameter called ``database_type`` exists. Then, +the generic ``app.lock`` service can be defined as follows: + +.. configuration-block:: + + .. code-block:: yaml + + services: + app.mysql_lock: + # ... + app.postgresql_lock: + # ... + app.sqlite_lock: + # ... + app.lock: + tags: + - { name: auto_alias, format: "app.%database_type%_lock" } + + .. code-block:: xml + + + + + + + + + + + + + + + + .. code-block:: php + + $container + ->register('app.mysql_lock', 'AppBundle\Lock\MysqlLock')->setPublic(false) + ->register('app.postgresql_lock', 'AppBundle\Lock\PostgresqlLock')->setPublic(false) + ->register('app.sqlite_lock', 'AppBundle\Lock\SqliteLock')->setPublic(false) + + ->register('app.lock') + ->addTag('auto_alias', array('format' => 'app.%database_type%_lock')) + ; + +The ``format`` option defines the expression used to construct the name of the service +to alias. This expression can use any container parameter (as usual, +wrapping their names with ``%`` characters). + +.. note:: + + When using the ``auto_alias`` tag, it's not mandatory to define the aliased + services as private. However, doing that (like in the above example) makes + sense most of the times to prevent accessing those services directly instead + of using the generic service alias. + console.command ---------------