67 lines
2.6 KiB
PHP
67 lines
2.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Module\Core\Infrastructure\Security;
|
|
|
|
use App\Module\Core\Domain\Entity\User;
|
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
|
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
|
|
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
|
|
|
/**
|
|
* Voter RBAC qui evalue les codes de permission metier au format
|
|
* "module.resource.action" (ex: "core.users.view").
|
|
*
|
|
* - Ignore silencieusement les attributs non-RBAC (ROLE_*, IS_AUTHENTICATED_*, ...),
|
|
* qui restent traites par les voters core de Symfony. Strategy 'affirmative'
|
|
* par defaut : tant qu'un voter repond GRANTED, l'acces est accorde.
|
|
* - Bypass total si l'utilisateur porte le flag isAdmin (decision architecturale
|
|
* gravee au ticket #343 section 11 : is_admin est le seul levier technique
|
|
* de bypass, jamais remplace par un check de role).
|
|
* - Sinon, compare l'attribut aux permissions effectives de l'utilisateur
|
|
* (union dedupliquee triee venant des roles et des permissions directes).
|
|
*
|
|
* @extends Voter<string, mixed>
|
|
*/
|
|
final class PermissionVoter extends Voter
|
|
{
|
|
/**
|
|
* Regex de reconnaissance des codes de permission.
|
|
*
|
|
* Contraintes :
|
|
* - Premier caractere alphabetique minuscule (pas de chiffre, pas de ROLE_).
|
|
* - Au moins un point de separation (ecarte les attributs atomiques
|
|
* type ROLE_ADMIN ou IS_AUTHENTICATED_FULLY).
|
|
* - Segments en snake_case minuscule coherents avec les permissions
|
|
* declarees par les *Module::permissions() et validees par app:sync-permissions.
|
|
*/
|
|
private const string PERMISSION_CODE_PATTERN = '/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/';
|
|
|
|
protected function supports(string $attribute, mixed $subject): bool
|
|
{
|
|
return (bool) preg_match(self::PERMISSION_CODE_PATTERN, $attribute);
|
|
}
|
|
|
|
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
|
|
{
|
|
$user = $token->getUser();
|
|
|
|
if (!$user instanceof User) {
|
|
// Token anonyme ou user d'un autre type : on refuse explicitement.
|
|
// Les voters core (AuthenticatedVoter) se chargent deja du cas
|
|
// "pas authentifie du tout".
|
|
return false;
|
|
}
|
|
|
|
if ($user->isAdmin()) {
|
|
// Bypass total : decision architecturale #343 section 11.
|
|
// Cette regle est dupliquee cote front dans usePermissions()
|
|
// et les deux doivent bouger ensemble si elle evolue un jour.
|
|
return true;
|
|
}
|
|
|
|
return in_array($attribute, $user->getEffectivePermissions(), true);
|
|
}
|
|
}
|