diff --git a/src/Controller/Api/UserinfoController.php b/src/Controller/Api/UserinfoController.php index e4a906e..9981af7 100644 --- a/src/Controller/Api/UserinfoController.php +++ b/src/Controller/Api/UserinfoController.php @@ -8,6 +8,7 @@ use App\BusProNet\Model\Notification; use App\BusProNet\Model\PersonalData; use App\Entity\User; use App\Security\Crypt; +use App\Security\Role; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; @@ -52,8 +53,9 @@ class UserinfoController extends AbstractController return new JsonResponse(['message' => $data->message, 'code' => $data->code], Response::HTTP_BAD_REQUEST); } - // Patch current user's roles - $data->roles = $user->getRoles(); + // Patch current user's roles. The implicit ROLE_USER says nothing about the + // account — every authenticated user holds it — and is not exported. + $data->roles = Role::assignedOnly($user->getRoles()); // Patch current user's hotel codes $data->hotelCodes = $user->getHotelCodes(); diff --git a/src/Security/Role.php b/src/Security/Role.php index 836cbc9..46b9651 100644 --- a/src/Security/Role.php +++ b/src/Security/Role.php @@ -72,6 +72,19 @@ final class Role return [] === $importable ? [self::CUSTOMER] : $importable; } + /** + * The roles actually assigned to an account — everything except the implicit ROLE_USER, + * which User::getRoles() prepends and which is never stored. + * + * @param string[] $roles + * + * @return string[] + */ + public static function assignedOnly(array $roles): array + { + return array_values(array_diff($roles, [self::USER])); + } + /** * The non-privileged half of a role set: what BpnAuthenticator syncs from the BusPro CRM. * @@ -81,7 +94,7 @@ final class Role */ public static function syncedOnly(array $roles): array { - return array_values(array_diff($roles, self::PRIVILEGED, [self::USER])); + return array_values(array_diff(self::assignedOnly($roles), self::PRIVILEGED)); } /** diff --git a/src/Twig/AppRuntime.php b/src/Twig/AppRuntime.php index 146794d..fd763c6 100644 --- a/src/Twig/AppRuntime.php +++ b/src/Twig/AppRuntime.php @@ -141,11 +141,7 @@ class AppRuntime implements RuntimeExtensionInterface $mapped = []; - foreach ($roles as $role) { - if ('ROLE_USER' === $role) { - continue; - } - + foreach (Role::assignedOnly($roles) as $role) { $mapped[] = $labels[$role] ?? $role; } diff --git a/tests/Security/RoleTest.php b/tests/Security/RoleTest.php index a38b508..81c6064 100644 --- a/tests/Security/RoleTest.php +++ b/tests/Security/RoleTest.php @@ -38,6 +38,15 @@ class RoleTest extends TestCase self::assertSame([Role::CUSTOMER], Role::filterImportable([])); } + public function testAssignedOnlyDropsTheImplicitRoleUser(): void + { + $roles = Role::assignedOnly([Role::USER, Role::TEAMER, Role::GROUPS_ADMIN]); + + self::assertSame([Role::TEAMER, Role::GROUPS_ADMIN], $roles); + // The value is JSON-encoded into the userinfo response and must not become an object. + self::assertSame(array_keys($roles), range(0, \count($roles) - 1)); + } + public function testCombineKeepsEachHalfInItsOwnLane(): void { $roles = Role::combine([Role::TEAMER, Role::ADMIN], [Role::GROUPS_ADMIN, Role::TEAMER]);