Skip to content

Commit 92962bd

Browse files
feature #61542 [Security] Allow subclassing #[IsGranted] (nicolas-grekas)
This PR was merged into the 7.4 branch. Discussion ---------- [Security] Allow subclassing `#[IsGranted]` | Q | A | ------------- | --- | Branch? | 7.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | - | License | MIT Was missing from #61504 Commits ------- a64980d [Security] Allow subclassing `#[IsGranted]`
2 parents f6e37ef + a64980d commit 92962bd

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

src/Symfony/Component/Security/Http/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ CHANGELOG
88
* Deprecate callable firewall listeners, extend `AbstractListener` or implement `FirewallListenerInterface` instead
99
* Deprecate `AbstractListener::__invoke`
1010
* Add `$methods` argument to `#[IsGranted]` to restrict validation to specific HTTP methods
11-
* Remove `final` keyword from `#[IsGranted]` to allow implementation of custom attributes
11+
* Allow subclassing `#[IsGranted]`
1212

1313
7.3
1414
---

src/Symfony/Component/Security/Http/EventListener/IsGrantedAttributeListener.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,14 @@ public function __construct(
3939

4040
public function onKernelControllerArguments(ControllerArgumentsEvent $event): void
4141
{
42-
if (!$attributes = $event->getAttributes(IsGranted::class)) {
42+
$attributes = [];
43+
foreach ($event->getAttributes() as $class => $attributes[]) {
44+
if (!is_a($class, IsGranted::class, true)) {
45+
array_pop($attributes);
46+
}
47+
}
48+
49+
if (!$attributes = array_merge(...$attributes)) {
4350
return;
4451
}
4552

src/Symfony/Component/Security/Http/Tests/EventListener/IsGrantedAttributeListenerTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
3030
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
3131
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
32+
use Symfony\Component\Security\Http\Attribute\IsGranted;
3233
use Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener;
3334
use Symfony\Component\Security\Http\Tests\Fixtures\IsGrantedAttributeController;
3435
use Symfony\Component\Security\Http\Tests\Fixtures\IsGrantedAttributeMethodsController;
@@ -524,4 +525,59 @@ public function testSkipsAuthorizationWhenMethodDoesNotMatchStringConstraint()
524525
$listener = new IsGrantedAttributeListener($authChecker);
525526
$listener->onKernelControllerArguments($event);
526527
}
528+
529+
public function testFiltersOnlyIsGrantedAttributesUsingInstanceof()
530+
{
531+
$authChecker = $this->createMock(AuthorizationCheckerInterface::class);
532+
$authChecker->expects($this->once())
533+
->method('isGranted')
534+
->with('ROLE_ADMIN')
535+
->willReturn(true);
536+
537+
$controller = [new IsGrantedAttributeMethodsController(), 'admin'];
538+
$event = new ControllerArgumentsEvent(
539+
$this->createMock(HttpKernelInterface::class),
540+
$controller,
541+
[],
542+
new Request(),
543+
null
544+
);
545+
546+
// Inject mixed attributes: one IsGranted and one unrelated object; only IsGranted should be processed
547+
$event->setController($controller, [
548+
IsGranted::class => [new IsGranted('ROLE_ADMIN')],
549+
\stdClass::class => [new \stdClass()],
550+
]);
551+
552+
$listener = new IsGrantedAttributeListener($authChecker);
553+
$listener->onKernelControllerArguments($event);
554+
}
555+
556+
public function testSupportsSubclassOfIsGrantedViaInstanceof()
557+
{
558+
$authChecker = $this->createMock(AuthorizationCheckerInterface::class);
559+
$authChecker->expects($this->once())
560+
->method('isGranted')
561+
->with('ROLE_ADMIN')
562+
->willReturn(true);
563+
564+
$controller = [new IsGrantedAttributeMethodsController(), 'admin'];
565+
$event = new ControllerArgumentsEvent(
566+
$this->createMock(HttpKernelInterface::class),
567+
$controller,
568+
[],
569+
new Request(),
570+
null
571+
);
572+
573+
$custom = new class('ROLE_ADMIN') extends IsGranted {};
574+
575+
// Inject subclass instance; instanceof IsGranted should match
576+
$event->setController($controller, [
577+
$custom::class => [$custom],
578+
]);
579+
580+
$listener = new IsGrantedAttributeListener($authChecker);
581+
$listener->onKernelControllerArguments($event);
582+
}
527583
}

0 commit comments

Comments
 (0)