Exponential 5.x Kernel 7.5.40 Exponential New Stack (Symfony-based) Kernel — PHP Class Reference

Core/Repository/Permission/PermissionResolver.php
Go to the documentation of this file.
1 <?php
2
7 namespace eZ\Publish\Core\Repository\Permission;
8
9 use eZ\Publish\API\Repository\PermissionResolver as PermissionResolverInterface;
10 use eZ\Publish\API\Repository\Repository as RepositoryInterface;
11 use eZ\Publish\API\Repository\Values\User\Limitation;
12 use eZ\Publish\API\Repository\Values\User\LookupLimitationResult;
13 use eZ\Publish\API\Repository\Values\User\LookupPolicyLimitations;
14 use eZ\Publish\API\Repository\Values\User\UserReference as APIUserReference;
15 use eZ\Publish\API\Repository\Values\ValueObject;
16 use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
17 use eZ\Publish\Core\Repository\Helper\LimitationService;
18 use eZ\Publish\Core\Repository\Helper\RoleDomainMapper;
19 use eZ\Publish\SPI\Limitation\Target;
20 use eZ\Publish\SPI\Limitation\TargetAwareType;
21 use eZ\Publish\SPI\Limitation\Type as LimitationType;
22 use eZ\Publish\SPI\Persistence\User\Handler as UserHandler;
23 use Exception;
24
28 class PermissionResolver implements PermissionResolverInterface
29 {
35 private $sudoNestingLevel = 0;
36
38 private $roleDomainMapper;
39
41 private $limitationService;
42
44 private $userHandler;
45
51 private $currentUserRef;
52
58 private $policyMap;
59
67 public function __construct(
68 RoleDomainMapper $roleDomainMapper,
69 LimitationService $limitationService,
70 UserHandler $userHandler,
71 APIUserReference $userReference,
72 array $policyMap = []
73 ) {
74 $this->roleDomainMapper = $roleDomainMapper;
75 $this->limitationService = $limitationService;
76 $this->userHandler = $userHandler;
77 $this->currentUserRef = $userReference;
78 $this->policyMap = $policyMap;
79 }
80
81 public function getCurrentUserReference()
82 {
83 return $this->currentUserRef;
84 }
85
86 public function setCurrentUserReference(APIUserReference $userReference)
87 {
88 $id = $userReference->getUserId();
89 if (!$id) {
90 throw new InvalidArgumentValue('$user->getUserId()', $id);
91 }
92
93 $this->currentUserRef = $userReference;
94 }
95
96 public function hasAccess($module, $function, APIUserReference $userReference = null)
97 {
98 if (!isset($this->policyMap[$module])) {
99 throw new InvalidArgumentValue('module', "module: {$module}/ function: {$function}");
100 } elseif (!array_key_exists($function, $this->policyMap[$module])) {
101 throw new InvalidArgumentValue('function', "module: {$module}/ function: {$function}");
102 }
103
104 // Full access if sudo nesting level is set by {@see sudo()}
105 if ($this->sudoNestingLevel > 0) {
106 return true;
107 }
108
109 if ($userReference === null) {
110 $userReference = $this->getCurrentUserReference();
111 }
112
113 // Uses SPI to avoid triggering permission checks in Role/User service
114 $permissionSets = [];
115 $spiRoleAssignments = $this->userHandler->loadRoleAssignmentsByGroupId($userReference->getUserId(), true);
116 foreach ($spiRoleAssignments as $spiRoleAssignment) {
117 $permissionSet = ['limitation' => null, 'policies' => []];
118
119 $spiRole = $this->userHandler->loadRole($spiRoleAssignment->roleId);
120 foreach ($spiRole->policies as $spiPolicy) {
121 if ($spiPolicy->module === '*' && $spiRoleAssignment->limitationIdentifier === null) {
122 return true;
123 }
124
125 if ($spiPolicy->module !== $module && $spiPolicy->module !== '*') {
126 continue;
127 }
128
129 if ($spiPolicy->function === '*' && $spiRoleAssignment->limitationIdentifier === null) {
130 return true;
131 }
132
133 if ($spiPolicy->function !== $function && $spiPolicy->function !== '*') {
134 continue;
135 }
136
137 if ($spiPolicy->limitations === '*' && $spiRoleAssignment->limitationIdentifier === null) {
138 return true;
139 }
140
141 $permissionSet['policies'][] = $this->roleDomainMapper->buildDomainPolicyObject($spiPolicy);
142 }
143
144 if (!empty($permissionSet['policies'])) {
145 if ($spiRoleAssignment->limitationIdentifier !== null) {
146 $permissionSet['limitation'] = $this->limitationService
147 ->getLimitationType($spiRoleAssignment->limitationIdentifier)
148 ->buildValue($spiRoleAssignment->values);
149 }
150
151 $permissionSets[] = $permissionSet;
152 }
153 }
154
155 if (!empty($permissionSets)) {
156 return $permissionSets;
157 }
158
159 return false; // No policies matching $module and $function, or they contained limitations
160 }
161
162 public function canUser($module, $function, ValueObject $object, array $targets = [])
163 {
164 $permissionSets = $this->hasAccess($module, $function);
165 if ($permissionSets === false || $permissionSets === true) {
166 return $permissionSets;
167 }
168
169 if (empty($targets)) {
170 $targets = null;
171 }
172
173 $currentUserRef = $this->getCurrentUserReference();
174 foreach ($permissionSets as $permissionSet) {
183 if (
184 $permissionSet['limitation'] instanceof Limitation
185 && $this->isDeniedByRoleLimitation(
186 $permissionSet['limitation'],
187 $currentUserRef,
188 $object,
189 $targets
190 )
191 ) {
192 continue;
193 }
194
203 foreach ($permissionSet['policies'] as $policy) {
204 $limitations = $policy->getLimitations();
205
206 /*
207 * Return true if policy gives full access (aka no limitations)
208 */
209 if ($limitations === '*') {
210 return true;
211 }
212
213 /*
214 * Loop over limitations, all must return ACCESS_GRANTED for policy to pass.
215 * If limitations was empty array this means same as '*'
216 */
217 $limitationsPass = true;
218 foreach ($limitations as $limitation) {
219 $type = $this->limitationService->getLimitationType($limitation->getIdentifier());
220 $accessVote = $type->evaluate(
221 $limitation,
222 $currentUserRef,
223 $object,
224 $this->prepareTargetsForType($targets, $type)
225 );
226 /*
227 * For policy limitation atm only support ACCESS_GRANTED
228 *
229 * Reasoning: Right now, use of a policy limitation not valid for a policy is per definition a
230 * BadState. To reach this you would have to configure the "policyMap" wrongly, like using
231 * Node (Location) limitation on state/assign. So in this case Role Limitations will return
232 * ACCESS_ABSTAIN (== no access here), and other limitations will throw InvalidArgument above,
233 * both cases forcing dev to investigate to find miss configuration. This might be relaxed in
234 * the future if valid use cases for ACCESS_ABSTAIN on policy limitations becomes known.
235 */
236 if ($accessVote !== LimitationType::ACCESS_GRANTED) {
237 $limitationsPass = false;
238 break; // Break to next policy, all limitations must pass
239 }
240 }
241 if ($limitationsPass) {
242 return true;
243 }
244 }
245 }
246
247 return false; // None of the limitation sets wanted to let you in, sorry!
248 }
249
253 public function lookupLimitations(
254 string $module,
255 string $function,
256 ValueObject $object,
257 array $targets = [],
258 array $limitationsIdentifiers = []
259 ): LookupLimitationResult {
260 $permissionSets = $this->hasAccess($module, $function);
261
262 if (is_bool($permissionSets)) {
263 return new LookupLimitationResult($permissionSets);
264 }
265
266 if (empty($targets)) {
267 $targets = null;
268 }
269
270 $currentUserReference = $this->getCurrentUserReference();
271
272 $passedLimitations = [];
273 $passedRoleLimitations = [];
274
275 foreach ($permissionSets as $permissionSet) {
276 if ($this->isDeniedByRoleLimitation($permissionSet['limitation'], $currentUserReference, $object, $targets)) {
277 continue;
278 }
279
281 foreach ($permissionSet['policies'] as $policy) {
282 $policyLimitations = $policy->getLimitations();
283
285 if ($policyLimitations === '*') {
286 return new LookupLimitationResult(true);
287 }
288
289 $limitationsPass = true;
290 $possibleLimitations = [];
291 $possibleRoleLimitation = $permissionSet['limitation'];
292 foreach ($policyLimitations as $limitation) {
293 $limitationsPass = $this->isGrantedByLimitation($limitation, $currentUserReference, $object, $targets);
294 if (!$limitationsPass) {
295 break;
296 }
297
298 $possibleLimitations[] = $limitation;
299 }
300
301 $limitationFilter = function (Limitation $limitation) use ($limitationsIdentifiers) {
302 return \in_array($limitation->getIdentifier(), $limitationsIdentifiers, true);
303 };
304
305 if (!empty($limitationsIdentifiers)) {
306 $possibleLimitations = array_filter($possibleLimitations, $limitationFilter);
307 if (!\in_array($possibleRoleLimitation, $limitationsIdentifiers, true)) {
308 $possibleRoleLimitation = null;
309 }
310 }
311
312 if ($limitationsPass) {
313 $passedLimitations[] = new LookupPolicyLimitations($policy, $possibleLimitations);
314 if (null !== $possibleRoleLimitation) {
315 $passedRoleLimitations[] = $possibleRoleLimitation;
316 }
317 }
318 }
319 }
320
321 return new LookupLimitationResult(!empty($passedLimitations), $passedRoleLimitations, $passedLimitations);
322 }
323
335 private function isGrantedByLimitation(
336 Limitation $limitation,
337 APIUserReference $currentUserReference,
338 ValueObject $object,
339 ?array $targets
340 ): bool {
341 $type = $this->limitationService->getLimitationType($limitation->getIdentifier());
342 $accessVote = $type->evaluate(
343 $limitation,
344 $currentUserReference,
345 $object,
346 $this->prepareTargetsForType($targets, $type)
347 );
348
349 return $accessVote === LimitationType::ACCESS_GRANTED;
350 }
351
363 private function isDeniedByRoleLimitation(
364 ?Limitation $limitation,
365 APIUserReference $currentUserReference,
366 ValueObject $object,
367 ?array $targets
368 ): bool {
369 if (null === $limitation) {
370 return false;
371 }
372
373 $type = $this->limitationService->getLimitationType($limitation->getIdentifier());
374 $accessVote = $type->evaluate(
375 $limitation,
376 $currentUserReference,
377 $object,
378 $this->prepareTargetsForType($targets, $type)
379 );
380
381 return $accessVote === LimitationType::ACCESS_DENIED;
382 }
383
408 public function sudo(callable $callback, RepositoryInterface $outerRepository)
409 {
410 ++$this->sudoNestingLevel;
411 try {
412 $returnValue = $callback($outerRepository);
413 } catch (Exception $e) {
414 --$this->sudoNestingLevel;
415 throw $e;
416 }
417
418 --$this->sudoNestingLevel;
419
420 return $returnValue;
421 }
422
431 private function prepareTargetsForType(?array $targets, LimitationType $type): ?array
432 {
433 $isTargetAware = $type instanceof TargetAwareType;
434
435 // BC: null for empty targets is still expected by some Limitations, so needs to be preserved
436 if (null === $targets) {
437 return $isTargetAware ? [] : null;
438 }
439
440 // BC: for TargetAware Limitations return only instances of Target, for others return only non-Target instances
441 $targets = array_filter(
442 $targets,
443 function ($target) use ($isTargetAware) {
444 $isTarget = $target instanceof Target;
445
446 return $isTargetAware ? $isTarget : !$isTarget;
447 }
448 );
449
450 // BC: treat empty targets after filtering as if they were empty the whole time
451 if (!$isTargetAware && empty($targets)) {
452 return null;
453 }
454
455 return $targets;
456 }
457 }
eZ\Publish\API\Repository\Values\User\Limitation
This class represents a Limitation applied to a policy.
Definition: API/Repository/Values/User/Limitation.php:15
eZ\Publish\API\Repository\Values\User\Limitation\getIdentifier
getIdentifier()
Returns the limitation identifier (one of the defined constants) or a custom limitation.
eZ\Publish\API\Repository\Values\User\LookupLimitationResult
This class represents a LookupLimitation for module and function in the context of current User.
Definition: LookupLimitationResult.php:17
eZ\Publish\API\Repository\Values\User\LookupPolicyLimitations
This class represents a result of lookup limitation for module and function in the context of current...
Definition: LookupPolicyLimitations.php:17
eZ\Publish\API\Repository\Values\ValueObject
Definition: API/Repository/Values/ValueObject.php:21
eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue
Invalid Argument Type Exception implementation.
Definition: Base/Exceptions/InvalidArgumentValue.php:17
eZ\Publish\Core\Repository\Helper\LimitationService
Internal service to deal with limitations and limitation types.
Definition: LimitationService.php:19
eZ\Publish\Core\Repository\Helper\RoleDomainMapper
Internal service to map Role objects between API and SPI values.
Definition: RoleDomainMapper.php:30
eZ\Publish\Core\Repository\Permission\PermissionResolver
Core implementation of PermissionResolver interface.
Definition: Core/Repository/Permission/PermissionResolver.php:29
eZ\Publish\Core\Repository\Permission\PermissionResolver\__construct
__construct(RoleDomainMapper $roleDomainMapper, LimitationService $limitationService, UserHandler $userHandler, APIUserReference $userReference, array $policyMap=[])
Definition: Core/Repository/Permission/PermissionResolver.php:67
eZ\Publish\Core\Repository\Permission\PermissionResolver\$userHandler
$userHandler
Definition: Core/Repository/Permission/PermissionResolver.php:44
eZ\Publish\Core\Repository\Permission\PermissionResolver\isGrantedByLimitation
isGrantedByLimitation(Limitation $limitation, APIUserReference $currentUserReference, ValueObject $object, ?array $targets)
Definition: Core/Repository/Permission/PermissionResolver.php:335
eZ\Publish\Core\Repository\Permission\PermissionResolver\prepareTargetsForType
prepareTargetsForType(?array $targets, LimitationType $type)
Prepare list of targets for the given Type keeping BC.
Definition: Core/Repository/Permission/PermissionResolver.php:431
eZ\Publish\Core\Repository\Permission\PermissionResolver\$currentUserRef
$currentUserRef
Definition: Core/Repository/Permission/PermissionResolver.php:51
eZ\Publish\Core\Repository\Permission\PermissionResolver\sudo
sudo(callable $callback, RepositoryInterface $outerRepository)
Definition: Core/Repository/Permission/PermissionResolver.php:408
eZ\Publish\Core\Repository\Permission\PermissionResolver\getCurrentUserReference
getCurrentUserReference()
Get current user reference.
Definition: Core/Repository/Permission/PermissionResolver.php:81
eZ\Publish\Core\Repository\Permission\PermissionResolver\$policyMap
$policyMap
Definition: Core/Repository/Permission/PermissionResolver.php:58
eZ\Publish\Core\Repository\Permission\PermissionResolver\$roleDomainMapper
$roleDomainMapper
Definition: Core/Repository/Permission/PermissionResolver.php:38
eZ\Publish\Core\Repository\Permission\PermissionResolver\isDeniedByRoleLimitation
isDeniedByRoleLimitation(?Limitation $limitation, APIUserReference $currentUserReference, ValueObject $object, ?array $targets)
Definition: Core/Repository/Permission/PermissionResolver.php:363
eZ\Publish\Core\Repository\Permission\PermissionResolver\$sudoNestingLevel
$sudoNestingLevel
Definition: Core/Repository/Permission/PermissionResolver.php:35
eZ\Publish\Core\Repository\Permission\PermissionResolver\setCurrentUserReference
setCurrentUserReference(APIUserReference $userReference)
Definition: Core/Repository/Permission/PermissionResolver.php:86
eZ\Publish\Core\Repository\Permission\PermissionResolver\hasAccess
hasAccess($module, $function, APIUserReference $userReference=null)
Definition: Core/Repository/Permission/PermissionResolver.php:96
eZ\Publish\Core\Repository\Permission\PermissionResolver\$limitationService
$limitationService
Definition: Core/Repository/Permission/PermissionResolver.php:41
eZ\Publish\API\Repository\PermissionResolver
This service provides methods for resolving permissions.
Definition: API/Repository/PermissionResolver.php:17
eZ\Publish\API\Repository\PermissionResolver\canUser
canUser($module, $function, ValueObject $object, array $targets=[])
Indicates if the current user is allowed to perform an action given by the function on the given obje...
Definition: Core/Repository/Permission/PermissionResolver.php:162
eZ\Publish\API\Repository\PermissionResolver\lookupLimitations
lookupLimitations(string $module, string $function, ValueObject $object, array $targets=[], array $limitationsIdentifiers=[])
{}
Definition: Core/Repository/Permission/PermissionResolver.php:253
eZ\Publish\API\Repository\Repository
Repository interface.
Definition: API/Repository/Repository.php:16
eZ\Publish\API\Repository\Values\User\UserReference
This interface represents a user reference for use in sessions and Repository.
Definition: API/Repository/Values/User/UserReference.php:13
eZ\Publish\SPI\Limitation\TargetAwareType
Represents Limitation type.
Definition: TargetAwareType.php:23
eZ\Publish\SPI\Limitation\Target
Marker interface for PermissionResolver::canUser $targets objects.
Definition: SPI/Limitation/Target.php:19
eZ\Publish\SPI\Limitation\Type
This interface represent the Limitation Type.
Definition: SPI/Limitation/Type.php:22
eZ\Publish\SPI\Persistence\User\Handler
Storage Engine handler for user module.
Definition: SPI/Persistence/User/Handler.php:15
eZ\Publish\API\Repository\Values\User\Limitation
Definition: BlockingLimitation.php:7
eZ\Publish\Core\Repository\Permission
Definition: CachedPermissionService.php:7
Copyright © 1998 – 2026 7x. All rights reserved. Licensed under the GNU GPLv2+.