feat: restrict admin role assignment to the privileged roles

This commit is contained in:
Björn Fromme
2026-08-12 17:47:21 +02:00
parent 7a82127494
commit 7ebd2dad1a
4 changed files with 40 additions and 15 deletions
@@ -45,6 +45,7 @@ class EditController extends AbstractController
return $this->render('admin/user/modal_edit.html.twig', [
'user' => $user,
'form' => $form,
'syncedRoles' => Role::syncedOnly($user->getRoles()),
]);
}
@@ -66,6 +67,7 @@ class EditController extends AbstractController
return $this->render('admin/user/modal_edit.html.twig', [
'user' => $user,
'form' => $form,
'syncedRoles' => Role::syncedOnly($user->getRoles()),
]);
}
+19 -7
View File
@@ -12,10 +12,12 @@ use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Lets an administrator assign roles and hotel codes to an existing account.
* Lets an administrator assign the privileged roles and the hotel codes of an existing account.
*
* This is the only way those two get changed after the account was created — BpnAuthenticator
* imports them once and never touches them again.
* Only Role::PRIVILEGED is offered: the remaining roles are synced from the BusPro CRM on every
* login (see BpnAuthenticator), so editing them here would be undone at the user's next login.
* Hotel codes are seeded once at account creation, which makes this form the only way to change
* them afterwards.
*
* @extends AbstractType<User>
*/
@@ -35,14 +37,16 @@ class UserType extends AbstractType
$builder
->add('roles', ChoiceType::class, [
'label' => 'Rollen',
'choices' => array_flip(Role::labels()),
'choices' => $this->privilegedChoices(),
'multiple' => true,
'expanded' => true,
'required' => false,
// User::getRoles() prepends the implicit ROLE_USER, which must not be written back.
'getter' => static fn (User $user): array => array_values(array_diff($user->getRoles(), ['ROLE_USER'])),
'help' => 'Alle übrigen Rollen kommen bei jeder Anmeldung aus BusPro und lassen sich hier nicht ändern.',
// Only the administrator-granted half is editable; the synced half is preserved,
// as is the implicit ROLE_USER, which must never be written back.
'getter' => static fn (User $user): array => Role::privilegedOnly($user->getRoles()),
'setter' => static function (User $user, array $roles): void {
$user->setRoles(array_values(array_unique($roles)));
$user->setRoles(Role::combine($user->getRoles(), $roles));
},
])
->add('hotelCodes', ChoiceType::class, [
@@ -58,6 +62,14 @@ class UserType extends AbstractType
;
}
/**
* @return array<string, string> label => role
*/
private function privilegedChoices(): array
{
return array_flip(array_intersect_key(Role::labels(), array_flip(Role::PRIVILEGED)));
}
/**
* Codes already stored on the account are always offered, even when they are missing from
* the configured catalog — otherwise saving the form would silently drop them.