feat: pending roles from crm to be confirmed by superadmins

This commit is contained in:
Björn Fromme
2026-08-10 12:26:21 +02:00
parent 681734533f
commit b01c2e84c7
26 changed files with 907 additions and 150 deletions
+71 -12
View File
@@ -8,12 +8,36 @@ use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Uid\Uuid;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface, TimestampableEntityInterface
{
use TimestampableEntity;
/**
* Assignable roles and their labels.
*/
public const ROLES = [
'ROLE_ADMIN' => 'Admin',
'ROLE_MANAGER' => 'Reisemanager',
'ROLE_HOUSE_MANAGER' => 'Hausleitung',
'ROLE_TEAMER' => 'Teamer',
];
/**
* Markers for administrative roles a user holds in the BusPro CRM, keyed by the role
* they stand for. They grant no privileges whatsoever and merely make the user show
* up for approval, because administrative roles may only ever be granted manually by
* a super admin.
*/
public const PENDING_ROLES = [
'ROLE_ADMIN' => 'ROLE_ADMIN_PENDING',
'ROLE_MANAGER' => 'ROLE_MANAGER_PENDING',
'ROLE_HOUSE_MANAGER' => 'ROLE_HOUSE_MANAGER_PENDING',
];
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
@@ -176,19 +200,16 @@ class User implements UserInterface, TimestampableEntityInterface
public function getRolesLabels(): array
{
$labels = [];
$labels = self::ROLES;
foreach ($this->roles as $role) {
$labels[] = match ($role) {
'ROLE_ADMIN' => 'Admin',
'ROLE_MANAGER' => 'Reisemanager',
'ROLE_HOUSE_MANAGER' => 'Hausleitung',
'ROLE_TEAMER' => 'Teamer',
default => $role,
};
foreach (self::PENDING_ROLES as $role => $pendingRole) {
$labels[$pendingRole] = self::ROLES[$role].' (nicht freigeschaltet)';
}
return $labels;
return array_map(
static fn (string $role): string => $labels[$role] ?? $role,
$this->roles,
);
}
public function setRoles(array $roles): static
@@ -198,6 +219,29 @@ class User implements UserInterface, TimestampableEntityInterface
return $this;
}
/**
* The manually assignable roles held by the user, i.e. without the implicit ROLE_USER
* and ROLE_SUPER_ADMIN added by getRoles() and without any pending marker. Used to
* edit role assignments: saving them resolves the pending approvals.
*/
public function getAssignedRoles(): array
{
return array_values(array_intersect($this->roles, array_keys(self::ROLES)));
}
/**
* The pending markers currently held by the user.
*/
public function getPendingRoles(): array
{
return array_values(array_intersect($this->roles, array_values(self::PENDING_ROLES)));
}
public function setAssignedRoles(array $roles): static
{
return $this->setRoles($roles);
}
public function hasRole(string $role): bool
{
return in_array($role, $this->getRoles());
@@ -215,6 +259,21 @@ class User implements UserInterface, TimestampableEntityInterface
return $this;
}
/**
* Super admin is an elevation of ROLE_ADMIN, never a standalone grant.
*/
#[Assert\Callback]
public function validateSuperAdmin(ExecutionContextInterface $context): void
{
if (true === $this->superAdmin && false === in_array('ROLE_ADMIN', $this->roles, true)) {
$context
->buildViolation('Nur Admins können zu Superadmins ernannt werden.')
->atPath('superAdmin')
->addViolation()
;
}
}
public function getLastLoginAt(): ?\DateTimeImmutable
{
return $this->lastLoginAt;
@@ -235,9 +294,9 @@ class User implements UserInterface, TimestampableEntityInterface
return 'app_manager_index';
} elseif ($this->hasRole('ROLE_HOUSE_MANAGER')) {
return 'app_house_manager_index';
} else {
return 'app_teamer_index';
}
return 'app_teamer_index';
}
public function eraseCredentials(): void