Skip to content

Commit

Permalink
Enable security information mapping for RoleSecurityHandler (#8192)
Browse files Browse the repository at this point in the history
  • Loading branch information
core23 authored Jul 15, 2024
1 parent 9ecd32d commit 77db467
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 11 deletions.
60 changes: 49 additions & 11 deletions src/Security/Handler/RoleSecurityHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,8 @@ public function isGranted(AdminInterface $admin, $attributes, ?object $object =
$attributes = [$attributes];
}

// NEXT_MAJOR: Change the foreach to a single check.
$useAll = false;
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);
// All the admin related role are available when you have the `_ALL` role.
$useAll = true;
}
}

$useAll = $this->hasOnlyAdminRoles($attributes);
$attributes = $this->mapAttributes($attributes, $admin);
$allRole = sprintf($this->getBaseRole($admin), 'ALL');

try {
Expand Down Expand Up @@ -125,4 +116,51 @@ private function isAnyGranted(array $attributes, ?object $subject = null): bool

return false;
}

/**
* @param array<string|Expression> $attributes
*/
private function hasOnlyAdminRoles(array $attributes): bool
{
// NEXT_MAJOR: Change the foreach to a single check.
foreach ($attributes as $attribute) {
// If the attribute is not already a ROLE_ we generate the related role.
if (\is_string($attribute) && !str_starts_with($attribute, 'ROLE_')) {
return true;
}
}

return false;
}

/**
* @param array<string|Expression> $attributes
* @param AdminInterface<object> $admin
*
* @return array<string|Expression>
*/
private function mapAttributes(array $attributes, AdminInterface $admin): array
{
$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 array_unique($mappedAttributes);
}
}
48 changes: 48 additions & 0 deletions tests/Security/Handler/RoleSecurityHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,54 @@ public function provideGetBaseRoleCases(): iterable
yield ['ROLE_FOO_BAR_%s', 'FOO.BAR'];
}

/**
* @dataProvider provideIsGrantedWithSecurityInformationCases
*
* @param array<string, string[]> $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<array{array<string, string[]>, 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.
*
Expand Down

0 comments on commit 77db467

Please sign in to comment.