From 957e67a08af2b3265753f9763943e8225ed779ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Parafi=C5=84ski?= Date: Thu, 10 Nov 2022 12:22:29 +0100 Subject: [PATCH] Merge pull request from GHSA-99r3-xmmq-7q7g --- eZ/Publish/Core/settings/policies.yml | 2 +- eZ/Publish/Core/settings/roles.yml | 12 + phpunit-integration-legacy.xml | 3 + phpunit.xml | 3 + .../User/Limitation/MemberOfLimitation.php | 21 + .../Values/User/Limitation/RoleLimitation.php | 21 + src/lib/Limitation/MemberOfLimitationType.php | 201 +++++++ src/lib/Limitation/RoleLimitationType.php | 143 +++++ tests/integration/Core/.gitkeep | 0 .../Limitation/MemberOfLimitationTest.php | 74 +++ .../Core/Limitation/RoleLimitationTest.php | 69 +++ .../Limitation/MemberOfLimitationTypeTest.php | 517 ++++++++++++++++++ .../lib/Limitation/RoleLimitationTypeTest.php | 318 +++++++++++ 13 files changed, 1383 insertions(+), 1 deletion(-) create mode 100644 src/contracts/Repository/Values/User/Limitation/MemberOfLimitation.php create mode 100644 src/contracts/Repository/Values/User/Limitation/RoleLimitation.php create mode 100644 src/lib/Limitation/MemberOfLimitationType.php create mode 100644 src/lib/Limitation/RoleLimitationType.php delete mode 100644 tests/integration/Core/.gitkeep create mode 100644 tests/integration/Core/Limitation/MemberOfLimitationTest.php create mode 100644 tests/integration/Core/Limitation/RoleLimitationTest.php create mode 100644 tests/lib/Limitation/MemberOfLimitationTypeTest.php create mode 100644 tests/lib/Limitation/RoleLimitationTypeTest.php diff --git a/eZ/Publish/Core/settings/policies.yml b/eZ/Publish/Core/settings/policies.yml index c0862d48982..c1140988d43 100644 --- a/eZ/Publish/Core/settings/policies.yml +++ b/eZ/Publish/Core/settings/policies.yml @@ -30,7 +30,7 @@ parameters: administrate: ~ role: - assign: ~ + assign: { MemberOf: true, Role: true } update: ~ create: ~ delete: ~ diff --git a/eZ/Publish/Core/settings/roles.yml b/eZ/Publish/Core/settings/roles.yml index 6cb58149303..9533763c90f 100644 --- a/eZ/Publish/Core/settings/roles.yml +++ b/eZ/Publish/Core/settings/roles.yml @@ -146,3 +146,15 @@ services: class: "%ezpublish.api.role.limitation_type.blocking.class%" arguments: ['AntiSpam'] tags: [{name: ezpublish.limitationType, alias: AntiSpam}] + + Ibexa\Core\Limitation\MemberOfLimitationType: + arguments: + $persistence: '@ezpublish.api.persistence_handler' + tags: + - { name: ezpublish.limitationType, alias: MemberOf } + + Ibexa\Core\Limitation\RoleLimitationType: + arguments: + $persistence: '@ezpublish.api.persistence_handler' + tags: + - { name: ezpublish.limitationType, alias: Role } diff --git a/phpunit-integration-legacy.xml b/phpunit-integration-legacy.xml index c42c1e661bd..ace45238b11 100644 --- a/phpunit-integration-legacy.xml +++ b/phpunit-integration-legacy.xml @@ -73,6 +73,9 @@ eZ/Publish/SPI/Tests/Search/Content/IndexerGatewayTest.php + + tests/integration + diff --git a/phpunit.xml b/phpunit.xml index 082bbb28172..fe30983fe49 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -73,6 +73,9 @@ eZ/Publish/API/Repository/Tests/Values/Content + + tests/lib + diff --git a/src/contracts/Repository/Values/User/Limitation/MemberOfLimitation.php b/src/contracts/Repository/Values/User/Limitation/MemberOfLimitation.php new file mode 100644 index 00000000000..960cf57ceaa --- /dev/null +++ b/src/contracts/Repository/Values/User/Limitation/MemberOfLimitation.php @@ -0,0 +1,21 @@ +limitationValues)) { + throw new InvalidArgumentType( + '$limitationValue->limitationValues', + 'array', + $limitationValue->limitationValues + ); + } + + foreach ($limitationValue->limitationValues as $key => $id) { + if (!is_int($id)) { + throw new InvalidArgumentType("\$limitationValue->limitationValues[{$key}]", 'int|string', $id); + } + } + } + + public function validate(APILimitationValue $limitationValue) + { + $validationErrors = []; + + foreach ($limitationValue->limitationValues as $key => $id) { + if ($id === self::SELF_USER_GROUP) { + continue; + } + try { + $this->persistence->contentHandler()->loadContentInfo($id); + } catch (NotFoundException $e) { + $validationErrors[] = new ValidationError( + "limitationValues[%key%] => '%value%' does not exist in the backend", + null, + [ + 'value' => $id, + 'key' => $key, + ] + ); + } + } + + return $validationErrors; + } + + /** + * @param mixed[] $limitationValues + * + * @return \eZ\Publish\API\Repository\Values\User\Limitation + */ + public function buildValue(array $limitationValues): APILimitationValue + { + return new MemberOfLimitation(['limitationValues' => $limitationValues]); + } + + public function evaluate(APILimitationValue $value, APIUserReference $currentUser, ValueObject $object, array $targets = null) + { + if (!$value instanceof MemberOfLimitation) { + throw new InvalidArgumentException( + '$value', + sprintf('Must be of type: %s', MemberOfLimitation::class) + ); + } + + if (!$object instanceof User + && !$object instanceof UserGroup + && !$object instanceof UserRoleAssignment + && !$object instanceof UserGroupRoleAssignment + ) { + return self::ACCESS_ABSTAIN; + } + + if ($object instanceof User) { + return $this->evaluateUser($value, $object, $currentUser); + } + + if ($object instanceof UserGroup) { + return $this->evaluateUserGroup($value, $object, $currentUser); + } + + if ($object instanceof UserRoleAssignment) { + return $this->evaluateUser($value, $object->getUser(), $currentUser); + } + + if ($object instanceof UserGroupRoleAssignment) { + return $this->evaluateUserGroup($value, $object->getUserGroup(), $currentUser); + } + + return self::ACCESS_DENIED; + } + + public function getCriterion(APILimitationValue $value, APIUserReference $currentUser) + { + throw new NotImplementedException('Member of Limitation Criterion'); + } + + public function valueSchema() + { + throw new NotImplementedException(__METHOD__); + } + + private function evaluateUser(MemberOfLimitation $value, User $object, APIUserReference $currentUser): bool + { + if (empty($value->limitationValues)) { + return self::ACCESS_DENIED; + } + + $userLocations = $this->persistence->locationHandler()->loadLocationsByContent($object->getUserId()); + + $userGroups = []; + foreach ($userLocations as $userLocation) { + $userGroups[] = $this->persistence->locationHandler()->load($userLocation->parentId); + } + $userGroupsIdList = array_column($userGroups, 'contentId'); + $limitationValuesUserGroupsIdList = $value->limitationValues; + + if (in_array(self::SELF_USER_GROUP, $limitationValuesUserGroupsIdList)) { + $currentUserGroupsIdList = $this->getCurrentUserGroupsIdList($currentUser); + + // Granted, if current user is in exactly those same groups + if (count(array_intersect($userGroupsIdList, $currentUserGroupsIdList)) === count($userGroupsIdList)) { + return self::ACCESS_GRANTED; + } + + // Unset SELF value, for next check + $key = array_search(self::SELF_USER_GROUP, $limitationValuesUserGroupsIdList); + unset($limitationValuesUserGroupsIdList[$key]); + } + + // Granted, if limitationValues matched user groups 1:1 + if (!empty($limitationValuesUserGroupsIdList) + && empty(array_diff($userGroupsIdList, $limitationValuesUserGroupsIdList)) + ) { + return self::ACCESS_GRANTED; + } + + return self::ACCESS_DENIED; + } + + private function evaluateUserGroup(MemberOfLimitation $value, UserGroup $userGroup, APIUserReference $currentUser): bool + { + $limitationValuesUserGroupsIdList = $value->limitationValues; + if (in_array(self::SELF_USER_GROUP, $limitationValuesUserGroupsIdList)) { + $limitationValuesUserGroupsIdList = $this->getCurrentUserGroupsIdList($currentUser); + } + + return in_array($userGroup->id, $limitationValuesUserGroupsIdList); + } + + private function getCurrentUserGroupsIdList(APIUserReference $currentUser): array + { + $currentUserLocations = $this->persistence->locationHandler()->loadLocationsByContent($currentUser->getUserId()); + $currentUserGroups = []; + foreach ($currentUserLocations as $currentUserLocation) { + $currentUserGroups[] = $this->persistence->locationHandler()->load($currentUserLocation->parentId); + } + + return array_column( + $currentUserGroups, + 'contentId' + ); + } +} diff --git a/src/lib/Limitation/RoleLimitationType.php b/src/lib/Limitation/RoleLimitationType.php new file mode 100644 index 00000000000..e33e1ea6d14 --- /dev/null +++ b/src/lib/Limitation/RoleLimitationType.php @@ -0,0 +1,143 @@ +limitationValues)) { + throw new InvalidArgumentType( + '$limitationValue->limitationValues', + 'array', + $limitationValue->limitationValues + ); + } + + foreach ($limitationValue->limitationValues as $key => $id) { + if (!is_int($id)) { + throw new InvalidArgumentType("\$limitationValue->limitationValues[{$key}]", 'int|string', $id); + } + } + } + + public function validate(APILimitationValue $limitationValue) + { + $validationErrors = []; + + foreach ($limitationValue->limitationValues as $key => $id) { + try { + $this->persistence->userHandler()->loadRole($id); + } catch (NotFoundException $e) { + $validationErrors[] = new ValidationError( + "limitationValues[%key%] => '%value%' does not exist in the backend", + null, + [ + 'value' => $id, + 'key' => $key, + ] + ); + } + } + + return $validationErrors; + } + + /** + * @param mixed[] $limitationValues + * + * @return \eZ\Publish\API\Repository\Values\User\Limitation + */ + public function buildValue(array $limitationValues): APILimitationValue + { + return new RoleLimitation(['limitationValues' => $limitationValues]); + } + + public function evaluate(APILimitationValue $value, APIUserReference $currentUser, ValueObject $object, array $targets = null) + { + if (!$value instanceof RoleLimitation) { + throw new InvalidArgumentException( + '$value', + sprintf('Must be of type: %s', RoleLimitation::class) + ); + } + + if ( + !$object instanceof Role + && !$object instanceof UserRoleAssignment + && !$object instanceof UserGroupRoleAssignment + && ($targets === null && ($object instanceof User || $object instanceof UserGroup)) + ) { + return self::ACCESS_ABSTAIN; + } + + if ($targets !== null) { + foreach ($targets as $target) { + if ($target instanceof Role && !$this->evaluateRole($value, $target)) { + return self::ACCESS_DENIED; + } + + return self::ACCESS_GRANTED; + } + } + + if ($object instanceof Role) { + return $this->evaluateRole($value, $object); + } + + if ($object instanceof UserRoleAssignment || $object instanceof UserGroupRoleAssignment) { + return $this->evaluateRole($value, $object->getRole()); + } + + return self::ACCESS_DENIED; + } + + public function getCriterion(APILimitationValue $value, APIUserReference $currentUser) + { + throw new NotImplementedException('Role Limitation Criterion'); + } + + public function valueSchema() + { + throw new NotImplementedException(__METHOD__); + } + + private function evaluateRole(RoleLimitation $value, Role $role): bool + { + return in_array($role->id, $value->limitationValues); + } +} diff --git a/tests/integration/Core/.gitkeep b/tests/integration/Core/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/integration/Core/Limitation/MemberOfLimitationTest.php b/tests/integration/Core/Limitation/MemberOfLimitationTest.php new file mode 100644 index 00000000000..5c89166a2f9 --- /dev/null +++ b/tests/integration/Core/Limitation/MemberOfLimitationTest.php @@ -0,0 +1,74 @@ +limitationValues[] = self::ADMIN_GROUP_ID; + + $allowInUsersLimitation = new MemberOfLimitation(); + $allowInUsersLimitation->limitationValues[] = self::USERS_GROUP_ID; + + $allowInSelfGroupLimitation = new MemberOfLimitation(); + $allowInSelfGroupLimitation->limitationValues[] = MemberOfLimitationType::SELF_USER_GROUP; + + return [ + [[$allowInAdministratorsLimitation], false], + [[$allowInUsersLimitation], true], + [[$allowInSelfGroupLimitation], true], + ]; + } + + /** + * @dataProvider userPermissionLimitationProvider + */ + public function testCanUserAssignRoleToUser(array $limitations, bool $expectedResult): void + { + $repository = $this->getRepository(); + $roleService = $repository->getRoleService(); + $userService = $repository->getUserService(); + + $adminRoleThatWillBeSet = $roleService->loadRoleByIdentifier('Administrator'); + $this->loginAsEditorUserWithLimitations('role', 'assign', $limitations); + + $this->assertCanUser( + $expectedResult, + 'role', + 'assign', + $limitations, + $userService->loadUser($this->permissionResolver->getCurrentUserReference()->getUserId()), + [$adminRoleThatWillBeSet] + ); + + $this->assertCanUser( + $expectedResult, + 'role', + 'assign', + $limitations, + $repository->sudo( + static function (Repository $repository) { + return $repository->getUserService()->loadUserGroup(self::USERS_GROUP_ID); + }, + $repository + ), + [$adminRoleThatWillBeSet] + ); + } +} diff --git a/tests/integration/Core/Limitation/RoleLimitationTest.php b/tests/integration/Core/Limitation/RoleLimitationTest.php new file mode 100644 index 00000000000..729862db80e --- /dev/null +++ b/tests/integration/Core/Limitation/RoleLimitationTest.php @@ -0,0 +1,69 @@ +getRepository()->getRoleService(); + $allowEditorLimitation->limitationValues[] = $roleService->loadRoleByIdentifier('Editor')->id; + + $allowAdministratorLimitation = new RoleLimitation(); + $allowAdministratorLimitation->limitationValues[] = $roleService->loadRoleByIdentifier('Administrator')->id; + + return [ + [[$allowEditorLimitation], false], + [[$allowAdministratorLimitation], true], + ]; + } + + /** + * @dataProvider userPermissionLimitationProvider + */ + public function testCanUserAssignRole(array $limitations, bool $expectedResult): void + { + $repository = $this->getRepository(); + $roleService = $repository->getRoleService(); + $userService = $repository->getUserService(); + + $adminRoleThatWillBeSet = $roleService->loadRoleByIdentifier('Administrator'); + $this->loginAsEditorUserWithLimitations('role', 'assign', $limitations); + + $this->assertCanUser( + $expectedResult, + 'role', + 'assign', + $limitations, + $userService->loadUser($this->permissionResolver->getCurrentUserReference()->getUserId()), + [$adminRoleThatWillBeSet] + ); + + $this->assertCanUser( + $expectedResult, + 'role', + 'assign', + $limitations, + $repository->sudo( + static function (Repository $repository) { + return $repository->getUserService()->loadUserGroup(self::USERS_GROUP_ID); + }, + $repository + ), + [$adminRoleThatWillBeSet] + ); + } +} diff --git a/tests/lib/Limitation/MemberOfLimitationTypeTest.php b/tests/lib/Limitation/MemberOfLimitationTypeTest.php new file mode 100644 index 00000000000..3dd5e8e9240 --- /dev/null +++ b/tests/lib/Limitation/MemberOfLimitationTypeTest.php @@ -0,0 +1,517 @@ +limitationType = new MemberOfLimitationType( + $this->getPersistenceMock() + ); + } + + /** + * @dataProvider providerForTestAcceptValue + */ + public function testAcceptValue(MemberOfLimitation $limitation): void + { + $this->expectNotToPerformAssertions(); + $this->limitationType->acceptValue($limitation); + } + + public function providerForTestAcceptValue(): array + { + return [ + [ + new MemberOfLimitation([ + 'limitationValues' => [], + ]), + ], + [ + new MemberOfLimitation([ + 'limitationValues' => [MemberOfLimitationType::SELF_USER_GROUP, 8], + ]), + ], + ]; + } + + /** + * @dataProvider providerForTestAcceptValueException + */ + public function testAcceptValueException(MemberOfLimitation $limitation): void + { + $this->expectException(InvalidArgumentType::class); + $this->limitationType->acceptValue($limitation); + } + + public function providerForTestAcceptValueException(): array + { + return [ + [ + new MemberOfLimitation([ + 'limitationValues' => 1, + ]), + ], + [ + new MemberOfLimitation([ + 'limitationValues' => null, + ]), + ], + [ + new MemberOfLimitation([ + 'limitationValues' => 'string', + ]), + ], + [ + new MemberOfLimitation([ + 'limitationValues' => ['string'], + ]), + ], + ]; + } + + /** + * @dataProvider providerForTestAcceptValue + */ + public function testValidatePass(MemberOfLimitation $limitation): void + { + $contentHandlerMock = $this->createMock(ContentHandlerInterface::class); + + $contentHandlerMock + ->method('loadContentInfo') + ->with(8); + + $this->getPersistenceMock() + ->method('contentHandler') + ->willReturn($contentHandlerMock); + + $validationErrors = $this->limitationType->validate($limitation); + + self::assertEmpty($validationErrors); + } + + /** + * @dataProvider providerForTestValidateError + */ + public function testValidateError(MemberOfLimitation $limitation, int $errorCount): void + { + $contentHandlerMock = $this->createMock(ContentHandlerInterface::class); + + if ($limitation->limitationValues !== null) { + $contentHandlerMock + ->method('loadContentInfo') + ->withConsecutive([14], [18]) + ->willReturnOnConsecutiveCalls( + $this->throwException(new NotFoundException('UserGroup', 18)), + new ContentInfo() + ); + + $this->getPersistenceMock() + ->method('contentHandler') + ->willReturn($contentHandlerMock); + } + + $validationErrors = $this->limitationType->validate($limitation); + self::assertCount($errorCount, $validationErrors); + } + + public function providerForTestValidateError() + { + return [ + [ + new MemberOfLimitation([ + 'limitationValues' => [14, 18], + ]), + 1, + ], + ]; + } + + /** + * @dataProvider providerForTestEvaluate + */ + public function testEvaluate( + MemberOfLimitation $limitation, + ValueObject $object, + ?bool $expected + ): void { + $locationHandlerMock = $this->createMock(LocationHandlerInterface::class); + + if ($object instanceof User || $object instanceof UserRoleAssignment) { + $locationHandlerMock + ->method('loadLocationsByContent') + ->with($object instanceof User ? $object->getUserId() : $object->getUser()->getUserId()) + ->willReturn([ + new Location(['parentId' => 13]), + new Location(['parentId' => 14]), + ]); + + $locationHandlerMock + ->method('load') + ->withConsecutive( + [13], [14] + ) + ->willReturnOnConsecutiveCalls( + new Location(['contentId' => 14]), + new Location(['contentId' => 25]) + ); + + $this->getPersistenceMock() + ->method('locationHandler') + ->willReturn($locationHandlerMock); + } + + $value = (new MemberOfLimitationType($this->getPersistenceMock()))->evaluate( + $limitation, + $this->getUserMock(), + $object + ); + + self::assertEquals($expected, $value); + } + + public function providerForTestEvaluate() + { + return [ + 'valid_group_limitation' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [14, 18], + ]), + 'object' => new UserGroup([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 14, + ]), + ]), + ]), + ]), + 'expected' => true, + ], + 'allow_non_user_groups_limitation' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [], + ]), + 'object' => new UserGroup([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 14, + ]), + ]), + ]), + ]), + 'expected' => false, + ], + 'pass_to_next_limitation' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [14, 18], + ]), + 'object' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 14, + ]), + ]), + 'expected' => null, + ], + 'invalid_user_must_have_permission_to_every_group_user_is_in' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [25, 10], + ]), + 'object' => new User([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 66, + ]), + ]), + ]), + ]), + 'expected' => false, + ], + 'user_must_have_permission_to_every_group_user_is_in' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [14, 25], + ]), + 'object' => new User([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 66, + ]), + ]), + ]), + ]), + 'expected' => true, + ], + 'user_role_assigment_valid' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [14, 25], + ]), + 'object' => new UserRoleAssignment([ + 'user' => new User([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 66, + ]), + ]), + ]), + ]), + ]), + 'expected' => true, + ], + 'user_role_assigment_invalid_user' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [25, 10], + ]), + 'object' => new UserRoleAssignment([ + 'user' => new User([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 66, + ]), + ]), + ]), + ]), + 'role' => new Role(['id' => 7]), + ]), + 'expected' => false, + ], + 'user_group_role_assigment_valid' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [14, 18], + ]), + 'object' => new UserGroupRoleAssignment([ + 'userGroup' => new UserGroup([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 14, + ]), + ]), + ]), + ]), + 'role' => new Role(['id' => 7]), + ]), + 'expected' => true, + ], + 'user_group_role_assigment_invalid_user_group' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [18], + ]), + 'object' => new UserGroupRoleAssignment([ + 'userGroup' => new UserGroup([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 14, + ]), + ]), + ]), + ]), + 'role' => new Role(['id' => 7]), + ]), + 'expected' => false, + ], + ]; + } + + /** + * @param \eZ\Publish\API\Repository\Values\User\User|\eZ\Publish\API\Repository\Values\User\UserRoleAssignment $object + * @dataProvider providerForTestEvaluateSelfGroup + */ + public function testEvaluateSelfGroup( + MemberOfLimitation $limitation, + ValueObject $object, + array $currentUserGroupLocations, + ?bool $expected + ): void { + $locationHandlerMock = $this->createMock(LocationHandlerInterface::class); + + $currentUserLocation = []; + + foreach ($currentUserGroupLocations as $groupLocation) { + $currentUserLocation[] = new Location(['parentId' => $groupLocation->contentId - 1]); + } + $locationHandlerMock + ->method('loadLocationsByContent') + ->withConsecutive( + [$object instanceof User ? $object->getUserId() : $object->getUser()->getUserId()], + [$this->getUserMock()->getUserId()] + ) + ->willReturnOnConsecutiveCalls( + [ + new Location(['parentId' => 13]), + new Location(['parentId' => 43]), + ], + $currentUserLocation + ); + + $locationHandlerMock + ->method('load') + ->withConsecutive( + [13], [43] + ) + ->willReturnOnConsecutiveCalls( + new Location(['contentId' => 14]), + new Location(['contentId' => 44]), + ...$currentUserGroupLocations + ); + + $this->getPersistenceMock() + ->method('locationHandler') + ->willReturn($locationHandlerMock); + + $this->getPersistenceMock() + ->method('locationHandler') + ->willReturn($locationHandlerMock); + + $value = (new MemberOfLimitationType($this->getPersistenceMock()))->evaluate( + $limitation, + $this->getUserMock(), + $object + ); + + self::assertEquals($expected, $value); + } + + public function providerForTestEvaluateSelfGroup(): array + { + return [ + 'role_assign_to_user_in_same_group' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [MemberOfLimitationType::SELF_USER_GROUP], + ]), + 'object' => new User([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 66, + ]), + ]), + ]), + ]), + 'currentUserGroupLocations' => [ + new Location(['contentId' => 14]), + new Location(['contentId' => 44]), + new Location(['contentId' => 55]), + ], + 'expected' => true, + ], + 'role_assign_to_user_in_other_group' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [MemberOfLimitationType::SELF_USER_GROUP], + ]), + 'object' => new User([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 66, + ]), + ]), + ]), + ]), + 'currentUserGroupLocations' => [ + new Location(['contentId' => 11]), + new Location(['contentId' => 14]), + ], + 'expected' => false, + ], + 'role_assign_to_user_in_overlapped_groups' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [MemberOfLimitationType::SELF_USER_GROUP, 14, 44], + ]), + 'object' => new User([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 66, + ]), + ]), + ]), + ]), + 'currentUserGroupLocations' => [ + new Location(['contentId' => 1]), + ], + 'expected' => true, + ], + 'user_role_assigment_to_user_in_same_group' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [MemberOfLimitationType::SELF_USER_GROUP], + ]), + 'object' => new UserRoleAssignment([ + 'user' => new User([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 66, + ]), + ]), + ]), + ]), + 'role' => new Role(['id' => 4]), + ]), + 'currentUserGroupLocations' => [ + new Location(['contentId' => 14]), + new Location(['contentId' => 44]), + new Location(['contentId' => 55]), + ], + 'expected' => true, + ], + 'user_role_assigment_to_user_in_other_group' => [ + 'limitation' => new MemberOfLimitation([ + 'limitationValues' => [MemberOfLimitationType::SELF_USER_GROUP], + ]), + 'object' => new UserRoleAssignment([ + 'user' => new User([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 66, + ]), + ]), + ]), + ]), + 'role' => new Role(['id' => 4]), + ]), + 'currentUserGroupLocations' => [ + new Location(['contentId' => 11]), + new Location(['contentId' => 14]), + ], + 'expected' => false, + ], + ]; + } +} diff --git a/tests/lib/Limitation/RoleLimitationTypeTest.php b/tests/lib/Limitation/RoleLimitationTypeTest.php new file mode 100644 index 00000000000..1defbef1d67 --- /dev/null +++ b/tests/lib/Limitation/RoleLimitationTypeTest.php @@ -0,0 +1,318 @@ +limitationType = new RoleLimitationType( + $this->getPersistenceMock() + ); + } + + /** + * @dataProvider providerForTestAcceptValue + */ + public function testAcceptValue(RoleLimitation $limitation): void + { + $this->expectNotToPerformAssertions(); + $this->limitationType->acceptValue($limitation); + } + + public function providerForTestAcceptValue(): array + { + return [ + [ + new RoleLimitation([ + 'limitationValues' => [], + ]), + ], + [ + new RoleLimitation([ + 'limitationValues' => [4, 8], + ]), + ], + ]; + } + + /** + * @dataProvider providerForTestAcceptValueException + */ + public function testAcceptValueException(RoleLimitation $limitation): void + { + $this->expectException(InvalidArgumentType::class); + $this->limitationType->acceptValue($limitation); + } + + public function providerForTestAcceptValueException(): array + { + return [ + [ + new RoleLimitation([ + 'limitationValues' => 1, + ]), + ], + [ + new RoleLimitation([ + 'limitationValues' => null, + ]), + ], + [ + new RoleLimitation([ + 'limitationValues' => 'string', + ]), + ], + [ + new RoleLimitation([ + 'limitationValues' => ['string'], + ]), + ], + ]; + } + + /** + * @dataProvider providerForTestAcceptValue + */ + public function testValidatePass(RoleLimitation $limitation): void + { + $userHandlerMock = $this->createMock(UserHandlerInterface::class); + $contentHandlerMock = $this->createMock(ContentHandlerInterface::class); + + if ($limitation->limitationValues !== null) { + $userHandlerMock + ->method('loadRole') + ->withConsecutive([4, Role::STATUS_DEFINED], [8, Role::STATUS_DEFINED]); + + $this->getPersistenceMock() + ->method('userHandler') + ->willReturn($userHandlerMock); + } + + if ($limitation->limitationValues !== null) { + $contentHandlerMock + ->method('loadContentInfo') + ->withConsecutive([14], [21]); + + $this->getPersistenceMock() + ->method('contentHandler') + ->willReturn($contentHandlerMock); + } + + $validationErrors = $this->limitationType->validate($limitation); + + self::assertEmpty($validationErrors); + } + + /** + * @dataProvider providerForTestValidateError + */ + public function testValidateError(RoleLimitation $limitation, int $errorCount): void + { + $userHandlerMock = $this->createMock(UserHandlerInterface::class); + + if ($limitation->limitationValues !== null) { + $userHandlerMock + ->method('loadRole') + ->withConsecutive([4, Role::STATUS_DEFINED], [8, Role::STATUS_DEFINED]) + ->willReturnOnConsecutiveCalls( + $this->throwException(new NotFoundException('Role', 4)), + new Role() + ); + + $this->getPersistenceMock() + ->method('userHandler') + ->willReturn($userHandlerMock); + } + + $validationErrors = $this->limitationType->validate($limitation); + self::assertCount($errorCount, $validationErrors); + } + + public function providerForTestValidateError() + { + return [ + [ + new RoleLimitation([ + 'limitationValues' => [4, 8], + ]), + 1, + ], + ]; + } + + /** + * @dataProvider providerForTestEvaluate + */ + public function testEvaluate( + RoleLimitation $limitation, + ValueObject $object, + ?array $targets, + ?bool $expected + ): void { + $locationHandlerMock = $this->createMock(LocationHandlerInterface::class); + + if ($object instanceof UserRoleAssignment) { + $locationHandlerMock + ->method('loadLocationsByContent') + ->with($object instanceof User ? $object->getUserId() : $object->getUser()->getUserId()) + ->willReturn([ + new Location(['contentId' => 14]), + new Location(['contentId' => 25]), + ]); + + $this->getPersistenceMock() + ->method('locationHandler') + ->willReturn($locationHandlerMock); + } + + $value = (new RoleLimitationType($this->getPersistenceMock()))->evaluate( + $limitation, + $this->getUserMock(), + $object, + $targets + ); + + self::assertEquals($expected, $value); + } + + public function providerForTestEvaluate() + { + return [ + 'valid_role_limitation' => [ + 'limitation' => new RoleLimitation([ + 'limitationValues' => [4, 8], + ]), + 'object' => new Role(['id' => 4]), + 'targets' => null, + 'expected' => true, + ], + 'allow_non_role_limitation' => [ + 'limitation' => new RoleLimitation([ + 'limitationValues' => [], + ]), + 'object' => new Role(['id' => 4]), + 'targets' => null, + 'expected' => false, + ], + 'pass_to_next_limitation' => [ + 'limitation' => new RoleLimitation([ + 'limitationValues' => [4, 8], + ]), + 'object' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 14, + ]), + ]), + 'targets' => null, + 'expected' => null, + ], + 'user_role_assigment_valid' => [ + 'limitation' => new RoleLimitation([ + 'limitationValues' => [4, 8], + ]), + 'object' => new UserRoleAssignment([ + 'user' => new User([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 66, + ]), + ]), + ]), + ]), + 'role' => new Role(['id' => 4]), + ]), + 'targets' => null, + 'expected' => true, + ], + 'user_role_assigment_invalid_role' => [ + 'limitation' => new RoleLimitation([ + 'limitationValues' => [4, 8], + ]), + 'object' => new UserRoleAssignment([ + 'user' => new User([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 66, + ]), + ]), + ]), + ]), + 'role' => new Role(['id' => 7]), + ]), + 'targets' => null, + 'expected' => false, + ], + 'user_group_role_assigment_valid' => [ + 'limitation' => new RoleLimitation([ + 'limitationValues' => [4, 8], + ]), + 'object' => new UserGroupRoleAssignment([ + 'userGroup' => new UserGroup([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 66, + ]), + ]), + ]), + ]), + 'role' => new Role(['id' => 4]), + ]), + 'targets' => null, + 'expected' => true, + ], + 'user_group_role_assigment_invalid_role' => [ + 'limitation' => new RoleLimitation([ + 'limitationValues' => [4, 8], + ]), + 'object' => new UserGroupRoleAssignment([ + 'userGroup' => new UserGroup([ + 'content' => new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => 66, + ]), + ]), + ]), + ]), + 'role' => new Role(['id' => 7]), + ]), + 'targets' => null, + 'expected' => false, + ], + ]; + } +}