From 24a123e97a32972810f96dcf3df6c8141c52294b Mon Sep 17 00:00:00 2001 From: Rostislav Vitek Date: Fri, 20 Dec 2024 11:32:48 +0100 Subject: [PATCH 1/4] administrator email now must be unique --- .../Administrator/AdministratorFormType.php | 7 +++ src/Form/Constraints/UniqueEntityField.php | 18 ++++++++ .../UniqueEntityFieldValidator.php | 45 +++++++++++++++++++ src/Migrations/Version20180702111015.php | 2 +- src/Migrations/Version20241220094923.php | 26 +++++++++++ src/Model/Administrator/Administrator.php | 2 +- 6 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 src/Form/Constraints/UniqueEntityField.php create mode 100644 src/Form/Constraints/UniqueEntityFieldValidator.php create mode 100644 src/Migrations/Version20241220094923.php diff --git a/src/Form/Admin/Administrator/AdministratorFormType.php b/src/Form/Admin/Administrator/AdministratorFormType.php index 43b7ddedb8..81381549bf 100644 --- a/src/Form/Admin/Administrator/AdministratorFormType.php +++ b/src/Form/Admin/Administrator/AdministratorFormType.php @@ -6,6 +6,7 @@ use Shopsys\FrameworkBundle\Component\Router\Security\RouteCsrfProtector; use Shopsys\FrameworkBundle\Form\Constraints\Email; +use Shopsys\FrameworkBundle\Form\Constraints\UniqueEntityField; use Shopsys\FrameworkBundle\Form\DisplayOnlyType; use Shopsys\FrameworkBundle\Form\DisplayOnlyUrlType; use Shopsys\FrameworkBundle\Form\GroupType; @@ -88,6 +89,12 @@ public function buildForm(FormBuilderInterface $builder, array $options): void new Constraints\Length( ['max' => 255, 'maxMessage' => 'Email cannot be longer than {{ limit }} characters'], ), + new UniqueEntityField([ + 'entityInstance' => $options['administrator'], + 'message' => 'Administrator with email "{{ value }}" is already registered', + 'fieldName' => 'email', + 'entityName' => Administrator::class, + ]), ], 'label' => t('Email'), ]); diff --git a/src/Form/Constraints/UniqueEntityField.php b/src/Form/Constraints/UniqueEntityField.php new file mode 100644 index 0000000000..86ccfb094b --- /dev/null +++ b/src/Form/Constraints/UniqueEntityField.php @@ -0,0 +1,18 @@ +em->getRepository($constraint->entityName)->findOneBy([$constraint->fieldName => $value]); + + if ($entity !== null && $entity !== $constraint->entityInstance) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $value) + ->setParameter('{{ fieldName }}', $constraint->fieldName) + ->addViolation(); + } + } +} diff --git a/src/Migrations/Version20180702111015.php b/src/Migrations/Version20180702111015.php index 5da823acec..f828f7b57a 100644 --- a/src/Migrations/Version20180702111015.php +++ b/src/Migrations/Version20180702111015.php @@ -25,7 +25,7 @@ public function up(Schema $schema): void $this->sql( 'INSERT INTO administrators (id, username, real_name, password, login_token, email, superadmin) VALUES ' - . '(1, \'superadmin\', \'superadmin\', \'$2y$12$ppwYj/By0pDkiLlE.ssf6uuwCvtfdDfsJJNr84fU59HmxSfj0luSC\', \'\', \'no-reply@shopsys.com\', true),' + . '(1, \'superadmin\', \'superadmin\', \'$2y$12$ppwYj/By0pDkiLlE.ssf6uuwCvtfdDfsJJNr84fU59HmxSfj0luSC\', \'\', \'no-reply.1@shopsys.com\', true),' . '(2, \'admin\', \'admin\', \'$2y$12$tRU86hi0UxWEMQzP08nl..hKiClF.Pj3D1oIcKDL.aA7ph2Vomwh2\', \'\', \'no-reply@shopsys.com\', false)', ); diff --git a/src/Migrations/Version20241220094923.php b/src/Migrations/Version20241220094923.php new file mode 100644 index 0000000000..74951eb712 --- /dev/null +++ b/src/Migrations/Version20241220094923.php @@ -0,0 +1,26 @@ +sql('CREATE UNIQUE INDEX UNIQ_73A716FE7927C74 ON administrators (email)'); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + */ + public function down(Schema $schema): void + { + } +} diff --git a/src/Model/Administrator/Administrator.php b/src/Model/Administrator/Administrator.php index 723ce994c0..97e885208c 100644 --- a/src/Model/Administrator/Administrator.php +++ b/src/Model/Administrator/Administrator.php @@ -80,7 +80,7 @@ class Administrator implements UserInterface, UniqueLoginInterface, TimelimitLog /** * @var string - * @ORM\Column(type="string", length=255) + * @ORM\Column(type="string", length=255, unique=true) */ protected $email; From 72ea1b3847c02995b483f3f15bb12c5fc0e86761 Mon Sep 17 00:00:00 2001 From: Rostislav Vitek Date: Fri, 20 Dec 2024 11:45:19 +0100 Subject: [PATCH 2/4] administrator username uniqueness is now checked using a form contraint instead of try-catching DuplicateUserNameException --- .../Admin/AdministratorController.php | 71 +++++++------------ .../Administrator/AdministratorFormType.php | 6 ++ .../Administrator/AdministratorFacade.php | 23 ------ .../Exception/DuplicateUserNameException.php | 19 ----- 4 files changed, 32 insertions(+), 87 deletions(-) delete mode 100644 src/Model/Administrator/Exception/DuplicateUserNameException.php diff --git a/src/Controller/Admin/AdministratorController.php b/src/Controller/Admin/AdministratorController.php index 87400124b6..054171d40a 100644 --- a/src/Controller/Admin/AdministratorController.php +++ b/src/Controller/Admin/AdministratorController.php @@ -18,7 +18,6 @@ use Shopsys\FrameworkBundle\Model\Administrator\Exception\AdministratorNotFoundException; use Shopsys\FrameworkBundle\Model\Administrator\Exception\DeletingLastAdministratorException; use Shopsys\FrameworkBundle\Model\Administrator\Exception\DeletingSelfException; -use Shopsys\FrameworkBundle\Model\Administrator\Exception\DuplicateUserNameException; use Shopsys\FrameworkBundle\Model\Administrator\Security\AdministratorRolesChangedFacade; use Shopsys\FrameworkBundle\Model\AdminNavigation\BreadcrumbOverrider; use Shopsys\FrameworkBundle\Model\Security\Authenticator; @@ -121,30 +120,21 @@ public function editAction(Request $request, int $id) $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - try { - $this->administratorFacade->edit($id, $administratorData); - - if ($loggedUser->getId() === $id) { - $this->administratorRolesChangedFacade->refreshAdministratorToken($administrator); - } - - $this->addSuccessFlashTwig( - t('Administrator {{ name }} modified'), - [ - 'name' => $administratorData->realName, - 'url' => $this->generateUrl('admin_administrator_edit', ['id' => $administrator->getId()]), - ], - ); - - return $this->redirectToRoute('admin_administrator_list'); - } catch (DuplicateUserNameException $ex) { - $this->addErrorFlashTwig( - t('Login name {{ name }} is already used'), - [ - 'name' => $administratorData->username, - ], - ); + $this->administratorFacade->edit($id, $administratorData); + + if ($loggedUser->getId() === $id) { + $this->administratorRolesChangedFacade->refreshAdministratorToken($administrator); } + + $this->addSuccessFlashTwig( + t('Administrator {{ name }} modified'), + [ + 'name' => $administratorData->realName, + 'url' => $this->generateUrl('admin_administrator_edit', ['id' => $administrator->getId()]), + ], + ); + + return $this->redirectToRoute('admin_administrator_list'); } if ($form->isSubmitted() && !$form->isValid()) { @@ -193,27 +183,18 @@ public function newAction(Request $request) if ($form->isSubmitted() && $form->isValid()) { $administratorData = $form->getData(); - try { - $administrator = $this->administratorFacade->create($administratorData); - $this->administratorPasswordFacade->resetPassword($administrator->getUsername()); - - $this->addSuccessFlashTwig( - t('Administrator {{ name }} created. A link to set a password has been sent to his email.'), - [ - 'name' => $administrator->getRealName(), - 'url' => $this->generateUrl('admin_administrator_edit', ['id' => $administrator->getId()]), - ], - ); - - return $this->redirectToRoute('admin_administrator_list'); - } catch (DuplicateUserNameException $ex) { - $this->addErrorFlashTwig( - t('Login name {{ name }} is already used'), - [ - 'name' => $administratorData->username, - ], - ); - } + $administrator = $this->administratorFacade->create($administratorData); + $this->administratorPasswordFacade->resetPassword($administrator->getUsername()); + + $this->addSuccessFlashTwig( + t('Administrator {{ name }} created. A link to set a password has been sent to his email.'), + [ + 'name' => $administrator->getRealName(), + 'url' => $this->generateUrl('admin_administrator_edit', ['id' => $administrator->getId()]), + ], + ); + + return $this->redirectToRoute('admin_administrator_list'); } if ($form->isSubmitted() && !$form->isValid()) { diff --git a/src/Form/Admin/Administrator/AdministratorFormType.php b/src/Form/Admin/Administrator/AdministratorFormType.php index 81381549bf..6f0c350ecf 100644 --- a/src/Form/Admin/Administrator/AdministratorFormType.php +++ b/src/Form/Admin/Administrator/AdministratorFormType.php @@ -69,6 +69,12 @@ public function buildForm(FormBuilderInterface $builder, array $options): void new Constraints\Length( ['max' => 100, 'maxMessage' => 'Username cannot be longer than {{ limit }} characters'], ), + new UniqueEntityField([ + 'entityInstance' => $options['administrator'], + 'message' => 'Administrator with user name "{{ value }}" is already registered', + 'fieldName' => 'username', + 'entityName' => Administrator::class, + ]), ], 'label' => t('Login name'), ]) diff --git a/src/Model/Administrator/AdministratorFacade.php b/src/Model/Administrator/AdministratorFacade.php index 454396cf26..ce54c2f4b6 100644 --- a/src/Model/Administrator/AdministratorFacade.php +++ b/src/Model/Administrator/AdministratorFacade.php @@ -10,7 +10,6 @@ use Shopsys\FrameworkBundle\Model\Administrator\Exception\DeletingLastAdministratorException; use Shopsys\FrameworkBundle\Model\Administrator\Exception\DeletingSelfException; use Shopsys\FrameworkBundle\Model\Administrator\Exception\DeletingSuperadminException; -use Shopsys\FrameworkBundle\Model\Administrator\Exception\DuplicateUserNameException; use Shopsys\FrameworkBundle\Model\Administrator\Role\AdministratorRoleFacade; use Shopsys\FrameworkBundle\Model\Administrator\Security\Exception\AdministratorIsNotLoggedException; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; @@ -42,11 +41,6 @@ public function __construct( */ public function create(AdministratorData $administratorData): Administrator { - $administratorByUserName = $this->administratorRepository->findByUserName($administratorData->username); - - if ($administratorByUserName !== null) { - throw new DuplicateUserNameException($administratorByUserName->getUsername()); - } $administrator = $this->administratorFactory->create($administratorData); $this->em->persist($administrator); @@ -65,7 +59,6 @@ public function create(AdministratorData $administratorData): Administrator public function edit($administratorId, AdministratorData $administratorData): Administrator { $administrator = $this->administratorRepository->getById($administratorId); - $this->checkUsername($administrator, $administratorData->username); $administrator->edit($administratorData); $this->em->flush(); @@ -75,22 +68,6 @@ public function edit($administratorId, AdministratorData $administratorData): Ad return $administrator; } - /** - * @param \Shopsys\FrameworkBundle\Model\Administrator\Administrator $administrator - * @param string $username - */ - protected function checkUsername(Administrator $administrator, string $username): void - { - $administratorByUserName = $this->administratorRepository->findByUserName($username); - - if ($administratorByUserName !== null - && $administratorByUserName !== $administrator - && $administratorByUserName->getUsername() === $username - ) { - throw new DuplicateUserNameException($administrator->getUsername()); - } - } - /** * @param int $administratorId */ diff --git a/src/Model/Administrator/Exception/DuplicateUserNameException.php b/src/Model/Administrator/Exception/DuplicateUserNameException.php deleted file mode 100644 index 4bddc183d3..0000000000 --- a/src/Model/Administrator/Exception/DuplicateUserNameException.php +++ /dev/null @@ -1,19 +0,0 @@ - Date: Fri, 20 Dec 2024 12:32:41 +0100 Subject: [PATCH 3/4] dump translations --- src/Resources/translations/messages.cs.po | 3 --- src/Resources/translations/messages.en.po | 3 --- src/Resources/translations/validators.cs.po | 9 +++++++++ src/Resources/translations/validators.en.po | 9 +++++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Resources/translations/messages.cs.po b/src/Resources/translations/messages.cs.po index cbf738e004..0e4e0f0afc 100644 --- a/src/Resources/translations/messages.cs.po +++ b/src/Resources/translations/messages.cs.po @@ -2356,9 +2356,6 @@ msgstr "Přihlášení se nepodařilo." msgid "Login name" msgstr "Přihlašovací jméno" -msgid "Login name {{ name }} is already used" -msgstr "Přihlašovací jméno {{ name }} je již použito" - msgid "Login time" msgstr "Čas přihlášení" diff --git a/src/Resources/translations/messages.en.po b/src/Resources/translations/messages.en.po index 8a50dcb2f5..bfdf278fb9 100644 --- a/src/Resources/translations/messages.en.po +++ b/src/Resources/translations/messages.en.po @@ -2356,9 +2356,6 @@ msgstr "" msgid "Login name" msgstr "" -msgid "Login name {{ name }} is already used" -msgstr "" - msgid "Login time" msgstr "" diff --git a/src/Resources/translations/validators.cs.po b/src/Resources/translations/validators.cs.po index 7392740973..e839c485ff 100644 --- a/src/Resources/translations/validators.cs.po +++ b/src/Resources/translations/validators.cs.po @@ -7,6 +7,12 @@ msgstr "" msgid "Address {{ url }} already exists." msgstr "Adresa {{ url }} již existuje." +msgid "Administrator with email \"{{ value }}\" is already registered" +msgstr "Aministrátor s e-mailem \"{{ value }}\" je již registrován" + +msgid "Administrator with user name \"{{ value }}\" is already registered" +msgstr "Aministrátor s uživatelským jménem \"{{ value }}\" je již registrován" + msgid "Amount of money should be greater than or equal to zero." msgstr "Tato částka musí být větší nebo rovna nule." @@ -547,6 +553,9 @@ msgstr "DIČ nesmí být delší než {{ limit }} znaků" msgid "Telephone number cannot be longer than {{ limit }} characters" msgstr "Telefon nesmí být delší než {{ limit }} znaků" +msgid "The \"{{ value }}\" value of \"{{ fieldName }}\" field must be unique" +msgstr "Hodnota \"{{ value }}\" pole \"{{ fieldName }}\" musí být unikátní" + msgid "The amount of money should be {{ limit }} or less." msgstr "Tato částka musí být {{ limit }} nebo méně." diff --git a/src/Resources/translations/validators.en.po b/src/Resources/translations/validators.en.po index 627a23a084..3c0138118f 100644 --- a/src/Resources/translations/validators.en.po +++ b/src/Resources/translations/validators.en.po @@ -7,6 +7,12 @@ msgstr "" msgid "Address {{ url }} already exists." msgstr "" +msgid "Administrator with email \"{{ value }}\" is already registered" +msgstr "" + +msgid "Administrator with user name \"{{ value }}\" is already registered" +msgstr "" + msgid "Amount of money should be greater than or equal to zero." msgstr "" @@ -547,6 +553,9 @@ msgstr "" msgid "Telephone number cannot be longer than {{ limit }} characters" msgstr "" +msgid "The \"{{ value }}\" value of \"{{ fieldName }}\" field must be unique" +msgstr "" + msgid "The amount of money should be {{ limit }} or less." msgstr "" From 6594e3b5a6ff05e4bd65cbfbfa7cace3fa41313c Mon Sep 17 00:00:00 2001 From: Rostislav Vitek Date: Sat, 21 Dec 2024 15:16:45 +0100 Subject: [PATCH 4/4] admin tweaks - add success flash message after setting new password - redirect to homepage after setting new password - redirect to homepage after editing admin self account when he does not have access to administrators section --- src/Controller/Admin/AdministratorController.php | 8 ++++++-- src/Resources/translations/messages.cs.po | 3 +++ src/Resources/translations/messages.en.po | 3 +++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Controller/Admin/AdministratorController.php b/src/Controller/Admin/AdministratorController.php index 054171d40a..8bcfb84eed 100644 --- a/src/Controller/Admin/AdministratorController.php +++ b/src/Controller/Admin/AdministratorController.php @@ -134,7 +134,9 @@ public function editAction(Request $request, int $id) ], ); - return $this->redirectToRoute('admin_administrator_list'); + $redirectRouteName = $this->isGranted(Roles::ROLE_ADMINISTRATOR_VIEW) ? 'admin_administrator_list' : 'admin_default_dashboard'; + + return $this->redirectToRoute($redirectRouteName); } if ($form->isSubmitted() && !$form->isValid()) { @@ -532,7 +534,9 @@ public function setNewPasswordAction(Request $request): Response $this->authenticator->loginAdministrator($administrator); } - return $this->redirectToRoute('admin_administrator_list'); + $this->addSuccessFlash(t('Password has been successfully set.')); + + return $this->redirectToRoute('admin_default_dashboard'); } if ($form->isSubmitted() && !$form->isValid()) { diff --git a/src/Resources/translations/messages.cs.po b/src/Resources/translations/messages.cs.po index 0e4e0f0afc..dfe2142059 100644 --- a/src/Resources/translations/messages.cs.po +++ b/src/Resources/translations/messages.cs.po @@ -3010,6 +3010,9 @@ msgstr "Heslo" msgid "Password again" msgstr "Heslo znovu" +msgid "Password has been successfully set." +msgstr "Heslo bylo úspěšně nastaveno." + msgid "Password has to include uppercase letters, lowercase letters, numbers and must be longer than 10 characters." msgstr "Heslo musí obsahovat velká písmena, malá písmena, číslice a musí být delší než 10 znaků." diff --git a/src/Resources/translations/messages.en.po b/src/Resources/translations/messages.en.po index bfdf278fb9..e29a260eec 100644 --- a/src/Resources/translations/messages.en.po +++ b/src/Resources/translations/messages.en.po @@ -3010,6 +3010,9 @@ msgstr "" msgid "Password again" msgstr "" +msgid "Password has been successfully set." +msgstr "" + msgid "Password has to include uppercase letters, lowercase letters, numbers and must be longer than 10 characters." msgstr ""