Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 55f1237

Browse filesBrowse files
[Security] Add #[IsGranted()]
1 parent 7c194b9 commit 55f1237
Copy full SHA for 55f1237

File tree

9 files changed

+524
-4
lines changed
Filter options

9 files changed

+524
-4
lines changed

‎src/Symfony/Bundle/SecurityBundle/Resources/config/security.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Resources/config/security.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator;
4343
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
4444
use Symfony\Component\Security\Http\Controller\UserValueResolver;
45+
use Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener;
4546
use Symfony\Component\Security\Http\Firewall;
4647
use Symfony\Component\Security\Http\FirewallMapInterface;
4748
use Symfony\Component\Security\Http\HttpUtils;
@@ -269,5 +270,9 @@
269270
service('security.expression_language'),
270271
])
271272
->tag('kernel.cache_warmer')
273+
274+
->set('controller.is_granted_attribute_listener', IsGrantedAttributeListener::class)
275+
->args([service('security.authorization_checker')])
276+
->tag('kernel.event_subscriber')
272277
;
273278
};

‎src/Symfony/Bundle/SecurityBundle/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/composer.json
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
"php": ">=8.1",
2020
"composer-runtime-api": ">=2.1",
2121
"ext-xml": "*",
22-
"symfony/config": "^5.4|^6.0",
23-
"symfony/dependency-injection": "^5.4|^6.0",
22+
"symfony/config": "^6.1",
23+
"symfony/dependency-injection": "^6.1",
2424
"symfony/event-dispatcher": "^5.4|^6.0",
25-
"symfony/http-kernel": "^5.4|^6.0",
25+
"symfony/http-kernel": "^6.2",
2626
"symfony/http-foundation": "^5.4|^6.0",
2727
"symfony/password-hasher": "^5.4|^6.0",
2828
"symfony/security-core": "^5.4|^6.0",
+43Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Http\Attribute;
13+
14+
/**
15+
* @author Ryan Weaver <ryan@knpuniversity.com>
16+
*/
17+
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)]
18+
class IsGranted
19+
{
20+
public function __construct(
21+
/**
22+
* Sets the first argument that will be passed to isGranted().
23+
*/
24+
public array|string|null $attributes = null,
25+
26+
/**
27+
* Sets the second argument passed to isGranted().
28+
*/
29+
public array|string|null $subject = null,
30+
31+
/**
32+
* The message of the exception - has a nice default if not set.
33+
*/
34+
public ?string $message = null,
35+
36+
/**
37+
* If set, will throw HttpKernel's HttpException with the given $statusCode.
38+
* If null, Security\Core's AccessDeniedException will be used.
39+
*/
40+
public ?int $statusCode = null,
41+
) {
42+
}
43+
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Http/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Add maximum username length enforcement of 4096 characters in `UserBadge`
8+
* Add `#[IsGranted()]`
89

910
6.0
1011
---
+107Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Http\EventListener;
13+
14+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
16+
use Symfony\Component\HttpKernel\Event\KernelEvent;
17+
use Symfony\Component\HttpKernel\Exception\HttpException;
18+
use Symfony\Component\HttpKernel\KernelEvents;
19+
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
20+
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
21+
use Symfony\Component\Security\Http\Attribute\IsGranted;
22+
23+
/**
24+
* Handles the IsGranted annotation on controllers.
25+
*
26+
* @author Ryan Weaver <ryan@knpuniversity.com>
27+
*/
28+
class IsGrantedAttributeListener implements EventSubscriberInterface
29+
{
30+
public function __construct(
31+
private AuthorizationCheckerInterface $authChecker,
32+
) {
33+
}
34+
35+
public function onKernelControllerArguments(ControllerArgumentsEvent $event)
36+
{
37+
/** @var IsGranted[] $attributes */
38+
if (!\is_array($attributes = $event->getAttributes()[IsGranted::class] ?? null)) {
39+
return;
40+
}
41+
42+
$namedArguments = [];
43+
$arguments = $event->getArguments();
44+
$r = $event->getRequest()->attributes->get('_controller_reflectors')[1] ?? new \ReflectionFunction($event->getController());
45+
46+
foreach ($r->getParameters() as $i => $param) {
47+
if ($param->isVariadic()) {
48+
$namedArguments[$param->name] = \array_slice($arguments, $i);
49+
break;
50+
}
51+
if (\array_key_exists($i, $arguments)) {
52+
$namedArguments[$param->name] = $arguments[$i];
53+
}
54+
}
55+
56+
foreach ($attributes as $attribute) {
57+
$subjectRef = $attribute->subject;
58+
$subject = null;
59+
60+
if ($subjectRef) {
61+
if (\is_array($subjectRef)) {
62+
foreach ($subjectRef as $ref) {
63+
if (!\array_key_exists($ref, $namedArguments)) {
64+
throw new \RuntimeException(sprintf('Could not find the subject "%s" for the #[IsGranted] attribute. Try adding a "$%s" argument to your controller method.', $ref, $ref));
65+
}
66+
$subject[$ref] = $namedArguments[$ref];
67+
}
68+
} elseif (!\array_key_exists($subjectRef, $namedArguments)) {
69+
throw new \RuntimeException(sprintf('Could not find the subject "%s" for the #[IsGranted] attribute. Try adding a "$%s" argument to your controller method.', $subjectRef, $subjectRef));
70+
} else {
71+
$subject = $namedArguments[$subjectRef];
72+
}
73+
}
74+
75+
if (!$this->authChecker->isGranted($attribute->attributes, $subject)) {
76+
$message = $attribute->message ?: sprintf('Access Denied by #[IsGranted(%s)] on controller', $this->getIsGrantedString($attribute));
77+
78+
if ($statusCode = $attribute->statusCode) {
79+
throw new HttpException($statusCode, $message);
80+
}
81+
82+
$accessDeniedException = new AccessDeniedException($message);
83+
$accessDeniedException->setAttributes($attribute->attributes);
84+
$accessDeniedException->setSubject($subject);
85+
86+
throw $accessDeniedException;
87+
}
88+
}
89+
}
90+
91+
public static function getSubscribedEvents(): array
92+
{
93+
return [KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments', 10]];
94+
}
95+
96+
private function getIsGrantedString(IsGranted $isGranted): string
97+
{
98+
$attributes = array_map(fn ($attribute) => '"'.$attribute.'"', (array) $isGranted->attributes);
99+
$argsString = 1 === \count($attributes) ? reset($attributes) : '['.implode(', ', $attributes).']';
100+
101+
if (null !== $isGranted->subject) {
102+
$argsString .= ', "'.implode('", "', (array) $isGranted->subject).'"';
103+
}
104+
105+
return $argsString;
106+
}
107+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.