From ea574427fb7f47aebd2b67666c8f6030c3ee7f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Tue, 22 Jul 2025 17:10:33 +0200 Subject: [PATCH] feat: disable/block teamer users as admin --- config/packages/security.yaml | 1 + migrations/Version20250722142834.php | 31 +++++++ .../Admin/Teamer/DisableUserController.php | 81 +++++++++++++++++++ src/Entity/User.php | 53 +++++++++++- src/Form/DisableUserType.php | 46 +++++++++++ src/Security/UserChecker.php | 26 ++++++ .../admin/teamer/modal_disable_user.html.twig | 19 +++++ .../admin/teamer/modal_enable_user.html.twig | 10 +++ .../disposition/modal_call_off.html.twig | 2 +- .../administrative/teamer/index.html.twig | 37 ++++++++- 10 files changed, 300 insertions(+), 6 deletions(-) create mode 100644 migrations/Version20250722142834.php create mode 100644 src/Controller/Admin/Teamer/DisableUserController.php create mode 100644 src/Form/DisableUserType.php create mode 100644 src/Security/UserChecker.php create mode 100644 templates/admin/teamer/modal_disable_user.html.twig create mode 100644 templates/admin/teamer/modal_enable_user.html.twig diff --git a/config/packages/security.yaml b/config/packages/security.yaml index d0c65fb..7400747 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -21,6 +21,7 @@ security: provider: bpn_user_provider custom_authenticators: - App\Security\BpnAuthenticator + user_checker: App\Security\UserChecker switch_user: role: CAN_IMPERSONATE logout: diff --git a/migrations/Version20250722142834.php b/migrations/Version20250722142834.php new file mode 100644 index 0000000..0b7cfe6 --- /dev/null +++ b/migrations/Version20250722142834.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE user ADD disabled_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', ADD disabled_reason LONGTEXT DEFAULT NULL, ADD disabled_reason_internal LONGTEXT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE user DROP disabled_at, DROP disabled_reason, DROP disabled_reason_internal'); + } +} diff --git a/src/Controller/Admin/Teamer/DisableUserController.php b/src/Controller/Admin/Teamer/DisableUserController.php new file mode 100644 index 0000000..aa9d331 --- /dev/null +++ b/src/Controller/Admin/Teamer/DisableUserController.php @@ -0,0 +1,81 @@ +getUser(); + $form = $this->createForm(DisableUserType::class, $user, ['hx_post' => $request->getUri()]); + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $user->setDisabledAt(new \DateTimeImmutable()); + + $this->entityManager->flush(); + + $this->addFlash('success', 'Der Benutzeraccount wurde gesperrt'); + + $this->logger->info('Disable user', [ + 'teamer' => $teamer->getFullName(), + 'user' => $user->getEmail(), + ]); + + return new HxRedirectResponse($this->generateUrl('app_administrative_teamer_index')); + } + + return $this->render('admin/teamer/modal_disable_user.html.twig', [ + 'form' => $form->createView(), + 'teamer' => $teamer, + ]); + } + + #[Route('/administrative/teamer/enable-user/{uuid}', name: 'app_admin_teamer_enable_user')] + #[IsGranted('ROLE_ADMIN')] + public function enable(Teamer $teamer, Request $request): Response + { + $user = $teamer->getUser(); + + if (true === $request->isMethod(Request::METHOD_POST)) { + $user + ->setDisabledAt(null) + ->setDisabledReason(null) + ->setDisabledReasonInternal(null) + ; + + $this->entityManager->flush(); + + $this->addFlash('success', 'Der Benutzeraccount wurde reaktiviert'); + + $this->logger->info('Enable user', [ + 'teamer' => $teamer->getFullName(), + 'user' => $user->getEmail(), + ]); + + return new HxRedirectResponse($this->generateUrl('app_administrative_teamer_index')); + } + + return $this->render('admin/teamer/modal_enable_user.html.twig', [ + 'teamer' => $teamer, + ]); + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index dce5872..385eff1 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -4,6 +4,7 @@ namespace App\Entity; use App\Entity\Traits\TimestampableEntity; use App\Repository\UserRepository; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Uid\Uuid; @@ -57,6 +58,15 @@ class User implements UserInterface, TimestampableEntityInterface #[ORM\Column] private bool $muteNotifications = false; + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + private ?\DateTimeImmutable $disabledAt = null; + + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $disabledReason = null; + + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $disabledReasonInternal = null; + public function __construct() { $this->uuid = Uuid::v4(); @@ -169,7 +179,7 @@ class User implements UserInterface, TimestampableEntityInterface $labels = []; foreach ($this->roles as $role) { - $labels[] = match($role) { + $labels[] = match ($role) { 'ROLE_ADMIN' => 'Admin', 'ROLE_MANAGER' => 'Reisemanager', 'ROLE_HOUSE_MANAGER' => 'Hausleitung', @@ -300,4 +310,45 @@ class User implements UserInterface, TimestampableEntityInterface return false; } + + public function getDisabledAt(): ?\DateTimeImmutable + { + return $this->disabledAt; + } + + public function setDisabledAt(?\DateTimeImmutable $disabledAt): static + { + $this->disabledAt = $disabledAt; + + return $this; + } + + public function isDisabled(): bool + { + return null !== $this->disabledAt; + } + + public function getDisabledReason(): ?string + { + return $this->disabledReason; + } + + public function setDisabledReason(?string $disabledReason): static + { + $this->disabledReason = $disabledReason; + + return $this; + } + + public function getDisabledReasonInternal(): ?string + { + return $this->disabledReasonInternal; + } + + public function setDisabledReasonInternal(?string $disabledReasonInternal): static + { + $this->disabledReasonInternal = $disabledReasonInternal; + + return $this; + } } diff --git a/src/Form/DisableUserType.php b/src/Form/DisableUserType.php new file mode 100644 index 0000000..f537869 --- /dev/null +++ b/src/Form/DisableUserType.php @@ -0,0 +1,46 @@ +add('disabledReason', TextareaType::class, [ + 'label' => 'Begründung', + 'attr' => [ + 'data-controller' => 'textarea-autosize', + 'data-action' => 'textarea-autosize#resize', + ], + 'constraints' => [ + new NotBlank([ + 'message' => 'Bitte gib die Begründung ein', + ]), + ], + ]) + ->add('disabledReasonInternal', TextareaType::class, [ + 'label' => 'Begründung intern', + 'required' => false, + 'attr' => [ + 'data-controller' => 'textarea-autosize', + 'data-action' => 'textarea-autosize#resize', + ], + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); + } +} diff --git a/src/Security/UserChecker.php b/src/Security/UserChecker.php new file mode 100644 index 0000000..93baa21 --- /dev/null +++ b/src/Security/UserChecker.php @@ -0,0 +1,26 @@ +isDisabled()) { + throw new CustomUserMessageAccountStatusException('Dein Account wurde gesperrt: '.$user->getDisabledReason()); + } + } + + public function checkPostAuth(UserInterface $user): void + { + } +} diff --git a/templates/admin/teamer/modal_disable_user.html.twig b/templates/admin/teamer/modal_disable_user.html.twig new file mode 100644 index 0000000..7b43483 --- /dev/null +++ b/templates/admin/teamer/modal_disable_user.html.twig @@ -0,0 +1,19 @@ +{% extends 'htmx_modal.html.twig' %} + +{% block title %}Benutzeraccount sperren{% endblock %} + +{% block content %} + {{ form_start(form) }} +

+ Teamer:in {{ teamer }} +

+
+ {{ form_row(form.disabledReason) }} + {{ form_row(form.disabledReasonInternal) }} +
+ + {{ form_rest(form) }} + {{ form_end(form) }} +{% endblock %} diff --git a/templates/admin/teamer/modal_enable_user.html.twig b/templates/admin/teamer/modal_enable_user.html.twig new file mode 100644 index 0000000..e34148b --- /dev/null +++ b/templates/admin/teamer/modal_enable_user.html.twig @@ -0,0 +1,10 @@ +{% extends 'htmx_confirmation_modal.html.twig' %} + +{% block content %} +
+ Möchtest du den Benutzeraccount von {{ teamer }} wirklich reaktivieren? +
+
+ Der Grund der Sperrung war: {{ teamer.user.disabledReasonInternal ?? teamer.user.disabledReason }} +
+{% endblock %} diff --git a/templates/administrative/disposition/modal_call_off.html.twig b/templates/administrative/disposition/modal_call_off.html.twig index 4498e78..0baafa8 100644 --- a/templates/administrative/disposition/modal_call_off.html.twig +++ b/templates/administrative/disposition/modal_call_off.html.twig @@ -5,7 +5,7 @@ {% block content %} {{ form_start(form) }}

- Teamer {{ disposition.teamer }} + Teamer:in {{ disposition.teamer }}

{{ form_row(form.calledOffBy) }} diff --git a/templates/administrative/teamer/index.html.twig b/templates/administrative/teamer/index.html.twig index cf54d0d..b98850b 100644 --- a/templates/administrative/teamer/index.html.twig +++ b/templates/administrative/teamer/index.html.twig @@ -101,15 +101,44 @@ {{ icon('check') }} {% endif %} - {% if is_granted('CAN_IMPERSONATE', teamer.user) %} - - {{ icon('mask') }} - + {% if teamer.user.disabled %} + {% if is_granted('ROLE_ADMIN') %} + + {% endif %} + {% else %} + {% if is_granted('ROLE_ADMIN') %} + + {% endif %} + {% if is_granted('CAN_IMPERSONATE', teamer.user) %} + + {{ icon('mask') }} + + {% endif %} {% endif %} {{ icon('user') }}
+ {% if teamer.user.disabled %} +
+ gesp. am {{ teamer.user.disabledAt | date('d.m.Y') }} +
+ {% endif %} {% else %}