Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable security information mapping for RoleSecurityHandler #8192

Merged
merged 2 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading