Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EZP-30344: Allowed limiting Content management to specific translations #2585

Merged
7 changes: 7 additions & 0 deletions doc/bc/changes-7.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ Changes affecting version compatibility with former or future versions.

Similarly, the default password of the Admin user is not changed. But you must of course change it
before going live with a new project, and when you do, the new rules come into effect.

* Language Limitation supports now properly multilingual Content items allowing to modify translation
which is not main for users with limitation to that translation only.

Creating draft from existing Versions is no longer disallowed, even if a source Version does not
contain any of the translations that are within the scope of the Language Limitations.
This is due to the fact that when creating a Draft, an intent of translating is not known yet.
21 changes: 21 additions & 0 deletions eZ/Publish/API/Repository/PermissionResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
namespace eZ\Publish\API\Repository;

use eZ\Publish\API\Repository\Values\User\LookupLimitationResult;
use eZ\Publish\API\Repository\Values\User\UserReference;
use eZ\Publish\API\Repository\Values\ValueObject;

Expand Down Expand Up @@ -70,4 +71,24 @@ public function hasAccess($module, $function, UserReference $userReference = nul
* @return bool
*/
public function canUser($module, $function, ValueObject $object, array $targets = []);

/**
* @param string $module The module, aka controller identifier to check permissions on
* @param string $function The function, aka the controller action to check permissions on
* @param \eZ\Publish\API\Repository\Values\ValueObject $object The object to check if the user has access to
* @param \eZ\Publish\API\Repository\Values\ValueObject[] $targets An array of location, parent or "assignment" value objects
* @param string[] $limitationsIdentifiers An array of Limitations identifiers to filter from all which will pass
*
* @return \eZ\Publish\API\Repository\Values\User\LookupLimitationResult
*
* @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
* @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
*/
public function lookupLimitations(
string $module,
string $function,
ValueObject $object,
array $targets = [],
array $limitationsIdentifiers = []
): LookupLimitationResult;
}
9 changes: 7 additions & 2 deletions eZ/Publish/API/Repository/Tests/BaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

use Doctrine\DBAL\Connection;
use eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException;
use eZ\Publish\API\Repository\Exceptions\ForbiddenException;
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
use eZ\Publish\API\Repository\Exceptions\UnauthorizedException;
use eZ\Publish\API\Repository\Tests\PHPUnitConstraint\ValidationErrorOccurs as PHPUnitConstraintValidationErrorOccurs;
use eZ\Publish\API\Repository\Tests\SetupFactory\Legacy;
use eZ\Publish\API\Repository\Values\Content\Content;
Expand Down Expand Up @@ -581,7 +584,9 @@ public function createRoleWithPolicies($roleName, array $policiesData)
*
* @return \eZ\Publish\API\Repository\Values\User\User
*
* @throws \Exception
* @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
* @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
* @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
*/
public function createUserWithPolicies($login, array $policiesData)
{
Expand All @@ -607,7 +612,7 @@ public function createUserWithPolicies($login, array $policiesData)
$repository->commit();

return $user;
} catch (\Exception $ex) {
} catch (ForbiddenException | NotFoundException | UnauthorizedException $ex) {
$repository->rollback();
throw $ex;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace eZ\Publish\API\Repository\Tests\Limitation\PermissionResolver;

use eZ\Publish\API\Repository\Tests\BaseTest;
use eZ\Publish\API\Repository\Values\ValueObject;

/**
* Base class for all Limitation integration tests.
*/
abstract class BaseLimitationIntegrationTest extends BaseTest
{
/**
* @var \eZ\Publish\API\Repository\PermissionResolver
*/
protected $permissionResolver;

public function setUp(): void
{
$repository = $this->getRepository(false);
$this->permissionResolver = $repository->getPermissionResolver();
}

/**
* Map Limitations list to readable string for debugging purposes.
*
* @param \eZ\Publish\API\Repository\Values\User\Limitation[] $limitations
*
* @return string
*/
protected function getLimitationsListAsString(array $limitations): string
{
$str = '';
foreach ($limitations as $limitation) {
$str .= sprintf(
'%s[%s]',
get_class($limitation),
implode(', ', $limitation->limitationValues)
);
}

return $str;
}

/**
* Create Editor user with the given Policy and Limitations and set it as current user.
*
* @param string $module
* @param string $function
* @param array $limitations
*
* @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
* @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
* @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
*/
protected function loginAsEditorUserWithLimitations(string $module, string $function, array $limitations = []): void
{
$user = $this->createUserWithPolicies(
uniqid('editor'),
[
['module' => $module, 'function' => $function, 'limitations' => $limitations],
]
);

$this->permissionResolver->setCurrentUserReference($user);
}

/**
* @param bool $expectedResult
* @param string $module
* @param string $function
* @param array $limitations
* @param \eZ\Publish\API\Repository\Values\ValueObject $object
* @param array $targets
*
* @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
* @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
*/
protected function assertCanUser(
bool $expectedResult,
string $module,
string $function,
array $limitations,
ValueObject $object,
array $targets = []
): void {
self::assertEquals(
$expectedResult,
$this->permissionResolver->canUser($module, $function, $object, $targets),
sprintf(
'Failure for %s/%s with limitations: %s',
$module,
$function,
$this->getLimitationsListAsString($limitations)
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace eZ\Publish\API\Repository\Tests\Limitation\PermissionResolver;

use eZ\Publish\API\Repository\Values\Content\VersionInfo;
use eZ\Publish\API\Repository\Values\User\Limitation;

/**
* Test mix of chosen core Content Limitations.
*/
class ContentLimitationsMixIntegrationTest extends BaseLimitationIntegrationTest
{
const LIMITATION_VALUES = 'limitationValues';

/**
* {@inheritdoc}
*
* Provides lists of:
*
* <code>[string $module, string $function, array $limitations, bool $expectedResult]</code>
*
* This provider also checks if all registered Limitations are used.
*/
public function providerForCanUser(): array
{
$commonLimitations = $this->getCommonLimitations();
$contentCreateLimitations = array_merge(
$commonLimitations,
[
new Limitation\ParentContentTypeLimitation([self::LIMITATION_VALUES => [1]]),
new Limitation\ParentDepthLimitation([self::LIMITATION_VALUES => [2]]),
new Limitation\LanguageLimitation([self::LIMITATION_VALUES => ['eng-US']]),
]
);

$contentEditLimitations = array_merge(
$commonLimitations,
[
new Limitation\ObjectStateLimitation(
[self::LIMITATION_VALUES => [1, 2]]
),
new Limitation\LanguageLimitation([self::LIMITATION_VALUES => ['eng-US']]),
]
);

$contentVersionReadLimitations = array_merge(
$commonLimitations,
[
new Limitation\StatusLimitation(
[self::LIMITATION_VALUES => [VersionInfo::STATUS_PUBLISHED]]
),
]
);

return [
['content', 'create', $contentCreateLimitations, true],
['content', 'edit', $contentEditLimitations, true],
['content', 'publish', $contentEditLimitations, true],
['content', 'versionread', $contentVersionReadLimitations, true],
];
}

/**
* @dataProvider providerForCanUser
*
* @param string $module
* @param string $function
* @param array $limitations
* @param bool $expectedResult
*
* @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
* @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
* @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
* @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
* @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
*/
public function testCanUser(
string $module,
string $function,
array $limitations,
bool $expectedResult
): void {
$repository = $this->getRepository();
$locationService = $repository->getLocationService();

$folder = $this->createFolder(['eng-US' => 'Folder'], 2);
$location = $locationService->loadLocation($folder->contentInfo->mainLocationId);

$this->loginAsEditorUserWithLimitations($module, $function, $limitations);

$this->assertCanUser(
$expectedResult,
$module,
$function,
$limitations,
$folder,
[$location]
);
}

/**
* Get a list of Limitations common to all test cases.
*
* @return \eZ\Publish\API\Repository\Values\User\Limitation[]
*/
private function getCommonLimitations(): array
{
return [
new Limitation\ContentTypeLimitation([self::LIMITATION_VALUES => [1]]),
new Limitation\SectionLimitation([self::LIMITATION_VALUES => [1]]),
new Limitation\SubtreeLimitation([self::LIMITATION_VALUES => ['/1/2']]),
];
}
}
Loading