Skip to content
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: 1 addition & 1 deletion src/Symfony/Component/Security/Http/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ CHANGELOG
* Deprecate callable firewall listeners, extend `AbstractListener` or implement `FirewallListenerInterface` instead
* Deprecate `AbstractListener::__invoke`
* Add `$methods` argument to `#[IsGranted]` to restrict validation to specific HTTP methods
* Remove `final` keyword from `#[IsGranted]` to allow implementation of custom attributes
* Allow subclassing `#[IsGranted]`

7.3
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,14 @@ public function __construct(

public function onKernelControllerArguments(ControllerArgumentsEvent $event): void
{
if (!$attributes = $event->getAttributes(IsGranted::class)) {
$attributes = [];
foreach ($event->getAttributes() as $class => $attributes[]) {
if (!is_a($class, IsGranted::class, true)) {
array_pop($attributes);
}
}

if (!$attributes = array_merge(...$attributes)) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener;
use Symfony\Component\Security\Http\Tests\Fixtures\IsGrantedAttributeController;
use Symfony\Component\Security\Http\Tests\Fixtures\IsGrantedAttributeMethodsController;
Expand Down Expand Up @@ -524,4 +525,59 @@ public function testSkipsAuthorizationWhenMethodDoesNotMatchStringConstraint()
$listener = new IsGrantedAttributeListener($authChecker);
$listener->onKernelControllerArguments($event);
}

public function testFiltersOnlyIsGrantedAttributesUsingInstanceof()
{
$authChecker = $this->createMock(AuthorizationCheckerInterface::class);
$authChecker->expects($this->once())
->method('isGranted')
->with('ROLE_ADMIN')
->willReturn(true);

$controller = [new IsGrantedAttributeMethodsController(), 'admin'];
$event = new ControllerArgumentsEvent(
$this->createMock(HttpKernelInterface::class),
$controller,
[],
new Request(),
null
);

// Inject mixed attributes: one IsGranted and one unrelated object; only IsGranted should be processed
$event->setController($controller, [
IsGranted::class => [new IsGranted('ROLE_ADMIN')],
\stdClass::class => [new \stdClass()],
]);

$listener = new IsGrantedAttributeListener($authChecker);
$listener->onKernelControllerArguments($event);
}

public function testSupportsSubclassOfIsGrantedViaInstanceof()
{
$authChecker = $this->createMock(AuthorizationCheckerInterface::class);
$authChecker->expects($this->once())
->method('isGranted')
->with('ROLE_ADMIN')
->willReturn(true);

$controller = [new IsGrantedAttributeMethodsController(), 'admin'];
$event = new ControllerArgumentsEvent(
$this->createMock(HttpKernelInterface::class),
$controller,
[],
new Request(),
null
);

$custom = new class('ROLE_ADMIN') extends IsGranted {};

// Inject subclass instance; instanceof IsGranted should match
$event->setController($controller, [
$custom::class => [$custom],
]);

$listener = new IsGrantedAttributeListener($authChecker);
$listener->onKernelControllerArguments($event);
}
}
Loading