From 6e3f37f80e8c8da6f78d29680c95a70835df71c3 Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Tue, 7 Nov 2023 17:06:16 +0100 Subject: [PATCH 1/2] IBX-6592: Allowed `Location` to be a part of permission check for Object State assignment --- eZ/Publish/API/Repository/ObjectStateService.php | 12 +++++++----- eZ/Publish/Core/Event/ObjectStateService.php | 6 ++++-- .../Limitation/NewObjectStateLimitationType.php | 8 ++++---- .../Core/Repository/ObjectStateService.php | 16 +++++++++------- .../SiteAccessAware/ObjectStateService.php | 11 ++++++++--- .../Decorator/ObjectStateServiceDecorator.php | 6 ++++-- 6 files changed, 36 insertions(+), 23 deletions(-) diff --git a/eZ/Publish/API/Repository/ObjectStateService.php b/eZ/Publish/API/Repository/ObjectStateService.php index 304d3287d4..ed51b1a0cb 100644 --- a/eZ/Publish/API/Repository/ObjectStateService.php +++ b/eZ/Publish/API/Repository/ObjectStateService.php @@ -7,6 +7,7 @@ namespace eZ\Publish\API\Repository; use eZ\Publish\API\Repository\Values\Content\ContentInfo; +use eZ\Publish\API\Repository\Values\Content\Location; use eZ\Publish\API\Repository\Values\ObjectState\ObjectState; use eZ\Publish\API\Repository\Values\ObjectState\ObjectStateCreateStruct; use eZ\Publish\API\Repository\Values\ObjectState\ObjectStateGroup; @@ -172,12 +173,13 @@ public function deleteObjectState(ObjectState $objectState): void; * * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the object state does not belong to the given group * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to change the object state - * - * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo - * @param \eZ\Publish\API\Repository\Values\ObjectState\ObjectStateGroup $objectStateGroup - * @param \eZ\Publish\API\Repository\Values\ObjectState\ObjectState $objectState */ - public function setContentState(ContentInfo $contentInfo, ObjectStateGroup $objectStateGroup, ObjectState $objectState): void; + public function setContentState( + ContentInfo $contentInfo, + ObjectStateGroup $objectStateGroup, + ObjectState $objectState, + ?Location $location = null + ): void; /** * Gets the object-state of object identified by $contentId. diff --git a/eZ/Publish/Core/Event/ObjectStateService.php b/eZ/Publish/Core/Event/ObjectStateService.php index 10ed436b94..d95ed9e81a 100644 --- a/eZ/Publish/Core/Event/ObjectStateService.php +++ b/eZ/Publish/Core/Event/ObjectStateService.php @@ -26,6 +26,7 @@ use eZ\Publish\API\Repository\Events\ObjectState\UpdateObjectStateGroupEvent; use eZ\Publish\API\Repository\ObjectStateService as ObjectStateServiceInterface; use eZ\Publish\API\Repository\Values\Content\ContentInfo; +use eZ\Publish\API\Repository\Values\Content\Location; use eZ\Publish\API\Repository\Values\ObjectState\ObjectState; use eZ\Publish\API\Repository\Values\ObjectState\ObjectStateCreateStruct; use eZ\Publish\API\Repository\Values\ObjectState\ObjectStateGroup; @@ -214,7 +215,8 @@ public function deleteObjectState(ObjectState $objectState): void public function setContentState( ContentInfo $contentInfo, ObjectStateGroup $objectStateGroup, - ObjectState $objectState + ObjectState $objectState, + ?Location $location = null ): void { $eventData = [ $contentInfo, @@ -229,7 +231,7 @@ public function setContentState( return; } - $this->innerService->setContentState($contentInfo, $objectStateGroup, $objectState); + $this->innerService->setContentState($contentInfo, $objectStateGroup, $objectState, $location); $this->eventDispatcher->dispatch( new SetContentStateEvent(...$eventData) diff --git a/eZ/Publish/Core/Limitation/NewObjectStateLimitationType.php b/eZ/Publish/Core/Limitation/NewObjectStateLimitationType.php index 7d6acce2db..65db5071d5 100644 --- a/eZ/Publish/Core/Limitation/NewObjectStateLimitationType.php +++ b/eZ/Publish/Core/Limitation/NewObjectStateLimitationType.php @@ -127,11 +127,11 @@ public function evaluate(APILimitationValue $value, APIUserReference $currentUse return false; } - foreach ($targets as $target) { - if (!$target instanceof ObjectState && !$target instanceof SPIObjectState) { - throw new InvalidArgumentException('$targets', 'Must contain ObjectState objects'); - } + $targets = array_filter($targets, static function ($target) { + return $target instanceof ObjectState || $target instanceof SPIObjectState; + }); + foreach ($targets as $target) { if (!in_array($target->id, $value->limitationValues)) { return false; } diff --git a/eZ/Publish/Core/Repository/ObjectStateService.php b/eZ/Publish/Core/Repository/ObjectStateService.php index 9d16add23d..d8397d0eb7 100644 --- a/eZ/Publish/Core/Repository/ObjectStateService.php +++ b/eZ/Publish/Core/Repository/ObjectStateService.php @@ -12,6 +12,7 @@ use eZ\Publish\API\Repository\PermissionResolver; use eZ\Publish\API\Repository\Repository as RepositoryInterface; use eZ\Publish\API\Repository\Values\Content\ContentInfo; +use eZ\Publish\API\Repository\Values\Content\Location; use eZ\Publish\API\Repository\Values\ObjectState\ObjectState as APIObjectState; use eZ\Publish\API\Repository\Values\ObjectState\ObjectStateCreateStruct; use eZ\Publish\API\Repository\Values\ObjectState\ObjectStateGroup as APIObjectStateGroup; @@ -463,14 +464,15 @@ public function deleteObjectState(APIObjectState $objectState): void * * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the object state does not belong to the given group * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to change the object state - * - * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo - * @param \eZ\Publish\API\Repository\Values\ObjectState\ObjectStateGroup $objectStateGroup - * @param \eZ\Publish\API\Repository\Values\ObjectState\ObjectState $objectState */ - public function setContentState(ContentInfo $contentInfo, APIObjectStateGroup $objectStateGroup, APIObjectState $objectState): void - { - if (!$this->permissionResolver->canUser('state', 'assign', $contentInfo, [$objectState])) { + public function setContentState( + ContentInfo $contentInfo, + APIObjectStateGroup $objectStateGroup, + APIObjectState $objectState, + ?Location $location = null + ): void { + $targets = $location !== null ? [$location, $objectState] : [$objectState]; + if (!$this->permissionResolver->canUser('state', 'assign', $contentInfo, $targets)) { throw new UnauthorizedException('state', 'assign', ['contentId' => $contentInfo->id]); } diff --git a/eZ/Publish/Core/Repository/SiteAccessAware/ObjectStateService.php b/eZ/Publish/Core/Repository/SiteAccessAware/ObjectStateService.php index 4a49b6b7b3..6ff9ea3723 100644 --- a/eZ/Publish/Core/Repository/SiteAccessAware/ObjectStateService.php +++ b/eZ/Publish/Core/Repository/SiteAccessAware/ObjectStateService.php @@ -9,6 +9,7 @@ use eZ\Publish\API\Repository\LanguageResolver; use eZ\Publish\API\Repository\ObjectStateService as ObjectStateServiceInterface; use eZ\Publish\API\Repository\Values\Content\ContentInfo; +use eZ\Publish\API\Repository\Values\Content\Location; use eZ\Publish\API\Repository\Values\ObjectState\ObjectState; use eZ\Publish\API\Repository\Values\ObjectState\ObjectStateCreateStruct; use eZ\Publish\API\Repository\Values\ObjectState\ObjectStateGroup; @@ -127,9 +128,13 @@ public function deleteObjectState(ObjectState $objectState): void $this->service->deleteObjectState($objectState); } - public function setContentState(ContentInfo $contentInfo, ObjectStateGroup $objectStateGroup, ObjectState $objectState): void - { - $this->service->setContentState($contentInfo, $objectStateGroup, $objectState); + public function setContentState( + ContentInfo $contentInfo, + ObjectStateGroup $objectStateGroup, + ObjectState $objectState, + ?Location $location = null + ): void { + $this->service->setContentState($contentInfo, $objectStateGroup, $objectState, $location); } public function getContentState(ContentInfo $contentInfo, ObjectStateGroup $objectStateGroup): ObjectState diff --git a/eZ/Publish/SPI/Repository/Decorator/ObjectStateServiceDecorator.php b/eZ/Publish/SPI/Repository/Decorator/ObjectStateServiceDecorator.php index 31ddc37dac..8763e2579e 100644 --- a/eZ/Publish/SPI/Repository/Decorator/ObjectStateServiceDecorator.php +++ b/eZ/Publish/SPI/Repository/Decorator/ObjectStateServiceDecorator.php @@ -10,6 +10,7 @@ use eZ\Publish\API\Repository\ObjectStateService; use eZ\Publish\API\Repository\Values\Content\ContentInfo; +use eZ\Publish\API\Repository\Values\Content\Location; use eZ\Publish\API\Repository\Values\ObjectState\ObjectState; use eZ\Publish\API\Repository\Values\ObjectState\ObjectStateCreateStruct; use eZ\Publish\API\Repository\Values\ObjectState\ObjectStateGroup; @@ -121,9 +122,10 @@ public function deleteObjectState(ObjectState $objectState): void public function setContentState( ContentInfo $contentInfo, ObjectStateGroup $objectStateGroup, - ObjectState $objectState + ObjectState $objectState, + ?Location $location = null ): void { - $this->innerService->setContentState($contentInfo, $objectStateGroup, $objectState); + $this->innerService->setContentState($contentInfo, $objectStateGroup, $objectState, $location); } public function getContentState( From 2b8fdecc2c659d5614eefb844097313ad1b74175 Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Wed, 8 Nov 2023 15:33:42 +0100 Subject: [PATCH 2/2] IBX-6592: Provided integration test --- .../SetContentStateTest.php | 91 +++++++++++++++++++ tests/integration/Core/RepositoryTestCase.php | 72 +++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 tests/integration/Core/Repository/ObjectStateService/SetContentStateTest.php diff --git a/tests/integration/Core/Repository/ObjectStateService/SetContentStateTest.php b/tests/integration/Core/Repository/ObjectStateService/SetContentStateTest.php new file mode 100644 index 0000000000..65b9fd111c --- /dev/null +++ b/tests/integration/Core/Repository/ObjectStateService/SetContentStateTest.php @@ -0,0 +1,91 @@ +loadObjectState(2); + + $subtreeLimitationFolder = $this->createFolder(['eng-GB' => 'Subtree limitation type'], 2); + $contentInfo = $subtreeLimitationFolder->getVersionInfo()->getContentInfo(); + $mainLocation = $contentInfo->getMainLocation(); + + $limitations = [ + new SubtreeLimitation( + [ + 'limitationValues' => [$subtreeLimitationValue ?? $mainLocation->getPathString()], + ], + ), + new ObjectStateLimitation( + [ + 'limitationValues' => [1, 2], + ], + ), + ]; + + $user = $this->createUserWithPolicies( + 'object_state_user', + [ + ['module' => 'content', 'function' => '*'], + ['module' => 'state', 'function' => 'assign', 'limitations' => $limitations], + ] + ); + + $permissionResolver->setCurrentUserReference($user); + + $childFolder = $this->createFolder(['eng-GB' => 'Child folder'], $mainLocation->id); + $childContentInfo = $childFolder->getVersionInfo()->getContentInfo(); + + if (!$isInsideLimitation) { + self::expectException(UnauthorizedException::class); + } + + $objectStateService->setContentState( + $childContentInfo, + $objectState->getObjectStateGroup(), + $objectState, + $childContentInfo->getMainLocation(), + ); + + $contentState = $objectStateService->getContentState($childContentInfo, $objectState->getObjectStateGroup()); + + self::assertSame($objectState->identifier, $contentState->identifier); + } + + public function dataProviderForTestSetContentObjectStateWithSubtreeLimitation(): iterable + { + yield 'inside subtree limitation' => [ + null, + true, + ]; + + yield 'outside limitation passes' => [ + '/1/43', + false, + ]; + } +} diff --git a/tests/integration/Core/RepositoryTestCase.php b/tests/integration/Core/RepositoryTestCase.php index 4a2b9ff13e..1dd7ced27e 100644 --- a/tests/integration/Core/RepositoryTestCase.php +++ b/tests/integration/Core/RepositoryTestCase.php @@ -9,6 +9,8 @@ namespace Ibexa\Tests\Integration\Core; use eZ\Publish\API\Repository\Values\Content\Content; +use eZ\Publish\API\Repository\Values\User\Role; +use eZ\Publish\API\Repository\Values\User\User; use Ibexa\Contracts\Core\Test\IbexaKernelTestCase; use InvalidArgumentException; @@ -70,4 +72,74 @@ public function createFolderDraft(array $names, int $parentLocationId = self::CO ] ); } + + /** + * @param array $policiesData + * + * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException + * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException + * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException + */ + public function createRoleWithPolicies(string $roleName, array $policiesData): Role + { + $roleService = self::getRoleService(); + + $roleCreateStruct = $roleService->newRoleCreateStruct($roleName); + foreach ($policiesData as $policyData) { + $policyCreateStruct = $roleService->newPolicyCreateStruct( + $policyData['module'], + $policyData['function'] + ); + + if (isset($policyData['limitations'])) { + foreach ($policyData['limitations'] as $limitation) { + $policyCreateStruct->addLimitation($limitation); + } + } + + $roleCreateStruct->addPolicy($policyCreateStruct); + } + + $roleDraft = $roleService->createRole($roleCreateStruct); + + $roleService->publishRoleDraft($roleDraft); + + return $roleService->loadRole($roleDraft->id); + } + + /** + * @param array>>> $policiesData + * + * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException + * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException + * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException + * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException + * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException + */ + public function createUserWithPolicies(string $login, array $policiesData): User + { + $roleService = self::getRoleService(); + $userService = self::getUserService(); + + $userCreateStruct = $userService->newUserCreateStruct( + $login, + "{$login}@test.dev", + $login, + 'eng-GB' + ); + + $userCreateStruct->setField('first_name', $login); + $userCreateStruct->setField('last_name', $login); + $user = $userService->createUser($userCreateStruct, []); + + $role = $this->createRoleWithPolicies( + uniqid('role_for_' . $login . '_', true), + $policiesData + ); + $roleService->assignRoleToUser($role, $user); + + return $user; + } }