Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Security] remove support for defining voters that don't implement VoterInterface. #23324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions UPGRADE-4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,8 @@ Security
* The `AccessDecisionManager::setVoters()` method has been removed. Pass the
voters to the constructor instead.

* Support for defining voters that don't implement the `VoterInterface` has been removed.

SecurityBundle
--------------

Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CHANGELOG
* made `FirewallMap::$container` and `::$map` private
* made the first `UserPasswordEncoderCommand::_construct()` argument mandatory
* `UserPasswordEncoderCommand` does not extend `ContainerAwareCommand` anymore
* removed support for voters that don't implement the `VoterInterface`

3.4.0
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,14 @@ public function process(ContainerBuilder $container)

$voters = $this->findAndSortTaggedServices('security.voter', $container);
if (!$voters) {
throw new LogicException('No security voters found. You need to tag at least one with "security.voter"');
throw new LogicException('No security voters found. You need to tag at least one with "security.voter".');
}

foreach ($voters as $voter) {
$class = $container->getDefinition((string) $voter)->getClass();

if (!is_a($class, VoterInterface::class, true)) {
@trigger_error(sprintf('Using a security.voter tag on a class without implementing the %1$s is deprecated as of 3.4 and will be removed in 4.0. Implement the %1$s instead.', VoterInterface::class), E_USER_DEPRECATED);
}

if (!method_exists($class, 'vote')) {
// in case the vote method is completely missing, to prevent exceptions when voting
throw new LogicException(sprintf('%s should implement the %s interface when used as voter.', $class, VoterInterface::class));
throw new LogicException(sprintf('%s must implement the %s when used as a voter.', $class, VoterInterface::class));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Tests\Authorization\Stub\VoterWithoutInterface;

class AddSecurityVotersPassTest extends TestCase
{
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException
* @expectedExceptionMessage No security voters found. You need to tag at least one with "security.voter".
*/
public function testNoVoters()
{
Expand Down Expand Up @@ -71,8 +70,8 @@ public function testThatSecurityVotersAreProcessedInPriorityOrder()
}

/**
* @group legacy
* @expectedDeprecation Using a security.voter tag on a class without implementing the Symfony\Component\Security\Core\Authorization\Voter\VoterInterface is deprecated as of 3.4 and will be removed in 4.0. Implement the Symfony\Component\Security\Core\Authorization\Voter\VoterInterface instead.
* @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException
* @expectedExceptionMessage stdClass must implement the Symfony\Component\Security\Core\Authorization\Voter\VoterInterface when used as a voter.
*/
public function testVoterMissingInterface()
{
Expand All @@ -82,40 +81,7 @@ public function testVoterMissingInterface()
->addArgument(array())
;
$container
->register('without_interface', VoterWithoutInterface::class)
->addTag('security.voter')
;
$compilerPass = new AddSecurityVotersPass();
$compilerPass->process($container);

$argument = $container->getDefinition('security.access.decision_manager')->getArgument(0);
$refs = $argument->getValues();
$this->assertEquals(new Reference('without_interface'), $refs[0]);
$this->assertCount(1, $refs);
}

/**
* @group legacy
*/
public function testVoterMissingInterfaceAndMethod()
{
$exception = LogicException::class;
$message = 'stdClass should implement the Symfony\Component\Security\Core\Authorization\Voter\VoterInterface interface when used as voter.';

if (method_exists($this, 'expectException')) {
$this->expectException($exception);
$this->expectExceptionMessage($message);
} else {
$this->setExpectedException($exception, $message);
}

$container = new ContainerBuilder();
$container
->register('security.access.decision_manager', AccessDecisionManager::class)
->addArgument(array())
;
$container
->register('without_method', 'stdClass')
->register('without_interface', 'stdClass')
->addTag('security.voter')
;
$compilerPass = new AddSecurityVotersPass();
Expand Down
3 changes: 2 additions & 1 deletion src/Symfony/Component/Security/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ CHANGELOG
You should implement this method yourself in your concrete authenticator.
* removed the `AccessDecisionManager::setVoters()` method
* removed the `RoleInterface`
* added a sixth `string $context` argument to`LogoutUrlGenerator::registerListener()`
* removed support for voters that don't implement the `VoterInterface`
* added a sixth `string $context` argument to `LogoutUrlGenerator::registerListener()`

3.4.0
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\LogicException;

/**
* AccessDecisionManager is the base class for all access decision managers
Expand All @@ -33,7 +32,7 @@ class AccessDecisionManager implements AccessDecisionManagerInterface
private $allowIfEqualGrantedDeniedDecisions;

/**
* @param iterable|VoterInterface[] $voters An iterator of VoterInterface instances
* @param iterable|VoterInterface[] $voters An array or an iterator of VoterInterface instances
* @param string $strategy The vote strategy
* @param bool $allowIfAllAbstainDecisions Whether to grant access if all voters abstained or not
* @param bool $allowIfEqualGrantedDeniedDecisions Whether to grant access if result are equals
Expand Down Expand Up @@ -71,7 +70,7 @@ private function decideAffirmative(TokenInterface $token, array $attributes, $ob
{
$deny = 0;
foreach ($this->voters as $voter) {
$result = $this->vote($voter, $token, $object, $attributes);
$result = $voter->vote($token, $object, $attributes);
switch ($result) {
case VoterInterface::ACCESS_GRANTED:
return true;
Expand Down Expand Up @@ -112,7 +111,7 @@ private function decideConsensus(TokenInterface $token, array $attributes, $obje
$grant = 0;
$deny = 0;
foreach ($this->voters as $voter) {
$result = $this->vote($voter, $token, $object, $attributes);
$result = $voter->vote($token, $object, $attributes);

switch ($result) {
case VoterInterface::ACCESS_GRANTED:
Expand Down Expand Up @@ -153,7 +152,7 @@ private function decideUnanimous(TokenInterface $token, array $attributes, $obje
$grant = 0;
foreach ($this->voters as $voter) {
foreach ($attributes as $attribute) {
$result = $this->vote($voter, $token, $object, array($attribute));
$result = $voter->vote($token, $object, array($attribute));

switch ($result) {
case VoterInterface::ACCESS_GRANTED:
Expand All @@ -177,27 +176,4 @@ private function decideUnanimous(TokenInterface $token, array $attributes, $obje

return $this->allowIfAllAbstainDecisions;
}

/**
* TokenInterface vote proxy method.
*
* Acts as a BC layer when the VoterInterface is not implemented on the voter.
*
* @deprecated as of 3.4 and will be removed in 4.0. Call the voter directly as the instance will always be a VoterInterface
*/
private function vote($voter, TokenInterface $token, $subject, $attributes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could now update the docblock of the constructor so that the type hint for the $voters argument is now VoterInterface[] instead of iterable|VoterInterface[]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum I'm not sure! It seems that SecurityBundle passes an iterator argument to the access decision manager so that voters can be lazy loaded.

Copy link
Contributor Author

@hhamon hhamon Jun 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// AddSecurityVotersPass.php
$adm = $container->getDefinition('security.access.decision_manager');
$adm->replaceArgument(0, new IteratorArgument($voters));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can only change the dockblock but not the signature as passing iterators must still be allowed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VoterInterface[] means array of VoterInterface instances. So we cannot remove iterable from the phpdoc, otherwise we effectively remove the support of iterators from our supported usage (and so from our BC promise). This is quite problematic when our own usage relies on it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum, I thought VoterInterface[] means something I can iterate over.

{
if ($voter instanceof VoterInterface) {
return $voter->vote($token, $subject, $attributes);
}

if (method_exists($voter, 'vote')) {
@trigger_error(sprintf('Calling vote() on an voter without %1$s is deprecated as of 3.4 and will be removed in 4.0. Implement the %1$s on your voter.', VoterInterface::class), E_USER_DEPRECATED);

// making the assumption that the signature matches
return $voter->vote($token, $subject, $attributes);
}

throw new LogicException(sprintf('%s should implement the %s interface when used as voter.', get_class($voter), VoterInterface::class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@
namespace Symfony\Component\Security\Core\Tests\Authorization;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Exception\LogicException;
use Symfony\Component\Security\Core\Tests\Authorization\Stub\VoterWithoutInterface;

class AccessDecisionManagerTest extends TestCase
{
Expand Down Expand Up @@ -141,34 +138,4 @@ protected function getVoter($vote)

return $voter;
}

public function testVotingWrongTypeNoVoteMethod()
{
$exception = LogicException::class;
$message = sprintf('stdClass should implement the %s interface when used as voter.', VoterInterface::class);

if (method_exists($this, 'expectException')) {
$this->expectException($exception);
$this->expectExceptionMessage($message);
} else {
$this->setExpectedException($exception, $message);
}

$adm = new AccessDecisionManager(array(new \stdClass()));
$token = $this->getMockBuilder(TokenInterface::class)->getMock();

$adm->decide($token, array('TEST'));
}

/**
* @group legacy
* @expectedDeprecation Calling vote() on an voter without Symfony\Component\Security\Core\Authorization\Voter\VoterInterface is deprecated as of 3.4 and will be removed in 4.0. Implement the Symfony\Component\Security\Core\Authorization\Voter\VoterInterface on your voter.
*/
public function testVotingWrongTypeWithVote()
{
$adm = new AccessDecisionManager(array(new VoterWithoutInterface()));
$token = $this->getMockBuilder(TokenInterface::class)->getMock();

$adm->decide($token, array('TEST'));
}
}

This file was deleted.