feat: demotion of user accounts via crm
This commit is contained in:
@@ -218,6 +218,37 @@ class UserDataHandler
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks a user the CRM no longer grants anything in this application.
|
||||
*
|
||||
* The granted roles are deliberately kept: they stay visible for review and are what
|
||||
* makes the user reappear in the administrative list, where a super admin can unblock
|
||||
* them. Only the privilege-free markers are dropped, as they no longer reflect the CRM.
|
||||
* Regaining a CRM role does not unblock the account, that is a manual decision.
|
||||
*/
|
||||
public function disableForRevokedCrmRoles(User $user): void
|
||||
{
|
||||
// an existing block may be a disciplinary one and must never be overwritten
|
||||
if (true === $user->isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->refreshPendingRoles($user, []);
|
||||
|
||||
$user
|
||||
->setDisabledAt(new \DateTimeImmutable())
|
||||
->setDisabledReason('Für deinen Account liegt in BusPro keine Berechtigung mehr vor.')
|
||||
->setDisabledReasonInternal('Automatisch gesperrt: keine Rollen in BusPro.')
|
||||
;
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->logger->info('Disable user without CRM roles', [
|
||||
'user_id' => $user->getId(),
|
||||
'user_email' => $user->getEmail(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps the pending markers in sync with the administrative roles claimed in the CRM.
|
||||
* The markers grant no privileges, so tracking them on every login is safe: only a
|
||||
|
||||
@@ -38,6 +38,7 @@ class EditController extends AbstractController
|
||||
'roles' => $user->getRoles(),
|
||||
'super_admin' => $user->isSuperAdmin(),
|
||||
'hotel_codes' => $user->getHotelCodes(),
|
||||
'disabled' => $user->isDisabled(),
|
||||
]);
|
||||
|
||||
return $this->redirectToRoute('app_admin_system_user_index');
|
||||
|
||||
@@ -387,6 +387,27 @@ class User implements UserInterface, TimestampableEntityInterface
|
||||
return null !== $this->disabledAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks or unblocks the account, keeping the reasons in sync. Unchanged state is a
|
||||
* no-op so that saving an unrelated change does not reset the timestamp.
|
||||
*/
|
||||
public function setDisabled(bool $disabled): static
|
||||
{
|
||||
if ($disabled === $this->isDisabled()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (false === $disabled) {
|
||||
return $this
|
||||
->setDisabledAt(null)
|
||||
->setDisabledReason(null)
|
||||
->setDisabledReasonInternal(null)
|
||||
;
|
||||
}
|
||||
|
||||
return $this->setDisabledAt(new \DateTimeImmutable());
|
||||
}
|
||||
|
||||
public function getDisabledReason(): ?string
|
||||
{
|
||||
return $this->disabledReason;
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Config\HouseCatalog;
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
@@ -32,6 +33,22 @@ class UserType extends AbstractType
|
||||
'required' => false,
|
||||
'help' => 'Setzt die Rolle Admin voraus.',
|
||||
])
|
||||
// must stay ahead of the reason: properties are written in field order and
|
||||
// unblocking clears the reasons
|
||||
->add('disabled', CheckboxType::class, [
|
||||
'label' => 'Account gesperrt',
|
||||
'required' => false,
|
||||
'help' => 'Gesperrte Benutzer:innen können sich nicht anmelden.',
|
||||
])
|
||||
->add('disabledReason', TextareaType::class, [
|
||||
'label' => 'Begründung',
|
||||
'required' => false,
|
||||
'help' => 'Wird bei der Anmeldung angezeigt.',
|
||||
'attr' => [
|
||||
'data-controller' => 'textarea-autosize',
|
||||
'data-action' => 'textarea-autosize#resize',
|
||||
],
|
||||
])
|
||||
;
|
||||
|
||||
// hotel codes already assigned to the user may predate the catalog, so they are
|
||||
|
||||
@@ -124,12 +124,31 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent
|
||||
// Determine teamer status from CRM attributes
|
||||
$isTeamer = $crmAttributes->isTeamer();
|
||||
|
||||
// Everything the CRM grants this person here: pending markers plus ROLE_TEAMER
|
||||
$claimedRoles = $this
|
||||
->userDataHandler
|
||||
->collectRoles($crmAttributes)
|
||||
;
|
||||
|
||||
// Check if user is already present in local database
|
||||
$user = $this
|
||||
->userDataHandler
|
||||
->findLocalUser($profileResponse)
|
||||
;
|
||||
|
||||
// BusPro knows this person but grants them nothing in this application, so they
|
||||
// are no user of it: never create an account, block an existing one. Returning
|
||||
// the blocked user lets the UserChecker explain why the login was refused.
|
||||
if ([] === $claimedRoles) {
|
||||
if (null === $user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->userDataHandler->disableForRevokedCrmRoles($user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
// Update existing user's teamer data and return it, leaving roles and hotel
|
||||
// codes alone: they are imported once on creation and managed manually after
|
||||
if (null !== $user) {
|
||||
@@ -152,7 +171,7 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent
|
||||
->userDataHandler
|
||||
->createLocalUser(
|
||||
$profileResponse,
|
||||
$this->userDataHandler->collectRoles($crmAttributes),
|
||||
$claimedRoles,
|
||||
$isTeamer,
|
||||
$crmSelections,
|
||||
$crmAttributes->getHotelCodes(),
|
||||
|
||||
@@ -21,7 +21,7 @@ class UserChecker implements UserCheckerInterface
|
||||
|
||||
if ([] === array_intersect($user->getRoles(), array_keys(User::ROLES))) {
|
||||
if ([] !== $user->getPendingRoles()) {
|
||||
throw new CustomUserMessageAccountStatusException('Deine Rolle wurde noch nicht freigeschaltet.');
|
||||
throw new CustomUserMessageAccountStatusException('Dein Account wurde noch nicht freigeschaltet.');
|
||||
}
|
||||
|
||||
throw new CustomUserMessageAccountStatusException('Keine gültige Rolle zugewiesen.');
|
||||
|
||||
Reference in New Issue
Block a user