| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- <?php
- namespace App\Tests\Unit\Security\Voter;
- use App\Entity\Access\Access;
- use App\Entity\Person\Person;
- use App\Security\Voter\EntityVoter\AbstractEntityVoter;
- use App\Service\Access\Utils;
- use App\Service\Security\InternalRequestsService;
- use App\Service\Security\SwitchUser;
- use Doctrine\ORM\EntityManagerInterface;
- use PHPUnit\Framework\MockObject\MockObject;
- use PHPUnit\Framework\TestCase;
- use Symfony\Bundle\SecurityBundle\Security;
- use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
- class TestableAbstractEntityVoter extends AbstractEntityVoter {
- public const READ = 'READ';
- public const EDIT = 'EDIT';
- public const CREATE = 'CREATE';
- public const DELETE = 'DELETE';
- public static function setEntityClass(?string $entityClass): void {
- static::$entityClass = $entityClass;
- }
- public static function getEntityClass(): ?string {
- return static::$entityClass;
- }
- /**
- * @param array<string> $allowedOperations
- * @return void
- */
- public static function setAllowedOperations(array $allowedOperations): void {
- static::$allowedOperations = $allowedOperations;
- }
- public static function getAllowedOperations(): array {
- return static::$allowedOperations;
- }
- public function supports(string $attribute, mixed $subject): bool {
- return parent::supports($attribute, $subject);
- }
- public function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool {
- return parent::voteOnAttribute($attribute, $subject, $token);
- }
- public function canView(object $subject): bool { return true; }
- public function canEdit(object $subject): bool { return true; }
- public function canCreate(object $subject): bool { return true; }
- public function canDelete(object $subject): bool { return true; }
- public function isUserLoggedIn(): bool { return parent::isUserLoggedIn(); }
- public function getUser(): ?Access { return parent::getUser(); }
- public function isValidInternalRequest(): bool { return parent::isValidInternalRequest(); }
- }
- class AbstractVoterTest extends TestCase
- {
- protected Security | MockObject $security;
- protected Utils | MockObject $accessUtils;
- protected EntityManagerInterface | MockObject $em;
- protected InternalRequestsService | MockObject $internalRequestsService;
- protected SwitchUser | MockObject $switchUserService;
- private bool $initialConditionSaved = false;
- private ?string $initialEntityClass = null;
- private ?array $initialAllowedOperations = null;
- private ?int $initialSwitchHeader = null;
- private ?string $initialRemoteAddr = null;
- private ?string $initialInternalRequestToken = null;
- private function saveInitialCondition() {
- $this->initialEntityClass = TestableAbstractEntityVoter::getEntityClass();
- $this->initialAllowedOperations = TestableAbstractEntityVoter::getAllowedOperations();
- $this->initialSwitchHeader = $_SERVER['HTTP_X_SWITCH_USER'] ?? null;
- $this->initialRemoteAddr = $_SERVER['REMOTE_ADDR'] ?? null;
- $this->initialInternalRequestToken = $_SERVER['HTTP_INTERNAL_REQUESTS_TOKEN'] ?? null;
- $this->initialConditionSaved = true;
- }
- private function reinitialize() {
- // Reinitialize the TestableAbstractVoter static properties
- TestableAbstractEntityVoter::setEntityClass($this->initialEntityClass);
- TestableAbstractEntityVoter::setAllowedOperations($this->initialAllowedOperations);
- // Reinitialize the global variables
- if ($this->initialSwitchHeader !== null) {
- $_SERVER['HTTP_X_SWITCH_USER'] = $this->initialSwitchHeader;
- } else if (array_key_exists('HTTP_X_SWITCH_USER', $_SERVER)) {
- unset($_SERVER['HTTP_X_SWITCH_USER']);
- }
- $_SERVER['REMOTE_ADDR'] = $this->initialRemoteAddr;
- if ($this->initialSwitchHeader !== null) {
- $_SERVER['HTTP_INTERNAL_REQUESTS_TOKEN'] = $this->initialSwitchHeader;
- } else if (array_key_exists('HTTP_INTERNAL_REQUESTS_TOKEN', $_SERVER)) {
- unset($_SERVER['HTTP_INTERNAL_REQUESTS_TOKEN']);
- }
- }
- public function setUp(): void {
- if (!$this->initialConditionSaved) {
- $this->saveInitialCondition();
- }
- $this->security = $this->getMockBuilder(Security::class)->disableOriginalConstructor()->getMock();
- $this->accessUtils = $this->getMockBuilder(Utils::class)->disableOriginalConstructor()->getMock();
- $this->internalRequestsService = $this->getMockBuilder(InternalRequestsService::class)->disableOriginalConstructor()->getMock();
- $this->em = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
- $this->switchUserService = $this->getMockBuilder(SwitchUser::class)->disableOriginalConstructor()->getMock();
- }
- public function tearDown(): void
- {
- $this->reinitialize();
- }
- private function makeAbstractVoterMockFor(string $methodName): MockObject | TestableAbstractEntityVoter {
- return $this->getMockBuilder(TestableAbstractEntityVoter::class)
- ->setConstructorArgs([
- $this->security,
- $this->accessUtils,
- $this->internalRequestsService,
- $this->em,
- $this->switchUserService
- ])
- ->setMethodsExcept([$methodName])
- ->getMock();
- }
- public function testSupports(): void {
- TestableAbstractEntityVoter::setEntityClass(Access::class);
- $abstractVoter = $this->makeAbstractVoterMockFor('supports');
- $access = new Access();
- $this->assertTrue(
- $abstractVoter->supports(TestableAbstractEntityVoter::READ, $access)
- );
- $this->assertTrue(
- $abstractVoter->supports(TestableAbstractEntityVoter::EDIT, $access)
- );
- $this->assertTrue(
- $abstractVoter->supports(TestableAbstractEntityVoter::CREATE, $access)
- );
- $this->assertTrue(
- $abstractVoter->supports(TestableAbstractEntityVoter::DELETE, $access)
- );
- }
- public function testSupportsNotSupportedClass(): void {
- TestableAbstractEntityVoter::setEntityClass(Access::class);
- $abstractVoter = $this->makeAbstractVoterMockFor('supports');
- $person = new Person();
- $this->assertFalse(
- $abstractVoter->supports(TestableAbstractEntityVoter::READ, $person)
- );
- }
- public function testSupportsNotAllowedOperation(): void {
- TestableAbstractEntityVoter::setEntityClass(Access::class);
- TestableAbstractEntityVoter::setAllowedOperations([TestableAbstractEntityVoter::READ]);
- $abstractVoter = $this->makeAbstractVoterMockFor('supports');
- $access = new Access();
- $this->assertTrue(
- $abstractVoter->supports(TestableAbstractEntityVoter::READ, $access)
- );
- $this->assertFalse(
- $abstractVoter->supports(TestableAbstractEntityVoter::EDIT, $access)
- );
- $this->assertFalse(
- $abstractVoter->supports(TestableAbstractEntityVoter::CREATE, $access)
- );
- $this->assertFalse(
- $abstractVoter->supports(TestableAbstractEntityVoter::DELETE, $access)
- );
- }
- public function testSupportsMissingEntityClass(): void {
- $abstractVoter = $this->makeAbstractVoterMockFor('supports');
- $this->expectException(\RuntimeException::class);
- $this->expectExceptionMessage('Setup the self::$entityClass property, or override the supports() method');
- $access = new Access();
- $abstractVoter->supports(TestableAbstractEntityVoter::READ, $access);
- }
- public function testVoteOnAttributeRead(): void {
- $abstractVoter = $this->makeAbstractVoterMockFor('voteOnAttribute');
- $subject = $this->getMockBuilder(Access::class)->getMock();
- $token = $this->getMockBuilder(TokenInterface::class)->getMock();
- $abstractVoter->expects(self::once())->method('canView')->with($subject)->willReturn(true);
- $abstractVoter->expects(self::never())->method('canEdit');
- $abstractVoter->expects(self::never())->method('canCreate');
- $abstractVoter->expects(self::never())->method('canDelete');
- $this->assertTrue(
- $abstractVoter->voteOnAttribute(TestableAbstractEntityVoter::READ, $subject, $token)
- );
- }
- public function testVoteOnAttributeEdit(): void {
- $abstractVoter = $this->makeAbstractVoterMockFor('voteOnAttribute');
- $subject = $this->getMockBuilder(Access::class)->getMock();
- $token = $this->getMockBuilder(TokenInterface::class)->getMock();
- $abstractVoter->expects(self::never())->method('canView');
- $abstractVoter->expects(self::once())->method('canEdit')->with($subject)->willReturn(true);
- $abstractVoter->expects(self::never())->method('canCreate');
- $abstractVoter->expects(self::never())->method('canDelete');
- $this->assertTrue(
- $abstractVoter->voteOnAttribute(TestableAbstractEntityVoter::EDIT, $subject, $token)
- );
- }
- public function testVoteOnAttributeCreate(): void {
- $abstractVoter = $this->makeAbstractVoterMockFor('voteOnAttribute');
- $subject = $this->getMockBuilder(Access::class)->getMock();
- $token = $this->getMockBuilder(TokenInterface::class)->getMock();
- $abstractVoter->expects(self::never())->method('canView');
- $abstractVoter->expects(self::never())->method('canEdit');
- $abstractVoter->expects(self::once())->method('canCreate')->with($subject)->willReturn(true);
- $abstractVoter->expects(self::never())->method('canDelete');
- $this->assertTrue(
- $abstractVoter->voteOnAttribute(TestableAbstractEntityVoter::CREATE, $subject, $token)
- );
- }
- public function testVoteOnAttributeDelete(): void {
- $abstractVoter = $this->makeAbstractVoterMockFor('voteOnAttribute');
- $subject = $this->getMockBuilder(Access::class)->getMock();
- $token = $this->getMockBuilder(TokenInterface::class)->getMock();
- $abstractVoter->expects(self::never())->method('canView');
- $abstractVoter->expects(self::never())->method('canEdit');
- $abstractVoter->expects(self::never())->method('canCreate');
- $abstractVoter->expects(self::once())->method('canDelete')->with($subject)->willReturn(true);
- $this->assertTrue(
- $abstractVoter->voteOnAttribute(TestableAbstractEntityVoter::DELETE, $subject, $token)
- );
- }
- public function testVoteOnAttributeInvalidAttribute(): void {
- $abstractVoter = $this->makeAbstractVoterMockFor('voteOnAttribute');
- $subject = $this->getMockBuilder(Access::class)->getMock();
- $token = $this->getMockBuilder(TokenInterface::class)->getMock();
- $abstractVoter->expects(self::never())->method('canView');
- $abstractVoter->expects(self::never())->method('canEdit');
- $abstractVoter->expects(self::never())->method('canCreate');
- $abstractVoter->expects(self::never())->method('canDelete');
- $this->assertFalse(
- $abstractVoter->voteOnAttribute('other_attr', $subject, $token)
- );
- }
- public function testGetUser() {
- $user = $this->getMockBuilder(Access::class)->getMock();
- $this->security->expects(self::once())->method('getUser')->willReturn($user);
- $abstractVoter = $this->makeAbstractVoterMockFor('getUser');
- $this->assertEquals(
- $user,
- $abstractVoter->getUser()
- );
- }
- public function testGetUserNoUser() {
- $this->security->expects(self::once())->method('getUser')->willReturn(null);
- $abstractVoter = $this->makeAbstractVoterMockFor('getUser');
- $this->assertNull(
- $abstractVoter->getUser()
- );
- }
- public function testGetUserSwitchUser() {
- $originalUser = $this->getMockBuilder(Access::class)->getMock();
- $switchUser = $this->getMockBuilder(Access::class)->getMock();
- $_SERVER['HTTP_X_SWITCH_USER'] = 1;
- $this->security->expects(self::once())->method('getUser')->willReturn($originalUser);
- $this->em->expects(self::once())->method('find')->with(Access::class, 1)->willReturn($switchUser);
- $abstractVoter = $this->makeAbstractVoterMockFor('getUser');
- $this->assertEquals(
- $switchUser,
- $abstractVoter->getUser()
- );
- }
- public function testIsUserLoggedIn(): void {
- $user = $this->getMockBuilder(Access::class)->getMock();
- $abstractVoter = $this->makeAbstractVoterMockFor('isUserLoggedIn');
- $abstractVoter->expects(self::once())->method('getUser')->willReturn($user);
- $this->assertTrue($abstractVoter->isUserLoggedIn());
- }
- public function testIsUserLoggedInNoUser(): void {
- $abstractVoter = $this->makeAbstractVoterMockFor('isUserLoggedIn');
- $abstractVoter->expects(self::once())->method('getUser')->willReturn(null);
- $this->assertFalse($abstractVoter->isUserLoggedIn());
- }
- public function testIsValidInternalRequest(): void {
- $clientIp = '0.0.0.0';
- $token = 'valid_token';
- $_SERVER['REMOTE_ADDR'] = $clientIp;
- $_SERVER['HTTP_INTERNAL_REQUESTS_TOKEN'] = $token;
- $this->internalRequestsService
- ->expects(self::once())
- ->method('isAllowed')
- ->with($clientIp, $token)
- ->willReturn(true);
- $abstractVoter = $this->makeAbstractVoterMockFor('isValidInternalRequest');
- $this->assertTrue($abstractVoter->isValidInternalRequest());
- }
- }
|