| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- <?php
- declare(strict_types=1);
- namespace App\Security\Voter\EntityVoter;
- use App\Entity\Access\Access;
- use App\Service\Access\Utils;
- use App\Service\Security\InternalRequestsService;
- use App\Service\Security\SwitchUser;
- use Doctrine\ORM\EntityManagerInterface;
- use Symfony\Bundle\SecurityBundle\Security;
- use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
- use Symfony\Component\Security\Core\Authorization\Voter\Voter;
- /**
- * Base class for custom Voters.
- *
- * This class also defines a default behavior for entity based voters (ex: FileVoter)
- *
- * @see doc/security.md
- */
- abstract class AbstractEntityVoter extends Voter
- {
- protected const READ = 'READ';
- protected const EDIT = 'EDIT';
- protected const CREATE = 'CREATE';
- protected const DELETE = 'DELETE';
- /**
- * The current user if any; access it trough isUserLoggedIn or getUser methods
- * If the current user is null, it has not been fetched already
- * If it is false, there is no user logged in.
- *
- * @phpstan-ignore-next-line faux positif
- */
- private Access|false|null $user = null;
- /**
- * The supported class name. Override it in subclass.
- * Ex:
- * protected ?string $entityClass = File::class;.
- */
- protected static ?string $entityClass = null;
- /**
- * List of supported operations. Override it to restrict.
- *
- * @var array<string>
- */
- protected static array $allowedOperations = [
- self::READ, self::EDIT, self::CREATE, self::DELETE,
- ];
- public function __construct(
- protected Security $security,
- protected Utils $accessUtils,
- private InternalRequestsService $internalRequestsService,
- private EntityManagerInterface $em,
- private SwitchUser $switchUser,
- ) {
- }
- /**
- * Default `supports` method, that uses self::entityClass and self::allowedOperations to determine if the voter
- * supports the subject and attribute.
- */
- protected function supports(string $attribute, mixed $subject): bool
- {
- if (static::$entityClass === null) {
- throw new \RuntimeException('Setup the self::$entityClass property, or override the supports() method');
- }
- return $subject !== null && $subject instanceof static::$entityClass && in_array($attribute, static::$allowedOperations);
- }
- /**
- * Default `voteOnAttribute` method, calling one of the `canXxxx()` method, according to the attribute.
- */
- protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
- {
- switch ($attribute) {
- case self::READ:
- return $this->canView($subject);
- case self::EDIT:
- return $this->canEdit($subject);
- case self::CREATE:
- return $this->canCreate($subject);
- case self::DELETE:
- return $this->canDelete($subject);
- }
- return false;
- }
- /**
- * Does the client have the right to view this resource?
- */
- protected function canView(object $subject): bool
- {
- return false;
- }
- /**
- * Does the client have the right to edit this resource?
- */
- protected function canEdit(object $subject): bool
- {
- return false;
- }
- /**
- * Does the client have the right to create this resource?
- */
- protected function canCreate(object $subject): bool
- {
- return false;
- }
- /**
- * Does the client have the right to delete this resource?
- */
- protected function canDelete(object $subject): bool
- {
- return false;
- }
- /**
- * Returns the current logged in user.
- */
- protected function getUser(): ?Access
- {
- if ($this->user === null) {
- /** @var Access $user */
- $user = $this->security->getUser();
- // <-- Special case of impersonated users: the switch user is not setup yet by symfony, we have to do it "manually"
- $switchHeaderId = $_SERVER['HTTP_X_SWITCH_USER'] ?? null;
- if ($switchHeaderId !== null) {
- $switchAs = $this->em->find(Access::class, $switchHeaderId);
- if (
- $switchAs
- && (
- $this->security->isGranted('ROLE_ALLOWED_TO_SWITCH')
- || $this->switchUser->isAllowedToSwitch($user, $switchAs)
- )
- ) {
- $user = $switchAs;
- }
- }
- // -->
- // If the user is not anonymous, remember it
- $this->user = $user instanceof Access ? $user : false;
- }
- return $this->user !== false ? $this->user : null;
- }
- /**
- * Is the client an authenticated user ?
- */
- protected function isUserLoggedIn(): bool
- {
- return $this->getUser() !== null;
- }
- /**
- * Is the current request a valid internal request?
- *
- * @see doc/internal_requests.md
- * @see doc/security.md
- */
- protected function isValidInternalRequest(): bool
- {
- $clientIp = $_SERVER['REMOTE_ADDR'] ?? null;
- $internalRequestToken = $_SERVER['HTTP_INTERNAL_REQUESTS_TOKEN'] ?? '';
- return $this->internalRequestsService->isAllowed($clientIp, $internalRequestToken);
- }
- }
|