diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index b18e2745915ef..156b29ab41905 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.3 +--- + + * Add `is_granted_for_user()` Twig function + 7.2 --- diff --git a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php index 863df15606735..9bf346caefc37 100644 --- a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php @@ -13,7 +13,9 @@ use Symfony\Component\Security\Acl\Voter\FieldVote; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Authorization\UserAuthorizationCheckerInterface; use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; +use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; @@ -28,6 +30,7 @@ final class SecurityExtension extends AbstractExtension public function __construct( private ?AuthorizationCheckerInterface $securityChecker = null, private ?ImpersonateUrlGenerator $impersonateUrlGenerator = null, + private ?UserAuthorizationCheckerInterface $userSecurityChecker = null, ) { } @@ -48,6 +51,19 @@ public function isGranted(mixed $role, mixed $object = null, ?string $field = nu } } + public function isGrantedForUser(UserInterface $user, mixed $attribute, mixed $subject = null, ?string $field = null): bool + { + if (!$this->userSecurityChecker) { + throw new \LogicException(\sprintf('An instance of "%s" must be provided to use "%s()".', UserAuthorizationCheckerInterface::class, __METHOD__)); + } + + if ($field) { + $subject = new FieldVote($subject, $field); + } + + return $this->userSecurityChecker->isGrantedForUser($user, $attribute, $subject); + } + public function getImpersonateExitUrl(?string $exitTo = null): string { if (null === $this->impersonateUrlGenerator) { @@ -86,12 +102,18 @@ public function getImpersonatePath(string $identifier): string public function getFunctions(): array { - return [ + $functions = [ new TwigFunction('is_granted', $this->isGranted(...)), new TwigFunction('impersonation_exit_url', $this->getImpersonateExitUrl(...)), new TwigFunction('impersonation_exit_path', $this->getImpersonateExitPath(...)), new TwigFunction('impersonation_url', $this->getImpersonateUrl(...)), new TwigFunction('impersonation_path', $this->getImpersonatePath(...)), ]; + + if ($this->userSecurityChecker) { + $functions[] = new TwigFunction('is_granted_for_user', $this->isGrantedForUser(...)); + } + + return $functions; } } diff --git a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php index 5da9a1484ac94..16421eaf504d4 100644 --- a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php +++ b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php @@ -61,6 +61,7 @@ class UndefinedCallableHandler 'logout_url' => 'security-http', 'logout_path' => 'security-http', 'is_granted' => 'security-core', + 'is_granted_for_user' => 'security-core', 'impersonation_path' => 'security-http', 'impersonation_url' => 'security-http', 'impersonation_exit_path' => 'security-http', diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/templating_twig.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/templating_twig.php index 05a74d086e820..96a7a2833a443 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/templating_twig.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/templating_twig.php @@ -26,6 +26,7 @@ ->args([ service('security.authorization_checker')->ignoreOnInvalid(), service('security.impersonate_url_generator')->ignoreOnInvalid(), + service('security.user_authorization_checker')->ignoreOnInvalid(), ]) ->tag('twig.extension') ;