<?php declare(strict_types=1);
namespace App\EventSubscriber;
use ApiPlatform\Core\Api\IriConverterInterface;
use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\TimeControl\TimeEntry;
use App\Entity\User;
use App\Repository\UserRepository;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;
class TimeEntryEventSubscriber implements EventSubscriberInterface
{
public function __construct(private Security $security, private IriConverterInterface $iriConverter, private UserRepository $repository)
{
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::VIEW => ['checkRoles', EventPriorities::PRE_WRITE],
];
}
public function checkRoles(ViewEvent $event): void
{
$originTimeEntry = $event->getControllerResult();
if (!$originTimeEntry instanceof TimeEntry) {
return;
}
if ($event->getRequest()->getMethod() === Request::METHOD_DELETE) {
$this->checkRolesOnDelete($originTimeEntry);
}
if (\in_array($event->getRequest()->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT], true)) {
$this->checkRoleOnChange($event, $originTimeEntry);
}
}
protected function checkRolesOnDelete(TimeEntry $originTimeEntry): void
{
$user = $this->security->getUser();
if (!$user instanceof User) {
return;
}
if (!$this->security->isGranted('ROLE_MANAGER') && $originTimeEntry->getUser() !== $user) {
throw new AccessDeniedHttpException('You cannot delete another user\'s entry');
}
if ($this->security->isGranted('ROLE_MANAGER') && !$this->security->isGranted('ROLE_ADMIN')) {
$manager = $user->getManager();
$originProject = $originTimeEntry->getProject();
if ($originTimeEntry->getUser() !== $user) {
if ($originProject) {
if (!$originProject->getManagers()->contains($manager)) {
throw new AccessDeniedHttpException('You cannot delete an entry not in your project');
}
} else {
throw new AccessDeniedHttpException('You cannot delete the entry for this user outside the project');
}
}
}
}
protected function checkRoleOnChange(ViewEvent $event, TimeEntry $originTimeEntry): void
{
$content = $event->getRequest()->getContent();
if (!\is_string($content)) {
throw new \RuntimeException('Request content must be a string');
}
try {
$requestData = \json_decode($content, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new \RuntimeException($e->getMessage());
}
$user = $this->security->getUser();
if (!$user instanceof User) {
throw new \RuntimeException('Cannot get user from security service');
}
$targetUser = $this->loadUser($requestData['user'] ?? null);
if ($targetUser === null) {
return;
}
if ($this->security->isGranted('ROLE_USER') && !$this->security->isGranted('ROLE_MANAGER')) {
if ($targetUser !== $user) {
throw new AccessDeniedHttpException('You cannot change your entry to another user\'s entry');
}
if ($originTimeEntry->getUser() !== $user) {
throw new AccessDeniedHttpException('You cannot change another user\'s entry');
}
}
if ($this->security->isGranted('ROLE_MANAGER') && !$this->security->isGranted('ROLE_ADMIN')) {
$manager = $user->getManager();
$originProject = $originTimeEntry->getProject();
if ($originTimeEntry->getUser() !== $user) {
if ($originProject) {
if (!$originProject->getManagers()->contains($manager)) {
throw new AccessDeniedHttpException('You cannot change an entry not in your project');
}
} else {
throw new AccessDeniedHttpException('You cannot change the entry for this user outside the project');
}
}
}
}
private function loadUser(?string $iri): ?User
{
if ($iri === null) {
return null;
}
$slug = \pathinfo($iri, PATHINFO_FILENAME);
return $this->repository->findOneBy(['slug' => $slug]);
}
}