Add CSRF token validation across backoffice forms

- Added CSRF validations to candidate correction, penalty, answer saving, and elimination forms.
- Updated corresponding Twig templates to include CSRF token inputs.
- Adjusted column count in `tab_result` template to maintain layout consistency.
This commit is contained in:
2026-05-23 14:51:45 +02:00
parent 5dd0e3ded0
commit fde177167a
5 changed files with 24 additions and 2 deletions
@@ -40,6 +40,10 @@ final class PrepareEliminationController extends AbstractController
public function viewElimination(Elimination $elimination, Request $request): Response
{
if ('POST' === $request->getMethod()) {
if (!$this->isCsrfTokenValid('prepare_elimination', $request->request->get('_token'))) {
throw $this->createAccessDeniedException();
}
$elimination->updateFromInputBag($request->request);
$this->em->flush();
@@ -193,6 +193,10 @@ class QuizController extends AbstractController
)]
public function saveCandidateAnswers(Season $season, Quiz $quiz, Question $question, Request $request): RedirectResponse
{
if (!$this->isCsrfTokenValid('candidate_answer', $request->request->get('_token'))) {
throw $this->createAccessDeniedException();
}
if (false === $season->quizzes->contains($quiz)
|| false === $quiz->questions->contains($question)) {
throw new BadRequestHttpException('Invalid quiz or question');
@@ -304,6 +308,10 @@ class QuizController extends AbstractController
throw new MethodNotAllowedHttpException(['POST']);
}
if (!$this->isCsrfTokenValid('candidate_correction', $request->request->get('_token'))) {
throw $this->createAccessDeniedException();
}
$corrections = (float) $request->request->get('corrections');
$this->quizCandidateRepository->setCorrectionsForCandidate($quiz, $candidate, $corrections);
@@ -323,6 +331,10 @@ class QuizController extends AbstractController
throw new MethodNotAllowedHttpException(['POST']);
}
if (!$this->isCsrfTokenValid('candidate_penalty', $request->request->get('_token'))) {
throw $this->createAccessDeniedException();
}
$penalty = (int) $request->request->get('penalty');
$this->quizCandidateRepository->setPenaltyForCandidate($quiz, $candidate, $penalty);
+4
View File
@@ -97,6 +97,10 @@ final class QuizController extends AbstractController
}
if ('POST' === $request->getMethod()) {
if (!$this->isCsrfTokenValid('question', $request->request->get('token'))) {
throw $this->createAccessDeniedException();
}
// TODO: Extract saving answer logic to a service
// Check if candidate is inactive for this quiz
$quizCandidate = $this->quizCandidateRepository->findOneBy(['quiz' => $quiz, 'candidate' => $candidate]);
@@ -15,6 +15,7 @@
<div class="row">
<div class="col-12 col-md-6">
<form method="post">
<input type="hidden" name="_token" value="{{ csrf_token('prepare_elimination') }}">
{%~ for candidate, colour in elimination.data %}
<div class="row mb-3">
<label for="colour-{{ candidate|lower }}" class="col-4 col-form-label">{{ candidate }}</label>
@@ -37,6 +37,7 @@
<td>
<form method="post"
action="{{ path('tvdt_backoffice_modify_correction', {quiz: quiz.id, candidate: candidate.id}) }}">
<input type="hidden" name="_token" value="{{ csrf_token('candidate_correction') }}">
<div class="row">
<div class="col-8">
<input class="form-control form-control-sm" type="number"
@@ -52,7 +53,7 @@
<td>
<form method="post"
action="{{ path('tvdt_backoffice_modify_penalty', {quiz: quiz.id, candidate: candidate.id}) }}">
<input type="hidden" name="_token" value="{{ csrf_token('candidate_answer') }}">
<input type="hidden" name="_token" value="{{ csrf_token('candidate_penalty') }}">
<div class="row">
<div class="col-8">
<input class="form-control form-control-sm" type="number"
@@ -70,7 +71,7 @@
</tr>
{% else %}
<tr>
<td colspan="5">{{ 'No results'|trans }}</td>
<td colspan="6">{{ 'No results'|trans }}</td>
</tr>
{% endfor %}
</tbody>