diff --git a/src/Module/Core/Infrastructure/Security/PermissionVoter.php b/src/Module/Core/Infrastructure/Security/PermissionVoter.php new file mode 100644 index 0000000..542e220 --- /dev/null +++ b/src/Module/Core/Infrastructure/Security/PermissionVoter.php @@ -0,0 +1,38 @@ + + */ +final class PermissionVoter extends Voter +{ + private const string PATTERN = '/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/'; + + protected function supports(string $attribute, mixed $subject): bool + { + return 1 === preg_match(self::PATTERN, $attribute); + } + + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool + { + $user = $token->getUser(); + if (!$user instanceof User) { + return false; + } + + // ROLE_ADMIN = bypass total (cf. Décision 1). + if (in_array('ROLE_ADMIN', $user->getRoles(), true)) { + return true; + } + + return in_array($attribute, $user->getEffectivePermissions(), true); + } +} diff --git a/tests/Unit/Module/Core/Infrastructure/Security/PermissionVoterTest.php b/tests/Unit/Module/Core/Infrastructure/Security/PermissionVoterTest.php new file mode 100644 index 0000000..c1094f9 --- /dev/null +++ b/tests/Unit/Module/Core/Infrastructure/Security/PermissionVoterTest.php @@ -0,0 +1,53 @@ +vote($this->token($user), null, ['ROLE_ADMIN'])); + self::assertSame(VoterInterface::ACCESS_ABSTAIN, $voter->vote($this->token($user), null, ['IS_AUTHENTICATED_FULLY'])); + } + + public function testGrantsWhenUserHasPermissionViaRole(): void + { + $voter = new PermissionVoter(); + $role = new Role('bureau', 'Bureau'); + $role->addPermission(new Permission('core.users.view', 'Voir', 'core')); + $user = new User(); + $user->addRbacRole($role); + + self::assertSame(VoterInterface::ACCESS_GRANTED, $voter->vote($this->token($user), null, ['core.users.view'])); + self::assertSame(VoterInterface::ACCESS_DENIED, $voter->vote($this->token($user), null, ['core.users.manage'])); + } + + public function testAdminBypassesViaRole(): void + { + $voter = new PermissionVoter(); + $user = new User(); + $user->setRoles(['ROLE_ADMIN']); + + self::assertSame(VoterInterface::ACCESS_GRANTED, $voter->vote($this->token($user), null, ['core.users.manage'])); + } + + private function token(User $user): UsernamePasswordToken + { + return new UsernamePasswordToken($user, 'main', $user->getRoles()); + } +}