1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @copyright Copyright (C) eZ Systems AS. All rights reserved. |
5
|
|
|
* @license For full copyright and license information view LICENSE file distributed with this source code. |
6
|
|
|
*/ |
7
|
|
|
namespace eZ\Publish\Core\Repository; |
8
|
|
|
|
9
|
|
|
use eZ\Publish\API\Repository\PermissionService as PermissionServiceInterface; |
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\UserReference as APIUserReference; |
13
|
|
|
use eZ\Publish\API\Repository\Values\ValueObject; |
14
|
|
|
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue; |
15
|
|
|
use eZ\Publish\Core\Repository\Helper\LimitationService; |
16
|
|
|
use eZ\Publish\Core\Repository\Helper\RoleDomainMapper; |
17
|
|
|
use eZ\Publish\Core\Repository\PermissionResolver\PermissionInfoMapper\Content as ContentPermissionInfoMapper; |
18
|
|
|
use eZ\Publish\Core\Repository\PermissionResolver\PermissionInfoMapper\Aggregate as PermissionInfoMapper; |
19
|
|
|
use eZ\Publish\Core\Repository\PermissionResolver\PermissionResolver; |
20
|
|
|
use eZ\Publish\SPI\Limitation\Type as LimitationType; |
21
|
|
|
use eZ\Publish\SPI\Persistence\User\Handler as UserHandler; |
22
|
|
|
use Exception; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Core implementation of PermissionsService interface. |
26
|
|
|
*/ |
27
|
|
|
class PermissionService implements PermissionServiceInterface |
28
|
|
|
{ |
29
|
|
|
/** |
30
|
|
|
* Counter for the current sudo nesting level {@see sudo()}. |
31
|
|
|
* |
32
|
|
|
* @var int |
33
|
|
|
*/ |
34
|
|
|
private $sudoNestingLevel = 0; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @var \eZ\Publish\Core\Repository\Helper\RoleDomainMapper |
38
|
|
|
*/ |
39
|
|
|
private $roleDomainMapper; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @var \eZ\Publish\Core\Repository\Helper\LimitationService |
43
|
|
|
*/ |
44
|
|
|
private $limitationService; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @var \eZ\Publish\SPI\Persistence\User\Handler |
48
|
|
|
*/ |
49
|
|
|
private $userHandler; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Currently logged in user reference for permission purposes. |
53
|
|
|
* |
54
|
|
|
* @var \eZ\Publish\API\Repository\Values\User\UserReference |
55
|
|
|
*/ |
56
|
|
|
private $currentUserRef; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @var \eZ\Publish\Core\Repository\PermissionResolver\PermissionInfoMapper |
60
|
|
|
*/ |
61
|
|
|
private $permissionInfoMapper; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @param \eZ\Publish\Core\Repository\Helper\RoleDomainMapper $roleDomainMapper |
65
|
|
|
* @param \eZ\Publish\Core\Repository\Helper\LimitationService $limitationService |
66
|
|
|
* @param \eZ\Publish\SPI\Persistence\User\Handler $userHandler |
67
|
|
|
* @param \eZ\Publish\API\Repository\Values\User\UserReference $userReference |
68
|
|
|
*/ |
69
|
|
|
public function __construct( |
70
|
|
|
RoleDomainMapper $roleDomainMapper, |
71
|
|
|
LimitationService $limitationService, |
72
|
|
|
UserHandler $userHandler, |
73
|
|
|
APIUserReference $userReference |
74
|
|
|
) { |
75
|
|
|
$this->roleDomainMapper = $roleDomainMapper; |
76
|
|
|
$this->limitationService = $limitationService; |
77
|
|
|
$this->userHandler = $userHandler; |
78
|
|
|
$this->currentUserRef = $userReference; |
79
|
|
|
|
80
|
|
|
// TODO: inject |
81
|
|
|
$permissionResolver = new PermissionResolver( |
82
|
|
|
$this->roleDomainMapper, |
83
|
|
|
$this->limitationService, |
84
|
|
|
$this->userHandler |
85
|
|
|
); |
86
|
|
|
$this->permissionInfoMapper = new PermissionInfoMapper(); |
87
|
|
|
$contentPermissionInfoMapper = new ContentPermissionInfoMapper($permissionResolver); |
88
|
|
|
$this->permissionInfoMapper->addMapper($contentPermissionInfoMapper); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
public function getCurrentUserReference() |
92
|
|
|
{ |
93
|
|
|
return $this->currentUserRef; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
public function setCurrentUserReference(APIUserReference $userReference) |
97
|
|
|
{ |
98
|
|
|
$id = $userReference->getUserId(); |
99
|
|
|
if (!$id) { |
100
|
|
|
throw new InvalidArgumentValue('$user->getUserId()', $id); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
$this->currentUserRef = $userReference; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
public function hasAccess($module, $function, APIUserReference $userReference = null) |
107
|
|
|
{ |
108
|
|
|
// Full access if sudo nesting level is set by {@see sudo()} |
109
|
|
|
if ($this->sudoNestingLevel > 0) { |
110
|
|
|
return true; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
if ($userReference === null) { |
114
|
|
|
$userReference = $this->getCurrentUserReference(); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
// Uses SPI to avoid triggering permission checks in Role/User service |
118
|
|
|
$permissionSets = array(); |
119
|
|
|
$spiRoleAssignments = $this->userHandler->loadRoleAssignmentsByGroupId($userReference->getUserId(), true); |
120
|
|
|
foreach ($spiRoleAssignments as $spiRoleAssignment) { |
121
|
|
|
$permissionSet = array('limitation' => null, 'policies' => array()); |
122
|
|
|
|
123
|
|
|
$spiRole = $this->userHandler->loadRole($spiRoleAssignment->roleId); |
124
|
|
|
foreach ($spiRole->policies as $spiPolicy) { |
125
|
|
|
if ($spiPolicy->module === '*' && $spiRoleAssignment->limitationIdentifier === null) { |
126
|
|
|
return true; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
if ($spiPolicy->module !== $module && $spiPolicy->module !== '*') { |
130
|
|
|
continue; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
if ($spiPolicy->function === '*' && $spiRoleAssignment->limitationIdentifier === null) { |
134
|
|
|
return true; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
if ($spiPolicy->function !== $function && $spiPolicy->function !== '*') { |
138
|
|
|
continue; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
if ($spiPolicy->limitations === '*' && $spiRoleAssignment->limitationIdentifier === null) { |
142
|
|
|
return true; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
$permissionSet['policies'][] = $this->roleDomainMapper->buildDomainPolicyObject($spiPolicy); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
View Code Duplication |
if (!empty($permissionSet['policies'])) { |
|
|
|
|
149
|
|
|
if ($spiRoleAssignment->limitationIdentifier !== null) { |
150
|
|
|
$permissionSet['limitation'] = $this->limitationService |
151
|
|
|
->getLimitationType($spiRoleAssignment->limitationIdentifier) |
152
|
|
|
->buildValue($spiRoleAssignment->values); |
|
|
|
|
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
$permissionSets[] = $permissionSet; |
156
|
|
|
} |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
if (!empty($permissionSets)) { |
160
|
|
|
return $permissionSets; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
return false;// No policies matching $module and $function, or they contained limitations |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
public function canUser($module, $function, ValueObject $object, array $targets = []) |
167
|
|
|
{ |
168
|
|
|
$permissionSets = $this->hasAccess($module, $function); |
169
|
|
|
if ($permissionSets === false || $permissionSets === true) { |
170
|
|
|
return $permissionSets; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
if (empty($targets)) { |
174
|
|
|
$targets = null; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
$currentUserRef = $this->getCurrentUserReference(); |
178
|
|
|
foreach ($permissionSets as $permissionSet) { |
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* First deal with Role limitation if any. |
181
|
|
|
* |
182
|
|
|
* Here we accept ACCESS_GRANTED and ACCESS_ABSTAIN, the latter in cases where $object and $targets |
183
|
|
|
* are not supported by limitation. |
184
|
|
|
* |
185
|
|
|
* @var \eZ\Publish\API\Repository\Values\User\Limitation[] |
186
|
|
|
*/ |
187
|
|
|
if ($permissionSet['limitation'] instanceof Limitation) { |
188
|
|
|
$type = $this->limitationService->getLimitationType($permissionSet['limitation']->getIdentifier()); |
189
|
|
|
$accessVote = $type->evaluate($permissionSet['limitation'], $currentUserRef, $object, $targets); |
|
|
|
|
190
|
|
|
if ($accessVote === LimitationType::ACCESS_DENIED) { |
191
|
|
|
continue; |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Loop over all policies. |
197
|
|
|
* |
198
|
|
|
* These are already filtered by hasAccess and given hasAccess did not return boolean |
199
|
|
|
* there must be some, so only return true if one of them says yes. |
200
|
|
|
* |
201
|
|
|
* @var \eZ\Publish\API\Repository\Values\User\Policy |
202
|
|
|
*/ |
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
|
|
View Code Duplication |
foreach ($limitations as $limitation) { |
|
|
|
|
219
|
|
|
$type = $this->limitationService->getLimitationType($limitation->getIdentifier()); |
220
|
|
|
$accessVote = $type->evaluate($limitation, $currentUserRef, $object, $targets); |
|
|
|
|
221
|
|
|
/* |
222
|
|
|
* For policy limitation atm only support ACCESS_GRANTED |
223
|
|
|
* |
224
|
|
|
* Reasoning: Right now, use of a policy limitation not valid for a policy is per definition a |
225
|
|
|
* BadState. To reach this you would have to configure the "policyMap" wrongly, like using |
226
|
|
|
* Node (Location) limitation on state/assign. So in this case Role Limitations will return |
227
|
|
|
* ACCESS_ABSTAIN (== no access here), and other limitations will throw InvalidArgument above, |
228
|
|
|
* both cases forcing dev to investigate to find miss configuration. This might be relaxed in |
229
|
|
|
* the future if valid use cases for ACCESS_ABSTAIN on policy limitations becomes known. |
230
|
|
|
*/ |
231
|
|
|
if ($accessVote !== LimitationType::ACCESS_GRANTED) { |
232
|
|
|
$limitationsPass = false; |
233
|
|
|
break;// Break to next policy, all limitations must pass |
234
|
|
|
} |
235
|
|
|
} |
236
|
|
|
if ($limitationsPass) { |
237
|
|
|
return true; |
238
|
|
|
} |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
return false;// None of the limitation sets wanted to let you in, sorry! |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
public function getPermissionInfo(ValueObject $object, APIUserReference $userReference = null) |
246
|
|
|
{ |
247
|
|
|
if ($userReference === null) { |
248
|
|
|
$userReference = $this->getCurrentUserReference(); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
return $this->permissionInfoMapper->map($object, $userReference); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* @internal For internal use only, do not depend on this method. |
256
|
|
|
* |
257
|
|
|
* Allows API execution to be performed with full access sand-boxed. |
258
|
|
|
* |
259
|
|
|
* The closure sandbox will do a catch all on exceptions and rethrow after |
260
|
|
|
* re-setting the sudo flag. |
261
|
|
|
* |
262
|
|
|
* Example use: |
263
|
|
|
* $location = $repository->sudo( |
264
|
|
|
* function ( Repository $repo ) use ( $locationId ) |
265
|
|
|
* { |
266
|
|
|
* return $repo->getLocationService()->loadLocation( $locationId ) |
267
|
|
|
* } |
268
|
|
|
* ); |
269
|
|
|
* |
270
|
|
|
* |
271
|
|
|
* @param \Closure $callback |
272
|
|
|
* @param \eZ\Publish\API\Repository\Repository $outerRepository |
273
|
|
|
* |
274
|
|
|
* @throws \RuntimeException Thrown on recursive sudo() use. |
275
|
|
|
* @throws \Exception Re throws exceptions thrown inside $callback |
276
|
|
|
* |
277
|
|
|
* @return mixed |
278
|
|
|
*/ |
279
|
|
|
public function sudo(\Closure $callback, RepositoryInterface $outerRepository) |
280
|
|
|
{ |
281
|
|
|
++$this->sudoNestingLevel; |
282
|
|
|
try { |
283
|
|
|
$returnValue = $callback($outerRepository); |
284
|
|
|
} catch (Exception $e) { |
285
|
|
|
--$this->sudoNestingLevel; |
286
|
|
|
throw $e; |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
--$this->sudoNestingLevel; |
290
|
|
|
|
291
|
|
|
return $returnValue; |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.