8000 add capability to answer VoteObject · symfony/symfony@694ba96 · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 694ba96

Browse files
committed
add capability to answer VoteObject
psalm errors
1 parent 8dc32f7 commit 694ba96

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1025
-85
lines changed

src/Symfony/Bridge/Twig/Extension/SecurityExtension.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ public function isGranted(mixed $role, mixed $object = null, ?string $ 10000 field = nu
4545
}
4646

4747
try {
48+
/**
49+
* @var bool
50+
*/
4851
return $this->securityChecker->isGranted($role, $object);
4952
} catch (AuthenticationCredentialsNotFoundException) {
5053
return false;

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
3636
use Symfony\Component\Routing\RouterInterface;
3737
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
38+
use Symfony\Component\Security\Core\Authorization\AccessDecision;
3839
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
3940
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
4041
use Symfony\Component\Security\Core\User\UserInterface;
@@ -202,6 +203,22 @@ protected function isGranted(mixed $attribute, mixed $subject = null): bool
202203
return $this->container->get('security.authorization_checker')->isGranted($attribute, $subject);
203204
}
204205

206+
/**
207+
* Checks if the attribute is granted against the current authentication token and optionally supplied subject.
208+
*
209+
* @throws \LogicException
210+
*/
211+
protected function getAccessDecision(mixed $attribute, mixed $subject = null): AccessDecision
212+
{
213+
if (!$this->container->has('security.authorization_checker')) {
214+
throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');
215+
}
216+
217+
$decision = $this->container->get('security.authorization_checker')->isGranted($attribute, $subject, AccessDecision::RETURN_AS_OBJECT);
218+
219+
return $decision instanceof AccessDecision ? $decision : new AccessDecision($decision);
220+
}
221+
205222
/**
206223
* Throws an exception unless the attribute is granted against the current authentication token and optionally
207224
* supplied subject.
@@ -210,10 +227,13 @@ protected function isGranted(mixed $attribute, mixed $subject = null): bool
210227
*/
211228
protected function denyAccessUnlessGranted(mixed $attribute, mixed $subject = null, string $message = 'Access Denied.'): void
212229
{
213-
if (!$this->isGranted($attribute, $subject)) {
230+
$decision = $this->getAccessDecision($attribute, $subject);
231+
232+
if ($decision->isDenied()) {
214233
$exception = $this->createAccessDeniedException($message);
215234
$exception->setAttributes([$attribute]);
216235
$exception->setSubject($subject);
236+
$exception->setAccessDecision($decision);
217237

218238
throw $exception;
219239
}

src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
use Symfony\Component\Routing\RouterInterface;
4141
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
4242
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
43+
use Symfony\Component\Security\Core\Authorization\AccessDecision;
4344
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
45+
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
4446
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
4547
use Symfony\Component\Security\Core\User\InMemoryUser;
4648
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
@@ -362,7 +364,14 @@ public function testdenyAccessUnlessGranted()
362364

363365
$this->expectException(AccessDeniedException::class);
364366

365-
$controller->denyAccessUnlessGranted('foo');
367+
try {
368+
$controller->denyAccessUnlessGranted('foo');
369+
} catch (AccessDeniedException $exception) {
370+
$this->assertFalse($exception->getAccessDecision()->getAccess());
371+
$this->assertEmpty($exception->getAccessDecision()->getVotes());
372+
$this->assertEmpty($exception->getAccessDecision()->getMessage());
373+
throw $exception;
374+
}
366375
}
367376

368377
/**
@@ -644,4 +653,29 @@ public function testSendEarlyHints()
644653

645654
$this->assertSame('</style.css>; rel="preload"; as="stylesheet",</script.js>; rel="preload"; as="script"', $response->headers->get('Link'));
646655
}
656+
657+
public function testdenyAccessUnlessGrantedWithAccessDecisionObject()
658+
{
659+
$authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class);
660+
$authorizationChecker->expects($this->once())
661+
->method('isGranted')
662+
->willReturn(new AccessDecision(false, [new Vote(-1)], 'access denied'));
663+
664+
$container = new Container();
665+
$container->set('security.authorization_checker', $authorizationChecker);
666+
667+
$controller = $this->createController();
668+
$controller->setContainer($container);
669+
670+
$this->expectException(AccessDeniedException::class);
671+
672+
try {
673+
$controller->denyAccessUnlessGranted('foo');
674+
} catch (AccessDeniedException $exception) {
675+
$this->assertFalse($exception->getAccessDecision()->getAccess());
676+
$this->assertCount(1, $exception->getAccessDecision()->getVotes());
677+
$this->assertSame('access denied', $exception->getAccessDecision()->getMessage());
678+
throw $exception;
679+
}
680+
}
647681
}

src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
2121
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
2222
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
23+
use Symfony\Component\Security\Core\Authorization\AccessDecision;
2324
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
2425
use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager;
2526
use Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter;
27+
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
28+
use Symfony\Component\Security\Core\Authorization\Voter\VoteInterface;
2629
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
2730
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
2831
use Symfony\Component\Security\Http\FirewallMapInterface;
@@ -138,6 +141,7 @@ public function collect(Request $request, Response $response, ?\Throwable $excep
138141

139142
// collect voter details
140143
$decisionLog = $this->accessDecisionManager->getDecisionLog();
144+
141145
foreach ($decisionLog as $key => $log) {
142146
$decisionLog[$key]['voter_details'] = [];
143147
foreach ($log['voterDetails'] as $voterDetail) {
@@ -146,10 +150,14 @@ public function collect(Request $request, Response $response, ?\Throwable $excep
146150
$decisionLog[$key]['voter_details'][] = [
147151
'class' => $classData,
148152
'attributes' => $voterDetail['attributes'], // Only displayed for unanimous strategy
149-
'vote' => $voterDetail['vote'],
153+
'vote' => $voterDetail['vote'] instanceof VoteInterface ? $voterDetail['vote'] : new Vote($voterDetail['vote']),
150154
];
151155
}
152156
unset($decisionLog[$key]['voterDetails']);
157+
158+
if (!$decisionLog[$key]['result'] instanceof AccessDecision) {
159+
$decisionLog[$key]['result'] = new AccessDecision($decisionLog[$key]['result']);
160+
}
153161
}
154162

155163
$this->data['access_decision_log'] = $decisionLog;

src/Symfony/Bundle/SecurityBundle/EventListener/VoteListener.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function __construct(
3131

3232
public function onVoterVote(VoteEvent $event): void
3333
{
34-
$this->traceableAccessDecisionManager->addVoterVote($event->getVoter(), $event->getAttributes(), $event->getVote());
34+
$this->traceableAccessDecisionManager->addVoterVote($event->getVoter(), $event->getAttributes(), $event->getVote(true));
3535
}
3636

3737
public static function getSubscribedEvents(): array

src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -518,14 +518,16 @@
518518
<col style="width: 30px">
519519
<col style="width: 120px">
520520
<col style="width: 25%">
521-
<col style="width: 60%">
521+
<col style="width: 40%">
522+
<col style="width: 20%">
522523

523524
<thead>
524525
<tr>
525526
<th>#</th>
526527
<th>Result</th>
527528
<th>Attributes</th>
528529
<th>Object</th>
530+
<th>Message</th>
529531
</tr>
530532
</thead>
531533

@@ -534,7 +536,7 @@
534536
<tr class="voter_result">
535537
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
536538
<td class="font-normal">
537-
{{ decision.result
539+
{{ decision.result.access
538540
? '<span class="label status-success same-width">GRANTED</span>'
539541
: '<span class="label status-error same-width">DENIED</span>'
540542
}}
@@ -554,6 +556,7 @@
554556
{% endif %}
555557
</td>
556558
<td>{{ profiler_dump(decision.seek('object')) }}</td>
559+
<td>{{ decision.result.message }}</td>
557560
</tr>
558561
<tr class="voter_details">
559562
<td></td>
@@ -570,14 +573,17 @@
570573
<td class="font-normal text-small">attribute {{ voter_detail['attributes'][0] }}</td>
571574
{% endif %}
572575
<td class="font-normal text-small">
573-
{% if voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_GRANTED') %}
576+
{% if voter_detail['vote'].access == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_GRANTED') %}
574577
ACCESS GRANTED
575-
{% elseif voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_ABSTAIN') %}
578+
{% elseif voter_detail['vote'].access == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_ABSTAIN') %}
576579
ACCESS ABSTAIN
577-
{% elseif voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_DENIED') %}
580+
{% elseif voter_detail['vote'].access == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_DENIED') %}
578581
ACCESS DENIED
579582
{% else %}
580-
unknown ({{ voter_detail['vote'] }})
583+
unknown ({{ voter_detail['vote'].access }})
584+
{% endif %}
585+
{% if voter_detail['vote'].messages is not empty %}
586+
: {{ voter_detail['vote'].messages | join(', ') }}
581587
{% endif %}
582588
</td>
583589
</tr>

src/Symfony/Bundle/SecurityBundle/Security.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ public function getUser(): ?UserInterface
5858
/**
5959
* Checks if the attributes are granted against the current authentication token and optionally supplied subject.
6060
*/
61-
public function isGranted(mixed $attributes, mixed $subject = null): bool
61+
public function isGranted(mixed $attributes, mixed $subject = null, $asObject = false): bool
6262
{
6363
return $this->container->get('security.authorization_checker')
64-
->isGranted($attributes, $subject);
64+
->isGranted($attributes, $subject, $asObject);
6565
}
6666

6767
public function getToken(): ?TokenInterface

0 commit comments

Comments
 (0)
0