src/EventsSubscriber/User/ApiCheckRouteAccessSubscriber.php line 35

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\EventsSubscriber\User;
  4. use App\Entity\User;
  5. use App\Services\Creator\CreatorServiceInterface;
  6. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  7. use Symfony\Component\HttpKernel\Event\RequestEvent;
  8. use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
  9. use Symfony\Component\HttpKernel\KernelEvents;
  10. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  11. class ApiCheckRouteAccessSubscriber implements EventSubscriberInterface
  12. {
  13.     private const PATTERN_USER_IN_URL '/\/api\/(v1|v2)\/(creators|sponsors)\/([0-9]+)/';
  14.     private const PATTERN_PROFILE_IN_URL '/\/api\/(v1|v2)\/(creators|sponsors)\/(profiles|dashboards)\/([0-9]+)/';
  15.     public function __construct(
  16.         protected TokenStorageInterface $tokenStorage,
  17.         private readonly CreatorServiceInterface $creatorService,
  18.     ) {
  19.     }
  20.     public static function getSubscribedEvents(): array
  21.     {
  22.         return [
  23.             KernelEvents::REQUEST => [
  24.                 ['checkAccess'],
  25.             ],
  26.         ];
  27.     }
  28.     public function checkAccess(RequestEvent $requestEvent): void
  29.     {
  30.         if (!$requestEvent->getRequest()->attributes->has('_api_operation_name')) {
  31.             return;
  32.         }
  33.         $routeContext $this->parseRouteUserFromUri($requestEvent);
  34.         if ($routeContext === null) {
  35.             return;
  36.         }
  37.         ['segment' => $segment'userId' => $userId] = $routeContext;
  38.         $authorizedUserId $this->getAuthorizedUserIdFromRequest($requestEvent);
  39.         if ($authorizedUserId === null) {
  40.             /** @var User|null $user */
  41.             $user $this->tokenStorage->getToken()?->getUser();
  42.             $authorizedUserId $this->getAuthorizedUserId($user);
  43.         }
  44.         if ($authorizedUserId === null) {
  45.             throw new AccessDeniedHttpException();
  46.         }
  47.         if ($authorizedUserId === $userId) {
  48.             return;
  49.         }
  50.         if (
  51.             $segment === 'creators'
  52.             && $this->creatorService->isLinkedCreatorProfile($authorizedUserId$userId)
  53.         ) {
  54.             return;
  55.         }
  56.         throw new AccessDeniedHttpException();
  57.     }
  58.     /**
  59.      * @return array{segment: string, userId: int}|null
  60.      */
  61.     private function parseRouteUserFromUri(RequestEvent $requestEvent): ?array
  62.     {
  63.         $pathOrUri $requestEvent->getRequest()->getPathInfo() ?: $requestEvent->getRequest()->getUri();
  64.         preg_match(self::PATTERN_USER_IN_URL$pathOrUri$matches);
  65.         if (($matches[2] ?? null) !== null && ($matches[3] ?? null) !== null) {
  66.             return ['segment' => $matches[2], 'userId' => (int) $matches[3]];
  67.         }
  68.         preg_match(self::PATTERN_PROFILE_IN_URL$pathOrUri$matches);
  69.         if (($matches[2] ?? null) !== null && ($matches[4] ?? null) !== null) {
  70.             return ['segment' => $matches[2], 'userId' => (int) $matches[4]];
  71.         }
  72.         return null;
  73.     }
  74.     private function getAuthorizedUserId(?User $user): ?int
  75.     {
  76.         if ($user === null) {
  77.             return null;
  78.         }
  79.         if (method_exists($user'getId')) {
  80.             $id $user->getId();
  81.             if ($id !== null) {
  82.                 return (int) $id;
  83.             }
  84.         }
  85.         $identifier $user->getUserIdentifier();
  86.         return is_numeric($identifier) ? (int) $identifier null;
  87.     }
  88.     private function getAuthorizedUserIdFromRequest(RequestEvent $requestEvent): ?int
  89.     {
  90.         $authorization = (string)$requestEvent->getRequest()->headers->get('Authorization''');
  91.         if (!str_starts_with($authorization'Bearer ')) {
  92.             return null;
  93.         }
  94.         $token substr($authorization7);
  95.         $parts explode('.'$token);
  96.         if (count($parts) < 2) {
  97.             return null;
  98.         }
  99.         $payload $this->decodeJwtPayload($parts[1]);
  100.         $id $payload['id'] ?? null;
  101.         return is_numeric($id) ? (int) $id null;
  102.     }
  103.     private function decodeJwtPayload(string $payload): ?array
  104.     {
  105.         $base64 strtr($payload'-_''+/');
  106.         $padded str_pad($base64strlen($base64) + (strlen($base64) % 4) % 4'='STR_PAD_RIGHT);
  107.         $decoded base64_decode($paddedtrue);
  108.         if ($decoded === false) {
  109.             return null;
  110.         }
  111.         try {
  112.             $json json_decode($decodedtrue512JSON_THROW_ON_ERROR);
  113.         } catch (\JsonException) {
  114.             return null;
  115.         }
  116.         return is_array($json) ? $json null;
  117.     }
  118. }