From 4f7f945582a3f2e1cefc06cc26231b25f403dde1 Mon Sep 17 00:00:00 2001 From: core23 Date: Mon, 24 Jun 2024 18:36:00 +0200 Subject: [PATCH] Enable security information mapping for RoleSecurityHandler --- src/Security/Handler/RoleSecurityHandler.php | 24 +++++++--- .../Handler/RoleSecurityHandlerTest.php | 48 +++++++++++++++++++ 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/src/Security/Handler/RoleSecurityHandler.php b/src/Security/Handler/RoleSecurityHandler.php index 637c697505..0d4d7462df 100644 --- a/src/Security/Handler/RoleSecurityHandler.php +++ b/src/Security/Handler/RoleSecurityHandler.php @@ -141,14 +141,26 @@ private function hasOnlyAdminRoles(mixed $attributes): bool */ private function mapAttributes(mixed $attributes, AdminInterface $admin): array { - // NEXT_MAJOR: Change the foreach to a single check. - foreach ($attributes as $pos => $attribute) { - // If the attribute is not already a ROLE_ we generate the related role. - if (\is_string($attribute) && !str_starts_with($attribute, 'ROLE_')) { - $attributes[$pos] = sprintf($this->getBaseRole($admin), $attribute); + $mappedAttributes = []; + + foreach ($attributes as $attribute) { + if (!\is_string($attribute) || str_starts_with($attribute, 'ROLE_')) { + $mappedAttributes[] = $attribute; + + continue; + } + + $baseRole = $this->getBaseRole($admin); + + $mappedAttributes[] = sprintf($baseRole, $attribute); + + foreach ($admin->getSecurityInformation() as $role => $permissions) { + if (\in_array($attribute, $permissions, true)) { + $mappedAttributes[] = sprintf($baseRole, $role); + } } } - return $attributes; + return array_unique($mappedAttributes); } } diff --git a/tests/Security/Handler/RoleSecurityHandlerTest.php b/tests/Security/Handler/RoleSecurityHandlerTest.php index 76a0b6e92a..aa0ba79028 100644 --- a/tests/Security/Handler/RoleSecurityHandlerTest.php +++ b/tests/Security/Handler/RoleSecurityHandlerTest.php @@ -67,6 +67,54 @@ public function provideGetBaseRoleCases(): iterable yield ['ROLE_FOO_BAR_%s', 'FOO.BAR']; } + /** + * @dataProvider provideIsGrantedWithSecurityInformationCases + * + * @param array $informationMapping + * @param string[] $userRoles + */ + public function testIsGrantedWithSecurityInformation(array $informationMapping, array $userRoles, bool $expected): void + { + $handler = new RoleSecurityHandler($this->authorizationChecker, 'ROLE_SUPER_ADMIN'); + + $subject = new \stdClass(); + + $this->admin + ->method('getCode') + ->willReturn('test'); + $this->admin->expects(static::once()) + ->method('getSecurityInformation') + ->willReturn($informationMapping); + + $this->authorizationChecker + ->method('isGranted') + ->willReturnCallback(static function (mixed $attribute, mixed $subject = null) use ($userRoles): bool { + if ($attribute instanceof Expression) { + $attribute = (string) $attribute; + } + + if (\in_array($attribute, $userRoles, true)) { + return $subject instanceof \stdClass; + } + + return false; + }); + + static::assertSame($expected, $handler->isGranted($this->admin, 'EDIT', $subject)); + } + + /** + * @phpstan-return iterable, string[], bool}> + */ + public function provideIsGrantedWithSecurityInformationCases(): iterable + { + yield 'default mapping' => [[], ['ROLE_TEST_EDIT'], true]; + yield 'with single mapping' => [['VIEW' => ['EDIT', 'SHOW']], ['ROLE_TEST_VIEW'], true]; + yield 'with multiple mappings' => [['WRITE' => ['EDIT', 'SHOW'], 'MANAGE' => ['EDIT', 'SHOW', 'DELETE']], ['ROLE_TEST_MANAGE'], true]; + yield 'with all mapping' => [['ADMIN' => ['ALL']], ['ROLE_TEST_ALL'], true]; + yield 'with missing permission' => [['SHOW' => ['VIEW', 'SHOW']], ['ROLE_TEST_VIEW'], false]; + } + /** * NEXT_MAJOR: Remove the group legacy and only keep string $superAdminRoles and string|Expression $operation in dataProvider. *