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

Roles matrix group by admin and admin group #1587

Merged
merged 6 commits into from
Feb 2, 2023
Merged
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
18 changes: 14 additions & 4 deletions src/Resources/views/Form/roles_matrix.html.twig
Original file line number Diff line number Diff line change
@@ -11,17 +11,26 @@ file that was distributed with this source code.
<table class="table table-condensed">
<thead>
<tr>
<th></th>
<th></th>
{% for label in permission_labels|sort %}
<th> {{ label }} </th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for admin_label, roles in grouped_roles %}
<tr>
<th>{{ admin_label }}</th>
{% for group_code, admin_roles in grouped_roles %}
{% set new_group = true %}
{% for admin_code, roles in admin_roles %}
<tr>
{% for role, attributes in roles|sort %}
{% if loop.first %}
{% if new_group %}
{% set new_group = false %}
<th rowspan="{{ admin_roles|length }}" scope="rowgroup">{{ attributes.group_label|default('') }}</th>
{% endif %}
<th>{{ attributes.admin_label|default('') }}</th>
{% endif %}
<td>
{{ form_widget(attributes.form, { label: false }) }}
{% if not attributes.is_granted %}
@@ -34,7 +43,8 @@ file that was distributed with this source code.
{% endif %}
</td>
{% endfor %}
</tr>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
70 changes: 54 additions & 16 deletions src/Security/RolesBuilder/AdminRolesBuilder.php
Original file line number Diff line number Diff line change
@@ -21,6 +21,8 @@

/**
* @author Silas Joisten <silasjoisten@hotmail.de>
*
* @phpstan-import-type Role from RolesBuilderInterface
*/
final class AdminRolesBuilder implements AdminRolesBuilderInterface
{
@@ -76,27 +78,63 @@ public function addExcludeAdmin(string $exclude): void

public function getRoles(?string $domain = null): array
{
$adminServiceCodes = array_diff($this->pool->getAdminServiceCodes(), $this->excludeAdmins);

// get groups and admins sort by config
$adminRoles = [];
foreach ($this->pool->getAdminServiceCodes() as $code) {
if (\in_array($code, $this->excludeAdmins, true)) {
continue;
}
foreach ($this->pool->getAdminGroups() as $groupCode => $group) {
foreach ($group['items'] as $item) {
if (!isset($item['admin'])) {
continue;
}

$key = array_search($item['admin'], $adminServiceCodes, true);
if (false === $key) {
continue;
}
unset($adminServiceCodes[$key]);

$admin = $this->pool->getInstance($code);
$securityHandler = $admin->getSecurityHandler();
$baseRole = $securityHandler->getBaseRole($admin);
foreach (array_keys($admin->getSecurityInformation()) as $key) {
$role = sprintf($baseRole, $key);
$adminRoles[$role] = [
'role' => $role,
'label' => $key,
'role_translated' => $this->translateRole($role, $domain),
'is_granted' => $this->isMaster($admin) || $this->authorizationChecker->isGranted($role),
'admin_label' => $admin->getTranslator()->trans($admin->getLabel() ?? ''),
];
$groupLabelTranslated = $this->translator->trans($group['label'], [], $group['translation_domain']);
piddubnij marked this conversation as resolved.
Show resolved Hide resolved

$adminRoles = array_merge($adminRoles, $this->getAdminRolesByAdminCode($item['admin'], $domain, $groupLabelTranslated, $groupCode));
piddubnij marked this conversation as resolved.
Show resolved Hide resolved
}
}

// admin with config "show_in_dashboard" set "false" or group not set, does not have group
foreach ($adminServiceCodes as $code) {
$adminRoles = array_merge($adminRoles, $this->getAdminRolesByAdminCode($code, $domain));
}

return $adminRoles;
}

/**
* @return array<string, array<string, string|bool>>
*
* @phpstan-return array<string, Role>
*/
private function getAdminRolesByAdminCode(string $code, ?string $domain = null, string $groupLabelTranslated = '', string $groupCode = ''): array
{
$adminRoles = [];
$admin = $this->pool->getInstance($code);
$securityHandler = $admin->getSecurityHandler();
$baseRole = $securityHandler->getBaseRole($admin);
$adminLabelTranslated = $admin->getTranslator()->trans($admin->getLabel() ?? '', [], $admin->getTranslationDomain());
$isMasterAdmin = $this->isMaster($admin);
foreach (array_keys($admin->getSecurityInformation()) as $key) {
$role = sprintf($baseRole, $key);
$adminRoles[$role] = [
'role' => $role,
'label' => $key,
'role_translated' => $this->translateRole($role, $domain),
'is_granted' => $isMasterAdmin || $this->authorizationChecker->isGranted($role),
'admin_label' => $adminLabelTranslated,
'admin_code' => $code,
'group_label' => $groupLabelTranslated,
'group_code' => $groupCode,
];
}

return $adminRoles;
}

5 changes: 4 additions & 1 deletion src/Security/RolesBuilder/RolesBuilderInterface.php
Original file line number Diff line number Diff line change
@@ -21,7 +21,10 @@
* role_translated: string,
* is_granted: boolean,
* label?: string,
* admin_label?: string
* admin_label?: string,
* admin_code?: string,
* group_label?: string,
* group_code?: string
* }
*/
interface RolesBuilderInterface
21 changes: 17 additions & 4 deletions src/Twig/RolesMatrixExtension.php
Original file line number Diff line number Diff line change
@@ -71,14 +71,27 @@ public function renderMatrix(Environment $environment, FormView $form): string
{
$groupedRoles = [];
foreach ($this->rolesBuilder->getRoles() as $role => $attributes) {
if (!isset($attributes['admin_label'])) {
continue;
if (!isset($attributes['admin_code'])) {
piddubnij marked this conversation as resolved.
Show resolved Hide resolved
// NEXT_MAJOR: Remove those lines and uncomment the last one.
if (!isset($attributes['admin_label'])) {
continue;
}

@trigger_error(
'Not setting the "admin_code" attribute to admin role is deprecated since sonata-project/user-bundle 5.5'
.' and without it admin role will be skipped in version 6.0.',
\E_USER_DEPRECATED
);

$attributes['admin_code'] = $attributes['admin_label'];
// continue;
}

$groupedRoles[$attributes['admin_label']][$role] = $attributes;
$groupCode = $attributes['group_code'] ?? '';
$groupedRoles[$groupCode][$attributes['admin_code']][$role] = $attributes;
foreach ($form->getIterator() as $child) {
if ($child->vars['value'] === $role) {
$groupedRoles[$attributes['admin_label']][$role]['form'] = $child;
$groupedRoles[$groupCode][$attributes['admin_code']][$role]['form'] = $child;
}
}
}
30 changes: 28 additions & 2 deletions tests/Security/RolesBuilder/AdminRolesBuilderTest.php
Original file line number Diff line number Diff line change
@@ -72,7 +72,19 @@ protected function setUp(): void
$container = new Container();
$container->set('sonata.admin.bar', $this->admin);

$this->pool = new Pool($container, ['sonata.admin.bar']);
$adminGroups = [
'bar' => [
'label' => 'Bar',
'translation_domain' => '',
'icon' => '<i class="fas fa-edit"></i>',
'items' => [['admin' => 'sonata.admin.bar', 'roles' => [], 'route_absolute' => false, 'route_params' => []]],
'keep_open' => false,
'on_top' => false,
'roles' => [],
],
];

$this->pool = new Pool($container, ['sonata.admin.bar'], $adminGroups);
$this->configuration = new SonataConfiguration('title', 'logo', [
'confirm_exit' => true,
'default_admin_route' => 'show',
@@ -105,7 +117,9 @@ protected function setUp(): void

public function testGetPermissionLabels(): void
{
$this->translator->method('trans');
$this->translator->method('trans')->willReturnCallback(
static fn (string $key): string => $key
);

$this->securityHandler->method('getBaseRole')
->willReturn('ROLE_SONATA_FOO_%s');
@@ -170,27 +184,39 @@ public function testGetRoles(): void
'role_translated' => 'ROLE_SONATA_FOO_GUEST',
'is_granted' => false,
'admin_label' => 'Foo',
'admin_code' => 'sonata.admin.bar',
'group_label' => 'Foo',
'group_code' => 'bar',
],
'ROLE_SONATA_FOO_STAFF' => [
'role' => 'ROLE_SONATA_FOO_STAFF',
'label' => 'STAFF',
'role_translated' => 'ROLE_SONATA_FOO_STAFF',
'is_granted' => false,
'admin_label' => 'Foo',
'admin_code' => 'sonata.admin.bar',
'group_label' => 'Foo',
'group_code' => 'bar',
],
'ROLE_SONATA_FOO_EDITOR' => [
'role' => 'ROLE_SONATA_FOO_EDITOR',
'label' => 'EDITOR',
'role_translated' => 'ROLE_SONATA_FOO_EDITOR',
'is_granted' => false,
'admin_label' => 'Foo',
'admin_code' => 'sonata.admin.bar',
'group_label' => 'Foo',
'group_code' => 'bar',
],
'ROLE_SONATA_FOO_ADMIN' => [
'role' => 'ROLE_SONATA_FOO_ADMIN',
'label' => 'ADMIN',
'role_translated' => 'ROLE_SONATA_FOO_ADMIN',
'is_granted' => false,
'admin_label' => 'Foo',
'admin_code' => 'sonata.admin.bar',
'group_label' => 'Foo',
'group_code' => 'bar',
],
];

107 changes: 92 additions & 15 deletions tests/Twig/RolesMatrixExtensionTest.php
Original file line number Diff line number Diff line change
@@ -201,6 +201,9 @@ public function testRenderMatrix(): void
'role_translated' => 'ROLE FOO TRANSLATED',
'admin_label' => 'fooadmin',
'is_granted' => true,
'admin_code' => 'fooadmin',
'group_label' => 'BarGroup',
'group_code' => 'bargroup',
],
];
$this->rolesBuilder
@@ -226,14 +229,80 @@ public function testRenderMatrix(): void
->method('render')
->with('@SonataUser/Form/roles_matrix.html.twig', [
'grouped_roles' => [
'fooadmin' => [
'BASE_ROLE_FOO_EDIT' => [
'role' => 'BASE_ROLE_FOO_EDIT',
'label' => 'EDIT',
'role_translated' => 'ROLE FOO TRANSLATED',
'admin_label' => 'fooadmin',
'is_granted' => true,
'form' => $form,
'bargroup' => [
'fooadmin' => [
'BASE_ROLE_FOO_EDIT' => [
'role' => 'BASE_ROLE_FOO_EDIT',
'label' => 'EDIT',
'role_translated' => 'ROLE FOO TRANSLATED',
'admin_label' => 'fooadmin',
'is_granted' => true,
'admin_code' => 'fooadmin',
'group_label' => 'BarGroup',
'group_code' => 'bargroup',
'form' => $form,
],
],
],
],
'permission_labels' => ['EDIT', 'CREATE'],
])
->willReturn('');

$rolesMatrixExtension = new RolesMatrixExtension($this->rolesBuilder);
$rolesMatrixExtension->renderMatrix($this->environment, $this->formView);
}

/**
* NEXT_MAJOR: Remove this test.
*
* @group legacy
*/
public function testRenderMatrixWithoutAdminCode(): void
{
$roles = [
'BASE_ROLE_FOO_EDIT' => [
'role' => 'BASE_ROLE_FOO_EDIT',
'label' => 'EDIT',
'role_translated' => 'ROLE FOO TRANSLATED',
'admin_label' => 'fooadmin',
'is_granted' => true,
],
];
$this->rolesBuilder
->expects(static::once())
->method('getRoles')
->willReturn($roles);

$this->rolesBuilder
->expects(static::once())
->method('getPermissionLabels')
->willReturn(['EDIT', 'CREATE']);

$form = new FormView();
$form->vars['value'] = 'BASE_ROLE_FOO_EDIT';

$this->formView
->expects(static::once())
->method('getIterator')
->willReturn(new \ArrayIterator([$form]));

$this->environment
->expects(static::once())
->method('render')
->with('@SonataUser/Form/roles_matrix.html.twig', [
'grouped_roles' => [
'' => [
'fooadmin' => [
'BASE_ROLE_FOO_EDIT' => [
'role' => 'BASE_ROLE_FOO_EDIT',
'label' => 'EDIT',
'role_translated' => 'ROLE FOO TRANSLATED',
'admin_label' => 'fooadmin',
'is_granted' => true,
'admin_code' => 'fooadmin',
'form' => $form,
],
],
],
],
@@ -254,6 +323,9 @@ public function testRenderMatrixFormVarsNotSet(): void
'role_translated' => 'ROLE FOO TRANSLATED',
'admin_label' => 'fooadmin',
'is_granted' => true,
'admin_code' => 'fooadmin',
'group_label' => 'BarGroup',
'group_code' => 'bargroup',
],
];
$this->rolesBuilder
@@ -279,13 +351,18 @@ public function testRenderMatrixFormVarsNotSet(): void
->method('render')
->with('@SonataUser/Form/roles_matrix.html.twig', [
'grouped_roles' => [
'fooadmin' => [
'BASE_ROLE_FOO_%s' => [
'role' => 'BASE_ROLE_FOO_EDIT',
'label' => 'EDIT',
'role_translated' => 'ROLE FOO TRANSLATED',
'admin_label' => 'fooadmin',
'is_granted' => true,
'bargroup' => [
'fooadmin' => [
'BASE_ROLE_FOO_%s' => [
'role' => 'BASE_ROLE_FOO_EDIT',
'label' => 'EDIT',
'role_translated' => 'ROLE FOO TRANSLATED',
'admin_label' => 'fooadmin',
'is_granted' => true,
'admin_code' => 'fooadmin',
'group_label' => 'BarGroup',
'group_code' => 'bargroup',
],
],
],
],