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

Skip to content

Commit 93e9c87

Browse files
committed
WIP
1 parent e9e653d commit 93e9c87

14 files changed

+129
-60
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/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: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,17 @@ public function __construct(iterable $voters = [], string $strategy = self::STRA
5555
$this->allowIfEqualGrantedDeniedDecisions = $allowIfEqualGrantedDeniedDecisions;
5656
}
5757

58+
public function getDecision(TokenInterface $token, array $attributes, object $object = null): AccessDecision
59+
{
60+
return $this->{$this->strategy}($token, $attributes, $object);
61+
}
62+
5863
/**
5964
* @param bool $allowMultipleAttributes Whether to allow passing multiple values to the $attributes array
6065
*
6166
* {@inheritdoc}
6267
*/
63-
public function decide(TokenInterface $token, array $attributes, $object = null/*, bool $allowMultipleAttributes = false*/)
68+
public function decide(TokenInterface $token, array $attributes, $object = null/*, bool $allowMultipleAttributes = false*/): bool
6469
{
6570
$allowMultipleAttributes = 3 < \func_num_args() && func_get_arg(3);
6671

@@ -69,7 +74,7 @@ public function decide(TokenInterface $token, array $attributes, $object = null/
6974
throw new InvalidArgumentException(sprintf('Passing more than one Security attribute to "%s()" is not supported.', __METHOD__));
7075
}
7176

72-
return $this->{$this->strategy}($token, $attributes, $object);
77+
return $this->getDecision($token, $attributes, $object)->isGranted();
7378
}
7479

7580
/**
@@ -91,7 +96,7 @@ private function decideAffirmative(TokenInterface $token, array $attributes, $ob
9196

9297
if ($vote->isDenied()) {
9398
++$deny;
94-
} elseif (VoterInterface::ACCESS_ABSTAIN !== $result) {
99+
} elseif (!$vote->isAbstain()) {
95100
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);
96101
}
97102
}
@@ -129,8 +134,6 @@ private function decideConsensus(TokenInterface $token, array $attributes, $obje
129134
++$grant;
130135
} elseif ($vote->isDenied()) {
131136
++$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);
134137
}
135138
}
136139

@@ -225,7 +228,7 @@ private function decideIfAllAbstainDecisions(): AccessDecision
225228
private function vote(VoterInterface $voter, TokenInterface $token, $subject, array $attributes): Vote
226229
{
227230
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);
231+
trigger_deprecation('symfony/security-core', 5.3, 'Returning an int from the "%s::vote()" method is deprecated. Return a "%s" object instead.', \get_class($voter), Vote::class);
229232
$vote = Vote::create($vote);
230233
}
231234

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
{

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,31 @@ public function __construct(AccessDecisionManagerInterface $manager)
4646
}
4747
}
4848

49+
public function getDecision(TokenInterface $token, array $attributes, object $object = null): AccessDecision
50+
{
51+
$currentDecisionLog = [
52+
'attributes' => $attributes,
53+
'object' => $object,
54+
'voterDetails' => [],
55+
];
56+
57+
$this->currentLog[] = &$currentDecisionLog;
58+
59+
$result = $this->manager->getDecision($token, $attributes, $object);
60+
61+
$currentDecisionLog['result'] = $result;
62+
63+
$this->decisionLog[] = array_pop($this->currentLog); // Using a stack since getDecision can be called by voters
64+
65+
return $result;
66+
}
67+
4968
/**
5069
* {@inheritdoc}
5170
*
5271
* @param bool $allowMultipleAttributes Whether to allow passing multiple values to the $attributes array
5372
*/
54-
public function decide(TokenInterface $token, array $attributes, $object = null/*, bool $allowMultipleAttributes = false*/)
73+
public function decide(TokenInterface $token, array $attributes, $object = null/*, bool $allowMultipleAttributes = false*/): bool
5574
{
5675
$currentDecisionLog = [
5776
'attributes' => $attributes,

src/Symfony/Component/Security/Core/Authorization/Voter/AuthenticatedVoter.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ public function __construct(AuthenticationTrustResolverInterface $authentication
4747
public function vote(TokenInterface $token, $subject, array $attributes)
4848
{
4949
if ($attributes === [self::PUBLIC_ACCESS]) {
50-
return VoterInterface::ACCESS_GRANTED;
50+
return Vote::createGranted();
5151
}
5252

53-
$result = VoterInterface::ACCESS_ABSTAIN;
53+
$result = Vote::createAbstain();
5454
foreach ($attributes as $attribute) {
5555
if (null === $attribute || (self::IS_AUTHENTICATED_FULLY !== $attribute
5656
&& self::IS_AUTHENTICATED_REMEMBERED !== $attribute
@@ -61,36 +61,36 @@ public function vote(TokenInterface $token, $subject, array $attributes)
6161
continue;
6262
}
6363

64-
$result = VoterInterface::ACCESS_DENIED;
64+
$result = Vote::createDenied();
6565

6666
if (self::IS_AUTHENTICATED_FULLY === $attribute
6767
&& $this->authenticationTrustResolver->isFullFledged($token)) {
68-
return VoterInterface::ACCESS_GRANTED;
68+
return Vote::createGranted();
6969
}
7070

7171
if (self::IS_AUTHENTICATED_REMEMBERED === $attribute
7272
&& ($this->authenticationTrustResolver->isRememberMe($token)
7373
|| $this->authenticationTrustResolver->isFullFledged($token))) {
74-
return VoterInterface::ACCESS_GRANTED;
74+
return Vote::createGranted();
7575
}
7676

7777
if (self::IS_AUTHENTICATED_ANONYMOUSLY === $attribute
7878
&& ($this->authenticationTrustResolver->isAnonymous($token)
7979
|| $this->authenticationTrustResolver->isRememberMe($token)
8080
|| $this->authenticationTrustResolver->isFullFledged($token))) {
81-
return VoterInterface::ACCESS_GRANTED;
81+
return Vote::createGranted();
8282
}
8383

8484
if (self::IS_REMEMBERED === $attribute && $this->authenticationTrustResolver->isRememberMe($token)) {
85-
return VoterInterface::ACCESS_GRANTED;
85+
return Vote::createGranted();
8686
}
8787

8888
if (self::IS_ANONYMOUS === $attribute && $this->authenticationTrustResolver->isAnonymous($token)) {
89-
return VoterInterface::ACCESS_GRANTED;
89+
return Vote::createGranted();
9090
}
9191

9292
if (self::IS_IMPERSONATOR === $attribute && $token instanceof SwitchUserToken) {
93-
return VoterInterface::ACCESS_GRANTED;
93+
return Vote::createGranted();
9494
}
9595
}
9696

src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public function __construct(ExpressionLanguage $expressionLanguage, Authenticati
4444
*/
4545
public function vote(TokenInterface $token, $subject, array $attributes)
4646
{
47-
$result = VoterInterface::ACCESS_ABSTAIN;
47+
$result = Vote::createAbstain();
4848
$variables = null;
4949
foreach ($attributes as $attribute) {
5050
if (!$attribute instanceof Expression) {
@@ -55,9 +55,9 @@ public function vote(TokenInterface $token, $subject, array $attributes)
5555
$variables = $this->getVariables($token, $subject);
5656
}
5757

58-
$result = VoterInterface::ACCESS_DENIED;
58+
$result = Vote::createDenied();
5959
if ($this->expressionLanguage->evaluate($attribute, $variables)) {
60-
return VoterInterface::ACCESS_GRANTED;
60+
return Vote::createGranted();
6161
}
6262
}
6363

src/Symfony/Component/Security/Core/Authorization/Voter/RoleVoter.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function __construct(string $prefix = 'ROLE_')
3232
*/
3333
public function vote(TokenInterface $token, $subject, array $attributes)
3434
{
35-
$result = VoterInterface::ACCESS_ABSTAIN;
35+
$result = Vote::createAbstain();
3636
$roles = $this->extractRoles($token);
3737

3838
foreach ($attributes as $attribute) {
@@ -44,10 +44,10 @@ public function vote(TokenInterface $token, $subject, array $attributes)
4444
trigger_deprecation('symfony/security-core', '5.1', 'The ROLE_PREVIOUS_ADMIN role is deprecated and will be removed in version 6.0, use the IS_IMPERSONATOR attribute instead.');
4545
}
4646

47-
$result = VoterInterface::ACCESS_DENIED;
47+
$result = Vote::createDenied();
4848
foreach ($roles as $role) {
4949
if ($attribute === $role) {
50-
return VoterInterface::ACCESS_GRANTED;
50+
return Vote::createGranted();
5151
}
5252
}
5353
}

src/Symfony/Component/Security/Core/Authorization/Voter/Vote.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
namespace Symfony\Component\Security\Core\Authorization\Voter;
1313

1414
/**
15-
* A Vote is returned by a Voter and contains the access (granted, abstain or denied). It can also contains a reason
16-
* explaining the why of the access which has been decided.
15+
* A Vote is returned by a Voter and contains the access (granted, abstain or denied).
16+
* It can also contains a reason explaining the vote decision.
1717
*
1818
* @author Dany Maillard <danymaillard93b@gmail.com>
1919
*/
@@ -39,24 +39,24 @@ public static function create(int $access, string $reason = '', array $parameter
3939
return new self($access, $reason, $parameters);
4040
}
4141

42-
public static function createGranted(string $reason, array $parameters = []): self
42+
public static function createGranted(string $reason = '', array $parameters = []): self
4343
{
4444
return new self(VoterInterface::ACCESS_GRANTED, $reason, $parameters);
4545
}
4646

47-
public static function createAbstrain(string $reason, array $parameters = []): self
47+
public static function createAbstain(string $reason = '', array $parameters = []): self
4848
{
4949
return new self(VoterInterface::ACCESS_ABSTAIN, $reason, $parameters);
5050
}
5151

52-
public static function createDenied(string $reason, array $parameters = []): self
52+
public static function createDenied(string $reason = '', array $parameters = []): self
5353
{
5454
return new self(VoterInterface::ACCESS_DENIED, $reason, $parameters);
5555
}
5656

57-
public function merge(self $vote): void
57+
public function setReason(string $reason)
5858
{
59-
$this->reason .= trim(' '.$vote->getReason());
59+
$this->reason = $reason;
6060
}
6161

6262
public function getReason(): string

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,18 @@ public function vote(TokenInterface $token, $subject, array $attributes)
5151
// as soon as at least one attribute is supported, default is to deny access
5252
$vote = $this->deny();
5353

54-
$v = \is_bool($v = $this->voteOnAttribute($attribute, $subject, $token)) ? Vote::create($v) : $v; // BC layer
55-
if ($v->isGranted()) {
54+
$decision = $this->voteOnAttribute($attribute, $subject, $token);
55+
if (\is_bool($decision)) {
56+
trigger_deprecation('symfony/security-core', '5.3', 'Returning a boolean in "%s::voteOnAttribute()" is deprecated, return an instance of "%s" instead.', static::class, Vote::class);
57+
$decision = $decision ? $this->grant() : $this->deny();
58+
}
59+
60+
if ($decision->isGranted()) {
5661
// grant access as soon as at least one attribute returns a positive response
57-
return $v;
58-
} else {
59-
$vote->merge($v);
62+
return $decision;
6063
}
64+
65+
$vote->setReason($vote->getReason().trim(' '.$decision->getReason()));
6166
}
6267

6368
return $vote;
@@ -76,7 +81,7 @@ public function grant(string $reason = '', array $parameters = []): Vote
7681
*/
7782
public function abstain(string $reason = '', array $parameters = []): Vote
7883
{
79-
return Vote::createAbstrain($reason, $parameters);
84+
return Vote::createAbstain($reason, $parameters);
8085
}
8186

8287
/**
@@ -103,7 +108,7 @@ abstract protected function supports(string $attribute, $subject);
103108
*
104109
* @param mixed $subject
105110
*
106-
* @return bool|Vote Returning a boolean is deprecated since Symfony 5.1. Return a Vote object instead.
111+
* @return Vote Returning a boolean is deprecated since Symfony 5.1. Return a Vote object instead.
107112
*/
108113
abstract protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token);
109114
}

0 commit comments

Comments
 (0)
0