feat(core) : gate sidebar by effective permissions

This commit is contained in:
Matthieu
2026-06-19 17:28:42 +02:00
parent 1a9eba93a0
commit 544d4cf44f
4 changed files with 68 additions and 9 deletions
+27 -4
View File
@@ -7,13 +7,14 @@ namespace App\Shared\Domain\Sidebar;
final class SidebarFilter
{
/**
* @param list<array{label:string, icon:string, roles?:list<string>, items: list<array{label:string, to:string, icon:string, module?:string, roles?:list<string>}>}> $sections
* @param list<string> $activeModuleIds
* @param list<string> $activeRoles
* @param list<array{label:string, icon:string, roles?:list<string>, permission?:string, items: list<array{label:string, to:string, icon:string, module?:string, roles?:list<string>, permission?:string}>}> $sections
* @param list<string> $activeModuleIds
* @param list<string> $activeRoles
* @param list<string> $activePermissions
*
* @return array{sections: list<array{label:string, icon:string, items: list<array{label:string, to:string, icon:string}>}>, disabledRoutes: list<string>}
*/
public static function filter(array $sections, array $activeModuleIds, array $activeRoles = []): array
public static function filter(array $sections, array $activeModuleIds, array $activeRoles = [], array $activePermissions = []): array
{
$outSections = [];
$disabledRoutes = [];
@@ -24,6 +25,11 @@ final class SidebarFilter
continue;
}
// Gate de permission au niveau section (RBAC fin).
if (!self::permissionSatisfied($section['permission'] ?? null, $activePermissions)) {
continue;
}
$items = [];
foreach ($section['items'] as $item) {
// Gate de rôle au niveau item.
@@ -31,6 +37,11 @@ final class SidebarFilter
continue;
}
// Gate de permission au niveau item (RBAC fin).
if (!self::permissionSatisfied($item['permission'] ?? null, $activePermissions)) {
continue;
}
// Filtrage par module actif (pilote la redirection front via disabledRoutes).
$module = $item['module'] ?? null;
if (null !== $module && !in_array($module, $activeModuleIds, true)) {
@@ -68,4 +79,16 @@ final class SidebarFilter
return false;
}
/**
* @param list<string> $activePermissions
*/
private static function permissionSatisfied(?string $required, array $activePermissions): bool
{
if (null === $required || '' === $required) {
return true;
}
return in_array($required, $activePermissions, true);
}
}
@@ -6,6 +6,7 @@ namespace App\Shared\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Shared\Domain\Contract\UserInterface;
use App\Shared\Domain\Module\ModuleRegistry;
use App\Shared\Domain\Sidebar\SidebarFilter;
use App\Shared\Infrastructure\ApiPlatform\Resource\SidebarResource;
@@ -31,7 +32,15 @@ final readonly class SidebarProvider implements ProviderInterface
$user = $this->security->getUser();
$roles = null !== $user ? $user->getRoles() : [];
$filtered = SidebarFilter::filter($sidebar, ModuleRegistry::ids($moduleClasses), array_values($roles));
// RBAC fin : permissions effectives du contrat. ROLE_ADMIN bypasse tout (Décision 1) :
// on lui injecte le catalogue complet des permissions déclarées pour satisfaire les gates.
if (in_array('ROLE_ADMIN', $roles, true)) {
$permissions = array_column(ModuleRegistry::permissions($moduleClasses), 'code');
} else {
$permissions = $user instanceof UserInterface ? $user->getEffectivePermissions() : [];
}
$filtered = SidebarFilter::filter($sidebar, ModuleRegistry::ids($moduleClasses), array_values($roles), $permissions);
$dto = new SidebarResource();
$dto->sections = $filtered['sections'];