N°8771 - Add Symfony form component to iTop core (#760)

- Add Symfony Form Component
- Add Symfony CSRF security component
- Add iTop default form template
- Add Twig debug extension to Twig Environment
- Add iTop abstract controller facility to get form builder
- Add Twig filter to make trans an alias of dict_s filter
This commit is contained in:
Benjamin Dalsass
2025-10-10 16:02:25 +02:00
committed by GitHub
parent 82395727bf
commit 5dd450e9bf
605 changed files with 60106 additions and 12 deletions

View File

@@ -0,0 +1,104 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\Authorization\Voter;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
/**
* AuthenticatedVoter votes if an attribute like IS_AUTHENTICATED_FULLY,
* IS_AUTHENTICATED_REMEMBERED, IS_AUTHENTICATED is present.
*
* This list is most restrictive to least restrictive checking.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class AuthenticatedVoter implements CacheableVoterInterface
{
public const IS_AUTHENTICATED_FULLY = 'IS_AUTHENTICATED_FULLY';
public const IS_AUTHENTICATED_REMEMBERED = 'IS_AUTHENTICATED_REMEMBERED';
public const IS_AUTHENTICATED = 'IS_AUTHENTICATED';
public const IS_IMPERSONATOR = 'IS_IMPERSONATOR';
public const IS_REMEMBERED = 'IS_REMEMBERED';
public const PUBLIC_ACCESS = 'PUBLIC_ACCESS';
private AuthenticationTrustResolverInterface $authenticationTrustResolver;
public function __construct(AuthenticationTrustResolverInterface $authenticationTrustResolver)
{
$this->authenticationTrustResolver = $authenticationTrustResolver;
}
public function vote(TokenInterface $token, mixed $subject, array $attributes): int
{
if ($attributes === [self::PUBLIC_ACCESS]) {
return VoterInterface::ACCESS_GRANTED;
}
$result = VoterInterface::ACCESS_ABSTAIN;
foreach ($attributes as $attribute) {
if (null === $attribute || (self::IS_AUTHENTICATED_FULLY !== $attribute
&& self::IS_AUTHENTICATED_REMEMBERED !== $attribute
&& self::IS_AUTHENTICATED !== $attribute
&& self::IS_IMPERSONATOR !== $attribute
&& self::IS_REMEMBERED !== $attribute)) {
continue;
}
$result = VoterInterface::ACCESS_DENIED;
if (self::IS_AUTHENTICATED_FULLY === $attribute
&& $this->authenticationTrustResolver->isFullFledged($token)) {
return VoterInterface::ACCESS_GRANTED;
}
if (self::IS_AUTHENTICATED_REMEMBERED === $attribute
&& ($this->authenticationTrustResolver->isRememberMe($token)
|| $this->authenticationTrustResolver->isFullFledged($token))) {
return VoterInterface::ACCESS_GRANTED;
}
if (self::IS_AUTHENTICATED === $attribute && $this->authenticationTrustResolver->isAuthenticated($token)) {
return VoterInterface::ACCESS_GRANTED;
}
if (self::IS_REMEMBERED === $attribute && $this->authenticationTrustResolver->isRememberMe($token)) {
return VoterInterface::ACCESS_GRANTED;
}
if (self::IS_IMPERSONATOR === $attribute && $token instanceof SwitchUserToken) {
return VoterInterface::ACCESS_GRANTED;
}
}
return $result;
}
public function supportsAttribute(string $attribute): bool
{
return \in_array($attribute, [
self::IS_AUTHENTICATED_FULLY,
self::IS_AUTHENTICATED_REMEMBERED,
self::IS_AUTHENTICATED,
self::IS_IMPERSONATOR,
self::IS_REMEMBERED,
self::PUBLIC_ACCESS,
], true);
}
public function supportsType(string $subjectType): bool
{
return true;
}
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\Authorization\Voter;
/**
* Let voters expose the attributes and types they care about.
*
* By returning false to either `supportsAttribute` or `supportsType`, the
* voter will never be called for the specified attribute or subject.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
interface CacheableVoterInterface extends VoterInterface
{
public function supportsAttribute(string $attribute): bool;
/**
* @param string $subjectType The type of the subject inferred by `get_class` or `get_debug_type`
*/
public function supportsType(string $subjectType): bool;
}

View File

@@ -0,0 +1,99 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\Authorization\Voter;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
/**
* ExpressionVoter votes based on the evaluation of an expression.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ExpressionVoter implements CacheableVoterInterface
{
private ExpressionLanguage $expressionLanguage;
private AuthenticationTrustResolverInterface $trustResolver;
private AuthorizationCheckerInterface $authChecker;
private ?RoleHierarchyInterface $roleHierarchy;
public function __construct(ExpressionLanguage $expressionLanguage, AuthenticationTrustResolverInterface $trustResolver, AuthorizationCheckerInterface $authChecker, ?RoleHierarchyInterface $roleHierarchy = null)
{
$this->expressionLanguage = $expressionLanguage;
$this->trustResolver = $trustResolver;
$this->authChecker = $authChecker;
$this->roleHierarchy = $roleHierarchy;
}
public function supportsAttribute(string $attribute): bool
{
return false;
}
public function supportsType(string $subjectType): bool
{
return true;
}
public function vote(TokenInterface $token, mixed $subject, array $attributes): int
{
$result = VoterInterface::ACCESS_ABSTAIN;
$variables = null;
foreach ($attributes as $attribute) {
if (!$attribute instanceof Expression) {
continue;
}
$variables ??= $this->getVariables($token, $subject);
$result = VoterInterface::ACCESS_DENIED;
if ($this->expressionLanguage->evaluate($attribute, $variables)) {
return VoterInterface::ACCESS_GRANTED;
}
}
return $result;
}
private function getVariables(TokenInterface $token, mixed $subject): array
{
$roleNames = $token->getRoleNames();
if (null !== $this->roleHierarchy) {
$roleNames = $this->roleHierarchy->getReachableRoleNames($roleNames);
}
$variables = [
'token' => $token,
'user' => $token->getUser(),
'object' => $subject,
'subject' => $subject,
'role_names' => $roleNames,
'trust_resolver' => $this->trustResolver,
'auth_checker' => $this->authChecker,
];
// this is mainly to propose a better experience when the expression is used
// in an access control rule, as the developer does not know that it's going
// to be handled by this voter
if ($subject instanceof Request) {
$variables['request'] = $subject;
}
return $variables;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\Authorization\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
/**
* RoleHierarchyVoter uses a RoleHierarchy to determine the roles granted to
* the user before voting.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RoleHierarchyVoter extends RoleVoter
{
private RoleHierarchyInterface $roleHierarchy;
public function __construct(RoleHierarchyInterface $roleHierarchy, string $prefix = 'ROLE_')
{
$this->roleHierarchy = $roleHierarchy;
parent::__construct($prefix);
}
/**
* @return array
*/
protected function extractRoles(TokenInterface $token)
{
return $this->roleHierarchy->getReachableRoleNames($token->getRoleNames());
}
}

View File

@@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\Authorization\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
/**
* RoleVoter votes if any attribute starts with a given prefix.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RoleVoter implements CacheableVoterInterface
{
private string $prefix;
public function __construct(string $prefix = 'ROLE_')
{
$this->prefix = $prefix;
}
public function vote(TokenInterface $token, mixed $subject, array $attributes): int
{
$result = VoterInterface::ACCESS_ABSTAIN;
$roles = $this->extractRoles($token);
foreach ($attributes as $attribute) {
if (!\is_string($attribute) || !str_starts_with($attribute, $this->prefix)) {
continue;
}
$result = VoterInterface::ACCESS_DENIED;
foreach ($roles as $role) {
if ($attribute === $role) {
return VoterInterface::ACCESS_GRANTED;
}
}
}
return $result;
}
public function supportsAttribute(string $attribute): bool
{
return str_starts_with($attribute, $this->prefix);
}
public function supportsType(string $subjectType): bool
{
return true;
}
/**
* @return array
*/
protected function extractRoles(TokenInterface $token)
{
return $token->getRoleNames();
}
}

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\Authorization\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Event\VoteEvent;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
* Decorates voter classes to send result events.
*
* @author Laurent VOULLEMIER <laurent.voullemier@gmail.com>
*
* @internal
*/
class TraceableVoter implements CacheableVoterInterface
{
private VoterInterface $voter;
private EventDispatcherInterface $eventDispatcher;
public function __construct(VoterInterface $voter, EventDispatcherInterface $eventDispatcher)
{
$this->voter = $voter;
$this->eventDispatcher = $eventDispatcher;
}
public function vote(TokenInterface $token, mixed $subject, array $attributes): int
{
$result = $this->voter->vote($token, $subject, $attributes);
$this->eventDispatcher->dispatch(new VoteEvent($this->voter, $subject, $attributes, $result), 'debug.security.authorization.vote');
return $result;
}
public function getDecoratedVoter(): VoterInterface
{
return $this->voter;
}
public function supportsAttribute(string $attribute): bool
{
return !$this->voter instanceof CacheableVoterInterface || $this->voter->supportsAttribute($attribute);
}
public function supportsType(string $subjectType): bool
{
return !$this->voter instanceof CacheableVoterInterface || $this->voter->supportsType($subjectType);
}
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\Authorization\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
/**
* Voter is an abstract default implementation of a voter.
*
* @author Roman Marintšenko <inoryy@gmail.com>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @template TAttribute of string
* @template TSubject of mixed
*/
abstract class Voter implements VoterInterface, CacheableVoterInterface
{
public function vote(TokenInterface $token, mixed $subject, array $attributes): int
{
// abstain vote by default in case none of the attributes are supported
$vote = self::ACCESS_ABSTAIN;
foreach ($attributes as $attribute) {
try {
if (!$this->supports($attribute, $subject)) {
continue;
}
} catch (\TypeError $e) {
if (str_contains($e->getMessage(), 'supports(): Argument #1')) {
continue;
}
throw $e;
}
// as soon as at least one attribute is supported, default is to deny access
$vote = self::ACCESS_DENIED;
if ($this->voteOnAttribute($attribute, $subject, $token)) {
// grant access as soon as at least one attribute returns a positive response
return self::ACCESS_GRANTED;
}
}
return $vote;
}
/**
* Return false if your voter doesn't support the given attribute. Symfony will cache
* that decision and won't call your voter again for that attribute.
*/
public function supportsAttribute(string $attribute): bool
{
return true;
}
/**
* Return false if your voter doesn't support the given subject type. Symfony will cache
* that decision and won't call your voter again for that subject type.
*
* @param string $subjectType The type of the subject inferred by `get_class()` or `get_debug_type()`
*/
public function supportsType(string $subjectType): bool
{
return true;
}
/**
* Determines if the attribute and subject are supported by this voter.
*
* @param mixed $subject The subject to secure, e.g. an object the user wants to access or any other PHP type
*
* @psalm-assert-if-true TSubject $subject
* @psalm-assert-if-true TAttribute $attribute
*/
abstract protected function supports(string $attribute, mixed $subject): bool;
/**
* Perform a single access check operation on a given attribute, subject and token.
* It is safe to assume that $attribute and $subject already passed the "supports()" method check.
*
* @param TAttribute $attribute
* @param TSubject $subject
*/
abstract protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool;
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\Authorization\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
/**
* VoterInterface is the interface implemented by all voters.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface VoterInterface
{
public const ACCESS_GRANTED = 1;
public const ACCESS_ABSTAIN = 0;
public const ACCESS_DENIED = -1;
/**
* Returns the vote for the given parameters.
*
* This method must return one of the following constants:
* ACCESS_GRANTED, ACCESS_DENIED, or ACCESS_ABSTAIN.
*
* @param mixed $subject The subject to secure
* @param array $attributes An array of attributes associated with the method being invoked
*
* @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED
*
* @psalm-return self::ACCESS_* must be transformed into @return on Symfony 7
*/
public function vote(TokenInterface $token, mixed $subject, array $attributes);
}