feat: improved assignment access checks with dedicated messages

This commit is contained in:
Björn Fromme
2025-12-03 09:22:59 +01:00
parent e54e3e85b9
commit 2e34aea17e
2 changed files with 41 additions and 59 deletions
@@ -31,6 +31,27 @@ class CreateController extends AbstractController
/** @var User $user */ /** @var User $user */
$user = $this->getUser(); $user = $this->getUser();
$teamer = $user->getTeamer(); $teamer = $user->getTeamer();
// Only allow applications on empty slots
$availableSlots = (int) $assignment->getAvailableDispositions();
if (0 < $availableSlots) {
$this->addFlash('error', 'Auf diesem Einsatz sind bereits alle Plätze belegt');
return $this->redirectToRoute('app_teamer_index');
}
// Only teamers without existing applications may apply to the assignment
$existingApplication = $this
->entityManager
->getRepository(Application::class)
->findOneBy(['teamer' => $teamer, 'assignment' => $assignment]);
if (null !== $existingApplication) {
$this->addFlash('error', 'Du hast dich bereits auf diesen Einsatz beworben');
return $this->redirectToRoute('app_teamer_index');
}
$application = new Application($assignment, $teamer); $application = new Application($assignment, $teamer);
$form = $this->createForm(TeamerApplicationType::class, $application); $form = $this->createForm(TeamerApplicationType::class, $application);
+20 -59
View File
@@ -2,10 +2,7 @@
namespace App\Security\Voter; namespace App\Security\Voter;
use App\Entity\Application;
use App\Entity\Assignment; use App\Entity\Assignment;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Authorization\Voter\Voter;
@@ -18,10 +15,8 @@ class AssignmentVoter extends Voter
public const PUBLISH = 'PUBLISH'; public const PUBLISH = 'PUBLISH';
public const CALL_OFF = 'CALL_OFF'; public const CALL_OFF = 'CALL_OFF';
public function __construct( public function __construct(private readonly Security $security)
private readonly Security $security, {
private readonly EntityManagerInterface $entityManager,
) {
} }
protected function supports(string $attribute, mixed $subject): bool protected function supports(string $attribute, mixed $subject): bool
@@ -30,65 +25,31 @@ class AssignmentVoter extends Voter
return false; return false;
} }
return in_array($attribute, [static::VIEW, static::EDIT, static::APPLY, static::PUBLISH, static::CALL_OFF]); return in_array($attribute, [self::VIEW, self::EDIT, self::APPLY, self::PUBLISH, self::CALL_OFF], true);
} }
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{ {
/** @var Assignment $assignment */ if (null === $token->getUser()) {
$assignment = $subject;
// All authenticated users may view assignments
if (static::VIEW === $attribute && null !== $token->getUser()) {
return true;
}
// Only users with administrative role may edit assignments
if (static::EDIT === $attribute) {
return $this->security->isGranted('ROLE_ADMINISTRATIVE');
}
// Only users with administrative role may publish assignments currently in draft
if (static::PUBLISH === $attribute) {
return Assignment::STATUS_DRAFT === $assignment->getStatus()
&& $this->security->isGranted('ROLE_ADMINISTRATIVE');
}
// Only users with administrative role may call off assignments
if (static::CALL_OFF === $attribute) {
return Assignment::STATUS_CALLED_OFF !== $assignment->getStatus()
&& $this->security->isGranted('ROLE_ADMINISTRATIVE');
}
// Only allow applications to published, not deleted and not called off assignments
if (
true === in_array($assignment->getStatus(), [
Assignment::STATUS_DRAFT,
Assignment::STATUS_DELETED,
Assignment::STATUS_CALLED_OFF,
])) {
return false; return false;
} }
// Only allow applications on empty slots /** @var Assignment $assignment */
$availableSlots = (int) $assignment->getAvailableDispositions(); $assignment = $subject;
if (0 < $availableSlots) {
return $assignment->getDispositions()->count() < $availableSlots;
}
// Only teamers without existing applications may apply to the assignment return match ($attribute) {
if ($this->security->isGranted('ROLE_TEAMER')) { self::VIEW => true,
/** @var User $user */ self::EDIT => $this->security->isGranted('ROLE_ADMINISTRATIVE'),
$user = $token->getUser(); self::PUBLISH => Assignment::STATUS_DRAFT === $assignment->getStatus()
$teamer = $user->getTeamer(); && $this->security->isGranted('ROLE_ADMINISTRATIVE'),
$application = $this self::CALL_OFF => Assignment::STATUS_CALLED_OFF !== $assignment->getStatus()
->entityManager && $this->security->isGranted('ROLE_ADMINISTRATIVE'),
->getRepository(Application::class) self::APPLY => false === in_array($assignment->getStatus(), [
->findOneBy(['teamer' => $teamer, 'assignment' => $assignment]); Assignment::STATUS_DRAFT,
Assignment::STATUS_DELETED,
return null === $application; Assignment::STATUS_CALLED_OFF,
} ], true),
default => false,
return false; };
} }
} }