Skip to content

Commit 2a74799

Browse files
authored
Merge pull request from GHSA-8h83-chh2-fchp
1 parent 76c7321 commit 2a74799

File tree

12 files changed

+1382
-1
lines changed

12 files changed

+1382
-1
lines changed

eZ/Publish/Core/settings/policies.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ parameters:
3030
administrate: ~
3131

3232
role:
33-
assign: ~
33+
assign: { MemberOf: true, Role: true }
3434
update: ~
3535
create: ~
3636
delete: ~

eZ/Publish/Core/settings/roles.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,15 @@ services:
128128
class: eZ\Publish\Core\Limitation\BlockingLimitationType
129129
arguments: ['AntiSpam']
130130
tags: [{name: ezpublish.limitationType, alias: AntiSpam}]
131+
132+
Ibexa\Core\Limitation\MemberOfLimitationType:
133+
arguments:
134+
$persistence: '@ezpublish.api.persistence_handler'
135+
tags:
136+
- { name: ezpublish.limitationType, alias: MemberOf }
137+
138+
Ibexa\Core\Limitation\RoleLimitationType:
139+
arguments:
140+
$persistence: '@ezpublish.api.persistence_handler'
141+
tags:
142+
- { name: ezpublish.limitationType, alias: Role }

phpunit.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@
7676
<testsuite name="eZ\Publish\API\Repository\Iterator">
7777
<directory>eZ/Publish/API/Repository/Tests/Iterator</directory>
7878
</testsuite>
79+
<testsuite name="Ibexa\Tests\Core\">
80+
<directory>tests/lib</directory>
81+
</testsuite>
7982
</testsuites>
8083
<filter>
8184
<whitelist>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Contracts\Core\Repository\Values\User\Limitation;
10+
11+
use eZ\Publish\API\Repository\Values\User\Limitation;
12+
13+
final class MemberOfLimitation extends Limitation
14+
{
15+
public const IDENTIFIER = 'MemberOf';
16+
17+
public function getIdentifier(): string
18+
{
19+
return self::IDENTIFIER;
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Contracts\Core\Repository\Values\User\Limitation;
10+
11+
use eZ\Publish\API\Repository\Values\User\Limitation;
12+
13+
final class RoleLimitation extends Limitation
14+
{
15+
public const IDENTIFIER = 'Role';
16+
17+
public function getIdentifier(): string
18+
{
19+
return self::IDENTIFIER;
20+
}
21+
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Core\Limitation;
10+
11+
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
12+
use eZ\Publish\API\Repository\Exceptions\NotImplementedException;
13+
use eZ\Publish\API\Repository\Values\User\Limitation as APILimitationValue;
14+
use eZ\Publish\API\Repository\Values\User\User;
15+
use eZ\Publish\API\Repository\Values\User\UserGroup;
16+
use eZ\Publish\API\Repository\Values\User\UserGroupRoleAssignment;
17+
use eZ\Publish\API\Repository\Values\User\UserReference as APIUserReference;
18+
use eZ\Publish\API\Repository\Values\User\UserRoleAssignment;
19+
use eZ\Publish\API\Repository\Values\ValueObject;
20+
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
21+
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentType;
22+
use eZ\Publish\Core\FieldType\ValidationError;
23+
use eZ\Publish\Core\Limitation\AbstractPersistenceLimitationType;
24+
use eZ\Publish\SPI\Limitation\Type as SPILimitationTypeInterface;
25+
use Ibexa\Contracts\Core\Repository\Values\User\Limitation\MemberOfLimitation;
26+
27+
final class MemberOfLimitationType extends AbstractPersistenceLimitationType implements SPILimitationTypeInterface
28+
{
29+
public const SELF_USER_GROUP = -1;
30+
31+
/**
32+
* @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentType
33+
*/
34+
public function acceptValue(APILimitationValue $limitationValue): void
35+
{
36+
if (!$limitationValue instanceof MemberOfLimitation) {
37+
throw new InvalidArgumentType(
38+
'$limitationValue',
39+
MemberOfLimitation::class,
40+
$limitationValue
41+
);
42+
}
43+
44+
if (!is_array($limitationValue->limitationValues)) {
45+
throw new InvalidArgumentType(
46+
'$limitationValue->limitationValues',
47+
'array',
48+
$limitationValue->limitationValues
49+
);
50+
}
51+
52+
foreach ($limitationValue->limitationValues as $key => $id) {
53+
if (!is_int($id)) {
54+
throw new InvalidArgumentType("\$limitationValue->limitationValues[{$key}]", 'int|string', $id);
55+
}
56+
}
57+
}
58+
59+
public function validate(APILimitationValue $limitationValue)
60+
{
61+
$validationErrors = [];
62+
63+
foreach ($limitationValue->limitationValues as $key => $id) {
64+
if ($id === self::SELF_USER_GROUP) {
65+
continue;
66+
}
67+
try {
68+
$this->persistence->contentHandler()->loadContentInfo($id);
69+
} catch (NotFoundException $e) {
70+
$validationErrors[] = new ValidationError(
71+
"limitationValues[%key%] => '%value%' does not exist in the backend",
72+
null,
73+
[
74+
'value' => $id,
75+
'key' => $key,
76+
]
77+
);
78+
}
79+
}
80+
81+
return $validationErrors;
82+
}
83+
84+
/**
85+
* @param mixed[] $limitationValues
86+
*
87+
* @return \eZ\Publish\API\Repository\Values\User\Limitation
88+
*/
89+
public function buildValue(array $limitationValues): APILimitationValue
90+
{
91+
return new MemberOfLimitation(['limitationValues' => $limitationValues]);
92+
}
93+
94+
public function evaluate(APILimitationValue $value, APIUserReference $currentUser, ValueObject $object, array $targets = null)
95+
{
96+
if (!$value instanceof MemberOfLimitation) {
97+
throw new InvalidArgumentException(
98+
'$value',
99+
sprintf('Must be of type: %s', MemberOfLimitation::class)
100+
);
101+
}
102+
103+
if (!$object instanceof User
104+
&& !$object instanceof UserGroup
105+
&& !$object instanceof UserRoleAssignment
106+
&& !$object instanceof UserGroupRoleAssignment
107+
) {
108+
return self::ACCESS_ABSTAIN;
109+
}
110+
111+
if ($object instanceof User) {
112+
return $this->evaluateUser($value, $object, $currentUser);
113+
}
114+
115+
if ($object instanceof UserGroup) {
116+
return $this->evaluateUserGroup($value, $object, $currentUser);
117+
}
118+
119+
if ($object instanceof UserRoleAssignment) {
120+
return $this->evaluateUser($value, $object->getUser(), $currentUser);
121+
}
122+
123+
if ($object instanceof UserGroupRoleAssignment) {
124+
return $this->evaluateUserGroup($value, $object->getUserGroup(), $currentUser);
125+
}
126+
127+
return self::ACCESS_DENIED;
128+
}
129+
130+
public function getCriterion(APILimitationValue $value, APIUserReference $currentUser)
131+
{
132+
throw new NotImplementedException('Member of Limitation Criterion');
133+
}
134+
135+
public function valueSchema()
136+
{
137+
throw new NotImplementedException(__METHOD__);
138+
}
139+
140+
private function evaluateUser(MemberOfLimitation $value, User $object, APIUserReference $currentUser): bool
141+
{
142+
if (empty($value->limitationValues)) {
143+
return self::ACCESS_DENIED;
144+
}
145+
146+
$userLocations = $this->persistence->locationHandler()->loadLocationsByContent($object->getUserId());
147+
148+
$userGroups = [];
149+
foreach ($userLocations as $userLocation) {
150+
$userGroups[] = $this->persistence->locationHandler()->load($userLocation->parentId);
151+
}
152+
$userGroupsIdList = array_column($userGroups, 'contentId');
153+
$limitationValuesUserGroupsIdList = $value->limitationValues;
154+
155+
if (in_array(self::SELF_USER_GROUP, $limitationValuesUserGroupsIdList)) {
156+
$currentUserGroupsIdList = $this->getCurrentUserGroupsIdList($currentUser);
157+
158+
// Granted, if current user is in exactly those same groups
159+
if (count(array_intersect($userGroupsIdList, $currentUserGroupsIdList)) === count($userGroupsIdList)) {
160+
return self::ACCESS_GRANTED;
161+
}
162+
163+
// Unset SELF value, for next check
164+
$key = array_search(self::SELF_USER_GROUP, $limitationValuesUserGroupsIdList);
165+
unset($limitationValuesUserGroupsIdList[$key]);
166+
}
167+
168+
// Granted, if limitationValues matched user groups 1:1
169+
if (!empty($limitationValuesUserGroupsIdList)
170+
&& empty(array_diff($userGroupsIdList, $limitationValuesUserGroupsIdList))
171+
) {
172+
return self::ACCESS_GRANTED;
173+
}
174+
175+
return self::ACCESS_DENIED;
176+
}
177+
178+
private function evaluateUserGroup(MemberOfLimitation $value, UserGroup $userGroup, APIUserReference $currentUser): bool
179+
{
180+
$limitationValuesUserGroupsIdList = $value->limitationValues;
181+
if (in_array(self::SELF_USER_GROUP, $limitationValuesUserGroupsIdList)) {
182+
$limitationValuesUserGroupsIdList = $this->getCurrentUserGroupsIdList($currentUser);
183+
}
184+
185+
return in_array($userGroup->id, $limitationValuesUserGroupsIdList);
186+
}
187+
188+
private function getCurrentUserGroupsIdList(APIUserReference $currentUser): array
189+
{
190+
$currentUserLocations = $this->persistence->locationHandler()->loadLocationsByContent($currentUser->getUserId());
191+
$currentUserGroups = [];
192+
foreach ($currentUserLocations as $currentUserLocation) {
193+
$currentUserGroups[] = $this->persistence->locationHandler()->load($currentUserLocation->parentId);
194+
}
195+
196+
return array_column(
197+
$currentUserGroups,
198+
'contentId'
199+
);
200+
}
201+
}

0 commit comments

Comments
 (0)