feat: reserve administrative roles for staff email addresses
This commit is contained in:
@@ -182,9 +182,11 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent
|
||||
->setHotelCodes(array_values(array_unique($crmAttributes->hotelCodes)))
|
||||
;
|
||||
|
||||
// Compared on the full role sets rather than on the markers alone: nominatedFrom() needs
|
||||
// to see ROLE_EMPLOYEE to know whether an EMPLOYEE_ONLY marker counts.
|
||||
$nominated = array_values(array_diff(
|
||||
Role::pendingOnly($user->getRoles()),
|
||||
Role::pendingOnly($previousRoles),
|
||||
array_keys(Role::nominatedFrom($user->getRoles())),
|
||||
array_keys(Role::nominatedFrom($previousRoles)),
|
||||
));
|
||||
|
||||
if ([] === $nominated) {
|
||||
@@ -198,9 +200,9 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent
|
||||
'roles' => $nominated,
|
||||
]);
|
||||
|
||||
// Only the newly appeared markers reach this point, so a repeat login with a nomination
|
||||
// still standing announces nothing. That difference is the whole de-duplication.
|
||||
return array_keys(Role::nominatedFrom($nominated));
|
||||
// Only the newly appeared nominations reach this point, so a repeat login with a
|
||||
// nomination still standing announces nothing. That difference is the whole de-duplication.
|
||||
return $nominated;
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
|
||||
|
||||
+45
-4
@@ -23,6 +23,10 @@ namespace App\Security;
|
||||
* That widens a claim from "what the CRM reports" to "what the CRM reports plus what the account
|
||||
* itself implies", and nothing more: ROLE_EMPLOYEE is not administrative, so it cannot reach the
|
||||
* nomination path, and the CRM remains the only source for every role that grants privileges.
|
||||
*
|
||||
* ROLE_EMPLOYEE does gate a few administrative roles, though (see EMPLOYEE_ONLY): for those, a
|
||||
* CRM claim only counts when the account is staff. It narrows what the CRM can nominate and
|
||||
* never widens it — the approval step still applies on top.
|
||||
*/
|
||||
final class Role
|
||||
{
|
||||
@@ -33,6 +37,7 @@ final class Role
|
||||
public const USER = 'ROLE_USER';
|
||||
|
||||
public const ADMIN = 'ROLE_ADMIN';
|
||||
public const TEAM_ADMIN = 'ROLE_TEAM_ADMIN';
|
||||
public const MANAGER = 'ROLE_MANAGER';
|
||||
public const TEAMER = 'ROLE_TEAMER';
|
||||
public const CUSTOMER = 'ROLE_CUSTOMER';
|
||||
@@ -58,6 +63,7 @@ final class Role
|
||||
*/
|
||||
public const ALL = [
|
||||
self::ADMIN,
|
||||
self::TEAM_ADMIN,
|
||||
self::MANAGER,
|
||||
self::TEAMER,
|
||||
self::CUSTOMER,
|
||||
@@ -90,6 +96,7 @@ final class Role
|
||||
*/
|
||||
public const ADMINISTRATIVE = [
|
||||
self::ADMIN,
|
||||
self::TEAM_ADMIN,
|
||||
self::MANAGER,
|
||||
self::HOUSE_MANAGER,
|
||||
self::GROUPS_ADMIN,
|
||||
@@ -97,6 +104,25 @@ final class Role
|
||||
self::CUSTOMER_EXPERT,
|
||||
];
|
||||
|
||||
/**
|
||||
* Administrative roles reserved for staff accounts. A CRM claim for one of these is ignored
|
||||
* unless the account is also claimed as ROLE_EMPLOYEE, so it neither nominates nor keeps a
|
||||
* role approved earlier. BusPro backend users outside the company can edit their own CRM
|
||||
* selections too, and these roles reach far enough that the email domain has to agree.
|
||||
* ROLE_HOUSE_MANAGER is the one administrative role left out: a Hausleitung signs in with
|
||||
* the hotel's own address.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public const EMPLOYEE_ONLY = [
|
||||
self::ADMIN,
|
||||
self::TEAM_ADMIN,
|
||||
self::MANAGER,
|
||||
self::GROUPS_ADMIN,
|
||||
self::GROUPS_MANAGER,
|
||||
self::CUSTOMER_EXPERT,
|
||||
];
|
||||
|
||||
/**
|
||||
* Roles nobody may approve for their own account. ROLE_ADMIN outranks every check in this
|
||||
* application, including the approval surface itself, so it always takes a second
|
||||
@@ -159,7 +185,9 @@ final class Role
|
||||
}
|
||||
|
||||
/**
|
||||
* The roles behind those markers, labelled — what an approver acts on.
|
||||
* The roles behind those markers, labelled — what an approver acts on. A marker for an
|
||||
* EMPLOYEE_ONLY role on an account that is not staff is left out: it predates the rule and
|
||||
* goes on that account's next login, and until then it must not be approvable.
|
||||
*
|
||||
* @param string[] $roles
|
||||
*
|
||||
@@ -170,9 +198,15 @@ final class Role
|
||||
$labels = self::labels();
|
||||
$nominated = [];
|
||||
|
||||
$isEmployee = \in_array(self::EMPLOYEE, $roles, true);
|
||||
|
||||
foreach (self::pendingOnly($roles) as $marker) {
|
||||
$role = self::realRole($marker);
|
||||
|
||||
if (false === $isEmployee && \in_array($role, self::EMPLOYEE_ONLY, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\in_array($role, self::ADMINISTRATIVE, true)) {
|
||||
$nominated[$role] = $labels[$role];
|
||||
}
|
||||
@@ -184,6 +218,8 @@ final class Role
|
||||
/**
|
||||
* The whole policy, applied on every login.
|
||||
*
|
||||
* 0. drop the claims for EMPLOYEE_ONLY roles unless ROLE_EMPLOYEE is claimed as well — they
|
||||
* then count as not made, so the steps below revoke and never mark them;
|
||||
* 1. revoke everything the CRM no longer claims — granted roles and markers alike, which is
|
||||
* what makes BusPro the source of truth;
|
||||
* 2. grant the unconditional roles it claims;
|
||||
@@ -204,6 +240,10 @@ final class Role
|
||||
{
|
||||
$claimed = array_values(array_intersect(self::ALL, array_unique($claimedRoles)));
|
||||
|
||||
if (false === \in_array(self::EMPLOYEE, $claimed, true)) {
|
||||
$claimed = array_values(array_diff($claimed, self::EMPLOYEE_ONLY));
|
||||
}
|
||||
|
||||
$roles = array_values(array_filter(
|
||||
self::assignedOnly($storedRoles),
|
||||
static fn (string $role): bool => \in_array(self::realRole($role), $claimed, true),
|
||||
@@ -224,8 +264,8 @@ final class Role
|
||||
|
||||
/**
|
||||
* Turns a marker into the role it stands for. Refuses anything the account is not nominated
|
||||
* for, so neither a hand-crafted request nor a claim revoked while the confirmation dialog
|
||||
* was open can grant a role the CRM never reported.
|
||||
* for (see nominatedFrom()), so neither a hand-crafted request nor a claim revoked while the
|
||||
* confirmation dialog was open can grant a role the CRM never reported.
|
||||
*
|
||||
* @param string[] $storedRoles
|
||||
*
|
||||
@@ -237,7 +277,7 @@ final class Role
|
||||
{
|
||||
$roles = self::assignedOnly($storedRoles);
|
||||
|
||||
if (false === \in_array(self::pending($role), $roles, true)) {
|
||||
if (false === \array_key_exists($role, self::nominatedFrom($roles))) {
|
||||
throw new \InvalidArgumentException(sprintf('The role "%s" is not pending approval.', $role));
|
||||
}
|
||||
|
||||
@@ -284,6 +324,7 @@ final class Role
|
||||
{
|
||||
$labels = [
|
||||
self::ADMIN => 'Administration',
|
||||
self::TEAM_ADMIN => 'Team Administration',
|
||||
self::MANAGER => 'Manager:in',
|
||||
self::TEAMER => 'Teamer:in',
|
||||
self::CUSTOMER => 'Kund:in',
|
||||
|
||||
Reference in New Issue
Block a user