8000 WIP · symfony/symfony@b5f1e4c · GitHub
[go: up one dir, main page]

Skip to content

Commit b5f1e4c

Browse files
committed
WIP
1 parent e9e653d commit b5f1e4c

29 files changed

+658
-243
lines changed

src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
3838
use Symfony\Component\Routing\RouterInterface;
3939
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
40+
use Symfony\Component\Security\Core\Authorization\AccessDecision;
4041
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
4142
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
4243
use Symfony\Component\Security\Core\User\UserInterface;
@@ -229,11 +230,22 @@ protected function isGranted($attribute, $subject = null): bool
229230
*/
230231
protected function denyAccessUnlessGranted($attribute, $subject = null, string $message = 'Access Denied.'): void
231232
{
232-
if (!$this->isGranted($attribute, $subject)) {
233+
if (!$this->container->has('security.authorization_checker')) {
234+
throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');
235+
}
236+
237+
$checker = $this->container->get('security.authorization_checker');
238+
if (method_exists($checker, 'getDecision')) {
239+
$decision = $checker->getDecision($attribute, $subject);
240+
} else {
241+
$decision = $checker->isGranted($attribute, $subject) ? AccessDecision::createGranted() : AccessDecision::createDenied();
242+
}
243+
244+
if (!$decision->isGranted()) {
233245
$exception = $this->createAccessDeniedException($message);
234246
$exception->setAttributes($attribute);
235247
$exception->setSubject($subject);
236-
$exception->setAccessDecision($this->container->get('security.authorization_checker')->getLastAccessDecision());
248+
$exception->setAccessDecision($decision);
237249

238250
throw $exception;
239251
}

src/Symfony/Bundle/SecurityBundle/Tests/EventListener/VoteListenerTest.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Bundle\SecurityBundle\EventListener\VoteListener;
1616
use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager;
17+
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
1718
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
1819
use Symfony\Component\Security\Core\Event\VoteEvent;
1920

@@ -29,10 +30,33 @@ public function testOnVoterVote()
2930
->setMethods(['addVoterVote'])
3031
->getMock();
3132

33+
$vote = Vote::createGranted();
3234
$traceableAccessDecisionManager
3335
->expects($this->once())
3436
->method('addVoterVote')
35-
->with($voter, ['myattr1', 'myattr2'], VoterInterface::ACCESS_GRANTED);
37+
->with($voter, ['myattr1', 'myattr2'], $vote);
38+
39+
$sut = new VoteListener($traceableAccessDecisionManager);
40+
$sut->onVoterVote(new VoteEvent($voter, 'mysubject', ['myattr1', 'myattr2'], $vote));
41+
}
42+
43+
/**
44+
* @group legacy
45+
*/
46+
public function testOnVoterVoteLegacy()
47+
{
48+
$voter = $this->createMock(VoterInterface::class);
49+
50+
$traceableAccessDecisionManager = $this
51+
->getMockBuilder(TraceableAccessDecisionManager::class)
52+
->disableOriginalConstructor()
53+
->setMethods(['addVoterVote'])
54+
->getMock();
55+
56+
$traceableAccessDecisionManager
57+
->expects($this->once())
58+
->method('addVoterVote')
59+
->with($voter, ['myattr1', 'myattr2'], Vote::createGranted());
3660

3761
$sut = new VoteListener($traceableAccessDecisionManager);
3862
$sut->onVoterVote(new VoteEvent($voter, 'mysubject', ['myattr1', 'myattr2'], VoterInterface::ACCESS_GRANTED));

src/Symfony/Component/Security/Core/Authorization/AccessDecision.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,13 @@ public function getDeniedVotes(): array
8686
return $this->getVotesByAccess(Voter::ACCESS_DENIED);
8787
}
8888

89+
/**
90+
* @return Vote[]
91+
*/
8992
private function getVotesByAccess(int $access): array
9093
{
91-
return array_filter($this->votes, function (Vote $vote) use ($access) { return $vote->getAccess() === $access; });
94+
return array_filter($this->votes, static function (Vote $vote) use ($access): bool {
95+
return $vote->getAccess() === $access;
96+
});
9297
}
9398
}

src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1515
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
16+
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
1617
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
1718
use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
1819

@@ -55,21 +56,30 @@ public function __construct(iterable $voters = [], string $strategy = self::STRA
5556
$this->allowIfEqualGrantedDeniedDecisions = $allowIfEqualGrantedDeniedDecisions;
5657
}
5758

59+
public function getDecision(TokenInterface $token, array $attributes, object $object = null): AccessDecision
60+
{
61+
return $this->{$this->strategy}($token, $attributes, $object);
62+
}
63+
5864
/**
5965
* @param bool $allowMultipleAttributes Whether to allow passing multiple values to the $attributes array
6066
*
6167
* {@inheritdoc}
68+
*
69+
* @deprecated since 5.3, use {@see getDecision()} instead.
6270
*/
63-
public function decide(TokenInterface $token, array $attributes, $object = null/*, bool $allowMultipleAttributes = false*/)
71+
public function decide(TokenInterface $token, array $attributes, $object = null/*, bool $allowMultipleAttributes = false*/): bool
6472
{
73+
trigger_deprecation('symfony/security-core', '5.3', 'Method "%s::decide()" has been deprecated, use "%s::getDecision()" instead.', __CLASS__, __CLASS__);
74+
6575
$allowMultipleAttributes = 3 < \func_num_args() && func_get_arg(3);
6676

6777
// Special case for AccessListener, do not remove the right side of the condition before 6.0
6878
if (\count($attributes) > 1 && !$allowMultipleAttributes) {
6979
throw new InvalidArgumentException(sprintf('Passing more than one Security attribute to "%s()" is not supported.', __METHOD__));
7080
}
7181

72-
return $this->{$this->strategy}($token, $attributes, $object);
82+
return $this->getDecision($token, $attributes, $object)->isGranted();
7383
}
7484

7585
/**
@@ -83,24 +93,26 @@ private function decideAffirmative(TokenInterface $token, array $attributes, $ob
8393
$votes = [];
8494
$deny = 0;
8595
foreach ($this->voters as $voter) {
86-
$votes[] = $vote = $this->vote($voter, $token, $object, $attributes);
96+
$vote = $this->vote($voter, $token, $object, $attributes);
97+
if (null === $vote) {
98+
continue;
99+
}
87100

101+
$votes[] = $vote;
88102
if ($vote->isGranted()) {
89103
return AccessDecision::createGranted($votes);
90104
}
91105

92106
if ($vote->isDenied()) {
93107
++$deny;
94-
} elseif (VoterInterface::ACCESS_ABSTAIN !== $result) {
95-
trigger_deprecation('symfony/security-core', '5.3', 'Returning "%s" in "%s::vote()" is deprecated, return one of "%s" constants: "ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN".', var_export($result, true), get_debug_type($voter), VoterInterface::class);
96108
}
97109
}
98110

99111
if ($deny > 0) {
100112
return AccessDecision::createDenied($votes);
101113
}
102114

103-
return $this->decideIfAllAbstainDecisions();
115+
return $this->decideIfAllAbstainDecisions($votes);
104116
}
105117

106118
/**
@@ -123,14 +135,16 @@ private function decideConsensus(TokenInterface $token, array $attributes, $obje
123135
$grant = 0;
124136
$deny = 0;
125137
foreach ($this->voters as $voter) {
126-
$votes[] = $vote = $this->vote($voter, $token, $object, $attributes);
138+
$vote = $this->vote($voter, $token, $object, $attributes);
139+
if (null === $vote) {
140+
continue;
141+
}
127142

143+
$votes[] = $vote;
128144
if ($vote->isGranted()) {
129145
++$grant;
130146
} elseif ($vote->isDenied()) {
131147
++$deny;
132-
} elseif (VoterInterface::ACCESS_ABSTAIN !== $result) {
133-
trigger_deprecation('symfony/security-core', '5.3', 'Returning "%s" in "%s::vote()" is deprecated, return one of "%s" constants: "ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN".', var_export($result, true), get_debug_type($voter), VoterInterface::class);
134148
}
135149
}
136150

@@ -144,12 +158,12 @@ private function decideConsensus(TokenInterface $token, array $attributes, $obje
144158

145159
if ($grant > 0) {
146160
return $this->allowIfEqualGrantedDeniedDecisions
147-
? AccessDecision::createGranted()
148-
: AccessDecision::createDenied()
161+
? AccessDecision::createGranted($votes)
162+
: AccessDecision::createDenied($votes)
149163
;
150164
}
151165

152-
return $this->decideIfAllAbstainDecisions();
166+
return $this->decideIfAllAbstainDecisions($votes);
153167
}
154168

155169
/**
@@ -164,16 +178,18 @@ private function decideUnanimous(TokenInterface $token, array $attributes, $obje
164178
$grant = 0;
165179
foreach ($this->voters as $voter) {
166180
foreach ($attributes as $attribute) {
167-
$votes[] = $vote = $this->vote($voter, $token, $object, [$attribute]);
181+
$vote = $this->vote($voter, $token, $object, [$attribute]);
182+
if (null === $vote) {
183+
continue;
184+
}
168185

186+
$votes[] = $vote;
169187
if ($vote->isDenied()) {
170188
return AccessDecision::createDenied($votes);
171189
}
172190

173191
if ($vote->isGranted()) {
174192
++$grant;
175-
} elseif (VoterInterface::ACCESS_ABSTAIN !== $result) {
176-
trigger_deprecation('symfony/security-core', '5.3', 'Returning "%s" in "%s::vote()" is deprecated, return one of "%s" constants: "ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN".', var_export($result, true), get_debug_type($voter), VoterInterface::class);
177193
}
178194
}
179195
}
@@ -183,7 +199,7 @@ private function decideUnanimous(TokenInterface $token, array $attributes, $obje
183199
return AccessDecision::createGranted($votes);
184200
}
185201

186-
return $this->decideIfAllAbstainDecisions();
202+
return $this->decideIfAllAbstainDecisions($votes);
187203
}
188204

189205
/**
@@ -195,40 +211,50 @@ private function decideUnanimous(TokenInterface $token, array $attributes, $obje
195211
*/
196212
private function decidePriority(TokenInterface $token, array $attributes, $object = null)
197213
{
214+
$votes = [];
198215
foreach ($this->voters as $voter) {
199-
$result = $voter->vote($token, $object, $attributes);
200-
201-
if (VoterInterface::ACCESS_GRANTED === $result) {
202-
return true;
216+
$vote = $this->vote($voter, $token, $object, $attributes);
217+
if (null === $vote) {
218+
continue;
203219
}
204220

205-
if (VoterInterface::ACCESS_DENIED === $result) {
206-
return false;
221+
$votes[] = $vote;
222+
if ($vote->isGranted()) {
223+
return AccessDecision::createGranted($votes);
207224
}
208225

209-
if (VoterInterface::ACCESS_ABSTAIN !== $result) {
210-
trigger_deprecation('symfony/security-core', '5.3', 'Returning "%s" in "%s::vote()" is deprecated, return one of "%s" constants: "ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN".', var_export($result, true), get_debug_type($voter), VoterInterface::class);
226+
if ($vote->isDenied()) {
227+
return AccessDecision::createDenied($votes);
211228
}
212229
}
213230

214-
return $this->allowIfAllAbstainDecisions;
231+
return $this->decideIfAllAbstainDecisions($votes);
215232
}
216233

217-
private function decideIfAllAbstainDecisions(): AccessDecision
234+
/**
235+
* @param Vote[] $votes
236+
*/
237+
private function decideIfAllAbstainDecisions(array $votes): AccessDecision
218238
{
219239
return $this->allowIfAllAbstainDecisions
220-
? AccessDecision::createGranted()
221-
: AccessDecision::createDenied()
240+
? AccessDecision::createGranted($votes)
241+
: AccessDecision::createDenied($votes)
222242
;
223243
}
224244

225-
private function vote(VoterInterface $voter, TokenInterface $token, $subject, array $attributes): Vote
245+
private function vote(VoterInterface $voter, TokenInterface $token, $subject, array $attributes): ?Vote
226246
{
227-
if (\is_int($vote = $voter->vote($token, $subject, $attributes))) {
228-
trigger_deprecation('symfony/security', 5.1, 'Returning an int from the "%s::vote()" method is deprecated. Return a "%s" object instead.', \get_class($this->voter), Vote::class);
229-
$vote = Vote::create($vote);
247+
$result = $voter->vote($token, $subject, $attributes);
248+
if ($result instanceof Vote) {
249+
return $result;
250+
}
251+
252+
trigger_deprecation('symfony/security-core', '5.3', 'Returning "%s" in "%s::vote()" is deprecated, return a "%s" object instead.', var_export($result, true), get_debug_type($voter), Vote::class);
253+
254+
if (\in_array($result, [VoterInterface::ACCESS_ABSTAIN, VoterInterface::ACCESS_DENIED, VoterInterface::ACCESS_GRANTED], true)) {
255+
return Vote::create($result);
230256
}
231257

232-
return $vote;
258+
return null;
233259
}
234260
}

src/Symfony/Component/Security/Core/Authorization/AccessDecisionManagerInterface.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* AccessDecisionManagerInterface makes authorization decisions.
1818
*
1919
* @author Fabien Potencier <fabien@symfony.com>
20+
*
21+
* @method AccessDecision getDecision(TokenInterface $token, array $attributes, object $object = null)
2022
*/
2123
interface AccessDecisionManagerInterface
2224
{
@@ -26,7 +28,9 @@ interface AccessDecisionManagerInterface
2628
* @param array $attributes An array of attributes associated with the method being invoked
2729
* @param object $object The object to secure
2830
*
29-
* @return bool|AccessDecision Returning a boolean is deprecated since Symfony 5.1. Return an AccessDecision object instead.
31+
* @return bool true if the access is granted, false otherwise
32+
*
33+
* @deprecated since 5.3, use {@see getDecision()} instead.
3034
*/
31-
public function decide(TokenInterface $token, array $attributes, $object = null);
35+
public function decide(TokenInterface $token, array $attributes, $object = null): bool;
3236
}

src/Symfony/Component/Security/Core/Authorization/AuthorizationChecker.php

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,32 +49,28 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM
4949
* @throws AuthenticationCredentialsNotFoundException when the token storage has no authentication token and $exceptionOnNoToken is set to true
5050
*/
5151
final public function isGranted($attribute, $subject = null): bool
52+
{
53+
return $this->getDecision($attribute, $subject)->isGranted();
54+
}
55+
56+
public function getDecision($attribute, $subject = null): AccessDecision
5257
{
5358
if (null === ($token = $this->tokenStorage->getToken())) {
5459
if ($this->exceptionOnNoToken) {
5560
throw new AuthenticationCredentialsNotFoundException('The token storage contains no authentication token. One possible reason may be that there is no firewall configured for this URL.');
5661
}
5762

5863
$token = new NullToken();
59-
} else {
60-
if ($this->alwaysAuthenticate || !$token->isAuthenticated()) {
61-
$this->tokenStorage->setToken($token = $this->authenticationManager->authenticate($token));
62-
}
64+
} elseif ($this->alwaysAuthenticate || !$token->isAuthenticated()) {
65+
$this->tokenStorage->setToken($token = $this->authenticationManager->authenticate($token));
6366
}
6467

65-
$this->lastAccessDecision = $this->accessDecisionManager->decide($token, [$attribute], $subject);
68+
if (!method_exists($this->accessDecisionManager, 'getDecision')) {
69+
trigger_deprecation('symfony/security-core', 5.3, 'Not implementing "%s::getDecision()" method is deprecated, and would be required in 6.0.', \get_class($this->accessDecisionManager));
6670

67-
if (\is_bool($this->lastAccessDecision)) {
68-
trigger_deprecation('symfony/security', 5.1, 'Returning a boolean from the "%s::decide()" method is deprecated. Return an "%s" object instead', \get_class($this->accessDecisionManager), AccessDecision::class);
69-
70-
return $this->lastAccessDecision;
71+
return $this->accessDecisionManager->decide($token, [$attribute], $subject) ? AccessDecision::createGranted() : AccessDecision::createDenied();
7172
}
7273

73-
return $this->lastAccessDecision->isGranted();
74-
}
75-
76-
public function getLastAccessDecision(): AccessDecision
77-
{
78-
return $this->lastAccessDecision;
74+
return $this->accessDecisionManager->getDecision($token, [$attribute], $subject);
7975
}
8076
}

src/Symfony/Component/Security/Core/Authorization/AuthorizationCheckerInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
* The AuthorizationCheckerInterface.
1616
*
1717
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
18+
*
19+
* @method AccessDecision getDecision(mixed $attribute, mixed $subject = null)
1820
*/
1921
interface AuthorizationCheckerInterface
2022
{

0 commit comments

Comments
 (0)
0