diff --git a/webapp/config/services.yaml b/webapp/config/services.yaml index 205c5ce8f7..2ae2a05c43 100644 --- a/webapp/config/services.yaml +++ b/webapp/config/services.yaml @@ -11,6 +11,8 @@ parameters: # Enable this to support removing time intervals from the contest. # This code is rarely tested and we discourage using it. removed_intervals: false + # Minimum password length for users + min_password_length: 10 services: # default configuration for services in *this* file diff --git a/webapp/src/Controller/Jury/UserController.php b/webapp/src/Controller/Jury/UserController.php index 3000e5650f..de13152405 100644 --- a/webapp/src/Controller/Jury/UserController.php +++ b/webapp/src/Controller/Jury/UserController.php @@ -16,6 +16,7 @@ use App\Service\SubmissionService; use App\Utils\Utils; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -42,6 +43,8 @@ public function __construct( KernelInterface $kernel, protected readonly EventLogService $eventLogService, protected readonly TokenStorageInterface $tokenStorage, + #[Autowire(param: 'min_password_length')] + private readonly int $minimumPasswordLength, ) { parent::__construct($em, $eventLogService, $dj, $kernel); } @@ -197,12 +200,12 @@ public function viewAction(int $userId, SubmissionService $submissionService): R public function checkPasswordLength(User $user, FormInterface $form): ?Response { - if ($user->getPlainPassword() && strlen($user->getPlainPassword()) < static::MIN_PASSWORD_LENGTH) { - $this->addFlash('danger', "Password should be " . static::MIN_PASSWORD_LENGTH . "+ chars."); + if ($user->getPlainPassword() && strlen($user->getPlainPassword()) < $this->minimumPasswordLength) { + $this->addFlash('danger', "Password should be " . $this->minimumPasswordLength . "+ chars."); return $this->render('jury/user_edit.html.twig', [ 'user' => $user, 'form' => $form, - 'min_password_length' => static::MIN_PASSWORD_LENGTH, + 'min_password_length' => $this->minimumPasswordLength, ]); } @@ -245,7 +248,6 @@ public function editAction(Request $request, int $userId): Response return $this->render('jury/user_edit.html.twig', [ 'user' => $user, 'form' => $form, - 'min_password_length' => static::MIN_PASSWORD_LENGTH, ]); } @@ -295,7 +297,6 @@ function () use ($user, $form) { return $this->render('jury/user_add.html.twig', [ 'user' => $user, 'form' => $form, - 'min_password_length' => static::MIN_PASSWORD_LENGTH, ]); } diff --git a/webapp/src/Controller/SecurityController.php b/webapp/src/Controller/SecurityController.php index 4b74f801a4..4f5c2fab3c 100644 --- a/webapp/src/Controller/SecurityController.php +++ b/webapp/src/Controller/SecurityController.php @@ -2,6 +2,7 @@ namespace App\Controller; +use App\Controller\Jury\UserController; use App\Entity\Team; use App\Entity\TeamAffiliation; use App\Entity\TeamCategory; @@ -12,6 +13,8 @@ use Doctrine\ORM\EntityManagerInterface; use Ramsey\Uuid\Uuid; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\HttpException; @@ -25,7 +28,9 @@ class SecurityController extends AbstractController public function __construct( private readonly DOMJudgeService $dj, private readonly ConfigurationService $config, - private readonly EntityManagerInterface $em + private readonly EntityManagerInterface $em, + #[Autowire(param: 'min_password_length')] + private readonly int $minimumPasswordLength, ) {} #[Route(path: '/login', name: 'login')] @@ -103,7 +108,12 @@ public function registerAction( $registration_form->handleRequest($request); if ($registration_form->isSubmitted() && $registration_form->isValid()) { $plainPass = $registration_form->get('plainPassword')->getData(); - $password = $passwordHasher->hashPassword($user, $plainPass); + if (strlen($plainPass) < $this->minimumPasswordLength) { + $this->addFlash('danger', "Password should be " . $this->minimumPasswordLength . "+ chars."); + return $this->redirectToRoute('register'); + } + + $password = $passwordHasher->hashPassword($user, $plainPass); $user->setPassword($password); if ((string)$user->getName() === '') { $user->setName($user->getUsername()); diff --git a/webapp/src/Form/Type/UserRegistrationType.php b/webapp/src/Form/Type/UserRegistrationType.php index 66ac1b91e3..0e42d1ea45 100644 --- a/webapp/src/Form/Type/UserRegistrationType.php +++ b/webapp/src/Form/Type/UserRegistrationType.php @@ -2,6 +2,7 @@ namespace App\Form\Type; +use App\Controller\Jury\UserController; use App\Entity\Role; use App\Entity\Team; use App\Entity\TeamAffiliation; @@ -12,6 +13,7 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\EmailType; @@ -35,7 +37,9 @@ class UserRegistrationType extends AbstractType public function __construct( protected readonly DOMJudgeService $dj, protected readonly ConfigurationService $config, - protected readonly EntityManagerInterface $em + protected readonly EntityManagerInterface $em, + #[Autowire(param: 'min_password_length')] + private readonly int $minimumPasswordLength, ) {} public function buildForm(FormBuilderInterface $builder, array $options): void @@ -174,7 +178,9 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'placeholder' => 'Password', 'autocomplete' => 'new-password', 'spellcheck' => 'false', + 'minlength' => $this->minimumPasswordLength, ], + 'help' => sprintf('Minimum length: %d characters', $this->minimumPasswordLength), ], 'second_options' => [ 'label' => false, @@ -182,6 +188,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'placeholder' => 'Repeat Password', 'autocomplete' => 'new-password', 'spellcheck' => 'false', + 'minlength' => $this->minimumPasswordLength, ], ], 'mapped' => false, diff --git a/webapp/src/Form/Type/UserType.php b/webapp/src/Form/Type/UserType.php index 73002221d6..73e869ca73 100644 --- a/webapp/src/Form/Type/UserType.php +++ b/webapp/src/Form/Type/UserType.php @@ -9,6 +9,7 @@ use App\Service\EventLogService; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; @@ -21,7 +22,12 @@ class UserType extends AbstractExternalIdEntityType { - public function __construct(protected readonly EntityManagerInterface $em, EventLogService $eventLogService) + public function __construct( + protected readonly EntityManagerInterface $em, + EventLogService $eventLogService, + #[Autowire(param: 'min_password_length')] + private readonly int $minimumPasswordLength, + ) { parent::__construct($eventLogService); } @@ -100,10 +106,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $form->add('plainPassword', PasswordType::class, [ 'required' => false, 'label' => 'Password', - 'help' => sprintf('Currently %s - fill to change. Any current login session of the user will be terminated.', $set), + 'help' => sprintf('Currently %s - fill to change. Any current login session of the user will be terminated. Minimum length: %d characters', $set, $this->minimumPasswordLength), 'attr' => [ 'autocomplete' => 'new-password', - 'minlength' => UserController::MIN_PASSWORD_LENGTH, + 'minlength' => $this->minimumPasswordLength, ], ]); }); diff --git a/webapp/templates/security/register.html.twig b/webapp/templates/security/register.html.twig index 90075a9ec1..47134dc690 100644 --- a/webapp/templates/security/register.html.twig +++ b/webapp/templates/security/register.html.twig @@ -13,6 +13,15 @@
DOMjudge +
+
+
+
+ {% block messages %} + {% include 'partials/messages.html.twig' %} + {% endblock %} +
+
{{ form_start(registration_form, { 'attr': {'class': 'form-signin'} }) }}

Register Account

diff --git a/webapp/tests/Unit/Controller/PublicControllerTest.php b/webapp/tests/Unit/Controller/PublicControllerTest.php index c76e7e6859..c53b43ea77 100644 --- a/webapp/tests/Unit/Controller/PublicControllerTest.php +++ b/webapp/tests/Unit/Controller/PublicControllerTest.php @@ -210,17 +210,17 @@ public function selfRegisterProvider(): Generator continue; } yield[['username'=>'minimaluser', 'teamName'=>'NewTeam','affiliation'=>'none'],'shirt-recognize-bar-together', $fixtures, $category]; - yield[['username'=>'bruteforce', 'teamName'=>'Fib(4)','affiliation'=>'none'],'0112', $fixtures, $category]; - yield[['username'=>'fullUser', 'name'=>'Full User', 'email'=>'email@domain.com','teamName'=>'Trial','affiliation'=>'none'],'.', $fixtures, $category]; + yield[['username'=>'bruteforce', 'teamName'=>'Fib(9)','affiliation'=>'none'],'01123581321', $fixtures, $category]; + yield[['username'=>'fullUser', 'name'=>'Full User', 'email'=>'email@domain.com','teamName'=>'Trial','affiliation'=>'none'],'..........', $fixtures, $category]; yield[['username'=>'student@', 'teamName'=>'Student@Uni', 'affiliation'=>'new','affiliationName'=>'NewUni','affiliationShortName'=>'nu'],'p@ssword_Is_long', $fixtures, $category]; yield[['username'=>'winner@', 'teamName'=>'FunnyTeamname', 'affiliation'=>'new','affiliationName'=>'SomeUni','affiliationShortName'=>'su','affiliationCountry'=>'SUR'],'p@ssword_Is_long', $fixtures, $category]; yield[['username'=>'klasse', 'teamName'=>'Klasse', 'affiliation'=>'existing','existingAffiliation'=>'1'],'p@ssword_Is_long', $fixtures, $category]; yield[['username'=>'newinstsamecountry', 'name'=>'CompetingDutchTeam', 'teamName'=>'SupperT3@m','affiliation'=>'new','affiliationName'=>'Vrije Universiteit', - 'affiliationShortName'=>'vu','affiliationCountry'=>'NLD'],'demo', $fixtures, $category]; + 'affiliationShortName'=>'vu','affiliationCountry'=>'NLD'],'demodemodemo', $fixtures, $category]; if (count($fixtures)===1) { - yield[['username'=>'reusevaluesofexistinguser', 'name'=>'selfregistered user for example team','email'=>'electronic@mail.tld','teamName'=>'EasyEnough','affiliation'=>'none'],'demo', [...$fixtures, SelfRegisteredUserFixture::class],'']; + yield[['username'=>'reusevaluesofexistinguser', 'name'=>'selfregistered user for example team','email'=>'electronic@mail.tld','teamName'=>'EasyEnough','affiliation'=>'none'],'demodemodemo', [...$fixtures, SelfRegisteredUserFixture::class],'']; } } }