diff --git a/UPGRADE-2.8.md b/UPGRADE-2.8.md index ae8bb7fa4ac98..ba7a13617af3f 100644 --- a/UPGRADE-2.8.md +++ b/UPGRADE-2.8.md @@ -442,38 +442,12 @@ FrameworkBundle Security -------- - * The AbstractToken::isGranted() method was deprecated. Instead, - override the voteOnAttribute() method. This method has one small - difference: it's passed the TokenInterface instead of the user: + * The `AbstractVoter` class was deprecated. Instead, extend the `Voter` class and + move your voting logic in the `supports($attribute, $subject)` and + `voteOnAttribute($attribute, $object, TokenInterface $token)` methods. - Before: - - ```php - class MyCustomVoter extends AbstractVoter - { - // ... - - protected function isGranted($attribute, $object, $user = null) - { - // ... - } - } - ``` - - After: - - ```php - class MyCustomVoter extends AbstractVoter - { - // ... - - protected function voteOnAttribute($attribute, $object, TokenInterface $token) - { - $user = $token->getUser(); - // ... - } - } - ``` + * The `VoterInterface::supportsClass` and `supportsAttribute` methods were + deprecated and will be removed from the interface in 3.0. Config ------ diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php index 7b04222eae921..5dcf787c9968c 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Core\Authorization\Voter; +@trigger_error('The '.__NAMESPACE__.'\AbstractVoter class is deprecated since version 2.8, to be removed in 3.0. Upgrade to Symfony\Component\Security\Core\Authorization\Voter\Voter instead.', E_USER_DEPRECATED); + use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -18,6 +20,8 @@ * Abstract Voter implementation that reduces boilerplate code required to create a custom Voter. * * @author Roman Marintšenko + * + * @deprecated since version 2.8, to be removed in 3.0. Upgrade to Symfony\Component\Security\Core\Authorization\Voter\Voter instead. */ abstract class AbstractVoter implements VoterInterface { @@ -26,8 +30,6 @@ abstract class AbstractVoter implements VoterInterface */ public function supportsAttribute($attribute) { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); - return in_array($attribute, $this->getSupportedAttributes()); } @@ -36,8 +38,6 @@ public function supportsAttribute($attribute) */ public function supportsClass($class) { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); - foreach ($this->getSupportedClasses() as $supportedClass) { if ($supportedClass === $class || is_subclass_of($class, $supportedClass)) { return true; @@ -62,7 +62,7 @@ public function supportsClass($class) */ public function vote(TokenInterface $token, $object, array $attributes) { - if (!$object) { + if (!$object || !$this->supportsClass(get_class($object))) { return self::ACCESS_ABSTAIN; } @@ -70,14 +70,14 @@ public function vote(TokenInterface $token, $object, array $attributes) $vote = self::ACCESS_ABSTAIN; foreach ($attributes as $attribute) { - if (!$this->supports($attribute, $object)) { + if (!$this->supportsAttribute($attribute)) { continue; } // as soon as at least one attribute is supported, default is to deny access $vote = self::ACCESS_DENIED; - if ($this->voteOnAttribute($attribute, $object, $token)) { + if ($this->isGranted($attribute, $object, $token->getUser())) { // grant access as soon as at least one voter returns a positive response return self::ACCESS_GRANTED; } @@ -86,62 +86,19 @@ public function vote(TokenInterface $token, $object, array $attributes) return $vote; } - /** - * Determines if the attribute and object are supported by this voter. - * - * This method will become abstract in 3.0. - * - * @param string $attribute An attribute - * @param string $object The object to secure - * - * @return bool True if the attribute and object is supported, false otherwise - */ - protected function supports($attribute, $object) - { - @trigger_error('The getSupportedClasses and getSupportedAttributes methods are deprecated since version 2.8 and will be removed in version 3.0. Overwrite supports instead.', E_USER_DEPRECATED); - - $classIsSupported = false; - foreach ($this->getSupportedClasses() as $supportedClass) { - if ($object instanceof $supportedClass) { - $classIsSupported = true; - break; - } - } - - if (!$classIsSupported) { - return false; - } - - if (!in_array($attribute, $this->getSupportedAttributes())) { - return false; - } - - return true; - } - /** * Return an array of supported classes. This will be called by supportsClass. * * @return array an array of supported classes, i.e. array('Acme\DemoBundle\Model\Product') - * - * @deprecated since version 2.8, to be removed in 3.0. Use supports() instead. */ - protected function getSupportedClasses() - { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); - } + abstract protected function getSupportedClasses(); /** * Return an array of supported attributes. This will be called by supportsAttribute. * * @return array an array of supported attributes, i.e. array('CREATE', 'READ') - * - * @deprecated since version 2.8, to be removed in 3.0. Use supports() instead. */ - protected function getSupportedAttributes() - { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); - } + abstract protected function getSupportedAttributes(); /** * Perform a single access check operation on a given attribute, object and (optionally) user @@ -154,33 +111,7 @@ protected function getSupportedAttributes() * @param object $object * @param UserInterface|string $user * - * @deprecated This method will be removed in 3.0 - override voteOnAttribute instead. - * - * @return bool - */ - protected function isGranted($attribute, $object, $user = null) - { - // forces isGranted() or voteOnAttribute() to be overridden - throw new \BadMethodCallException(sprintf('You must override the voteOnAttribute() method in "%s".', get_class($this))); - } - - /** - * Perform a single access check operation on a given attribute, object and token. - * It is safe to assume that $attribute and $object's class pass supports method call. - * - * This method will become abstract in 3.0. - * - * @param string $attribute - * @param object $object - * @param TokenInterface $token - * * @return bool */ - protected function voteOnAttribute($attribute, $object, TokenInterface $token) - { - // the user should override this method, and not rely on the deprecated isGranted() - @trigger_error(sprintf("The AbstractVoter::isGranted() method is deprecated since 2.8 and won't be called anymore in 3.0. Override voteOnAttribute() in %s instead.", get_class($this)), E_USER_DEPRECATED); - - return $this->isGranted($attribute, $object, $token->getUser()); - } + abstract protected function isGranted($attribute, $object, $user = null); } diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php new file mode 100644 index 0000000000000..8d36fd8f8c919 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Voter; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * Voter is an abstract default implementation of a voter. + * + * @author Roman Marintšenko + * @author Grégoire Pineau + */ +abstract class Voter implements VoterInterface +{ + /** + * {@inheritdoc} + */ + public function supportsAttribute($attribute) + { + throw new \BadMethodCallException('supportsAttribute method is deprecated since version 2.8, to be removed in 3.0'); + } + + /** + * {@inheritdoc} + */ + public function supportsClass($class) + { + throw new \BadMethodCallException('supportsClass method is deprecated since version 2.8, to be removed in 3.0'); + } + + /** + * {@inheritdoc} + */ + public function vote(TokenInterface $token, $object, array $attributes) + { + // abstain vote by default in case none of the attributes are supported + $vote = self::ACCESS_ABSTAIN; + + foreach ($attributes as $attribute) { + if (!$this->supports($attribute, $object)) { + continue; + } + + // as soon as at least one attribute is supported, default is to deny access + $vote = self::ACCESS_DENIED; + + if ($this->voteOnAttribute($attribute, $object, $token)) { + // grant access as soon as at least one attribute returns a positive response + return self::ACCESS_GRANTED; + } + } + + return $vote; + } + + /** + * Determines if the attribute and subject are supported by this voter. + * + * @param string $attribute An attribute + * @param mixed $subject The subject to secure, e.g. an object the user wants to access or any other PHP type + * + * @return bool True if the attribute and subject are supported, false otherwise + */ + abstract protected function supports($attribute, $subject); + + /** + * Perform a single access check operation on a given attribute, subject and token. + * + * @param string $attribute + * @param mixed $subject + * @param TokenInterface $token + * + * @return bool + */ + abstract protected function voteOnAttribute($attribute, $subject, TokenInterface $token); +} diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php index 5ea77320cf610..b537c1b2effc9 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php @@ -11,10 +11,11 @@ namespace Symfony\Component\Security\Core\Tests\Authorization\Voter; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; +/** + * @group legacy + */ class AbstractVoterTest extends \PHPUnit_Framework_TestCase { protected $token; @@ -50,75 +51,8 @@ public function getTests() */ public function testVote(array $attributes, $expectedVote, $object, $message) { - $voter = new AbstractVoterTest_Voter(); + $voter = new Fixtures\MyVoter(); $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); } - - /** - * @dataProvider getTests - * @group legacy - */ - public function testVoteLegacy(array $attributes, $expectedVote, $object, $message) - { - $voter = new AbstractVoterTest_LegacyVoter(); - - $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); - } - - /** - * @group legacy - * @expectedException \BadMethodCallException - */ - public function testNoOverriddenMethodsThrowsException() - { - $voter = new AbstractVoterTest_NothingImplementedVoter(); - $voter->vote($this->token, new \stdClass(), array('EDIT')); - } -} - -class AbstractVoterTest_Voter extends AbstractVoter -{ - protected function voteOnAttribute($attribute, $object, TokenInterface $token) - { - return 'EDIT' === $attribute; - } - - protected function supports($attribute, $object) - { - return $object instanceof \stdClass && in_array($attribute, array('EDIT', 'CREATE')); - } -} - -class AbstractVoterTest_LegacyVoter extends AbstractVoter -{ - protected function getSupportedClasses() - { - return array('stdClass'); - } - - protected function getSupportedAttributes() - { - return array('EDIT', 'CREATE'); - } - - protected function isGranted($attribute, $object, $user = null) - { - return 'EDIT' === $attribute; - } -} - -class AbstractVoterTest_NothingImplementedVoter extends AbstractVoter -{ - protected function getSupportedClasses() - { - return array('stdClass'); - } - - protected function getSupportedAttributes() - { - return array('EDIT', 'CREATE'); - } - - // this is a bad voter that hasn't overridden isGranted or voteOnAttribute } diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/Fixtures/MyVoter.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/Fixtures/MyVoter.php new file mode 100644 index 0000000000000..b75f79851be99 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/Fixtures/MyVoter.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Tests\Authorization\Voter; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; + +class VoterTest extends \PHPUnit_Framework_TestCase +{ + protected $token; + + protected function setUp() + { + $this->token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + } + + public function getTests() + { + return array( + array(array('EDIT'), VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if attribute and class are supported and attribute grants access'), + array(array('CREATE'), VoterInterface::ACCESS_DENIED, new \stdClass(), 'ACCESS_DENIED if attribute and class are supported and attribute does not grant access'), + + array(array('DELETE', 'EDIT'), VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if one attribute is supported and grants access'), + array(array('DELETE', 'CREATE'), VoterInterface::ACCESS_DENIED, new \stdClass(), 'ACCESS_DENIED if one attribute is supported and denies access'), + + array(array('CREATE', 'EDIT'), VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if one attribute grants access'), + + array(array('DELETE'), VoterInterface::ACCESS_ABSTAIN, new \stdClass(), 'ACCESS_ABSTAIN if no attribute is supported'), + + array(array('EDIT'), VoterInterface::ACCESS_ABSTAIN, $this, 'ACCESS_ABSTAIN if class is not supported'), + + array(array('EDIT'), VoterInterface::ACCESS_ABSTAIN, null, 'ACCESS_ABSTAIN if object is null'), + + array(array(), VoterInterface::ACCESS_ABSTAIN, new \stdClass(), 'ACCESS_ABSTAIN if no attributes were provided'), + ); + } + + /** + * @dataProvider getTests + */ + public function testVote(array $attributes, $expectedVote, $object, $message) + { + $voter = new VoterTest_Voter(); + + $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); + } +} + +class VoterTest_Voter extends Voter +{ + protected function voteOnAttribute($attribute, $object, TokenInterface $token) + { + return 'EDIT' === $attribute; + } + + protected function supports($attribute, $object) + { + return $object instanceof \stdClass && in_array($attribute, array('EDIT', 'CREATE')); + } +}