1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @link https://www.yiiframework.com/ |
4
|
|
|
* @copyright Copyright (c) 2008 Yii Software LLC |
5
|
|
|
* @license https://www.yiiframework.com/license/ |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace yii\rbac; |
9
|
|
|
|
10
|
|
|
use Yii; |
11
|
|
|
use yii\base\InvalidArgumentException; |
12
|
|
|
use yii\base\InvalidCallException; |
13
|
|
|
use yii\helpers\VarDumper; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* PhpManager represents an authorization manager that stores authorization |
17
|
|
|
* information in terms of a PHP script file. |
18
|
|
|
* |
19
|
|
|
* The authorization data will be saved to and loaded from three files |
20
|
|
|
* specified by [[itemFile]], [[assignmentFile]] and [[ruleFile]]. |
21
|
|
|
* |
22
|
|
|
* PhpManager is mainly suitable for authorization data that is not too big |
23
|
|
|
* (for example, the authorization data for a personal blog system). |
24
|
|
|
* Use [[DbManager]] for more complex authorization data. |
25
|
|
|
* |
26
|
|
|
* Note that PhpManager is not compatible with facebooks [HHVM](https://hhvm.com/) because |
27
|
|
|
* it relies on writing php files and including them afterwards which is not supported by HHVM. |
28
|
|
|
* |
29
|
|
|
* For more details and usage information on PhpManager, see the [guide article on security authorization](guide:security-authorization). |
30
|
|
|
* |
31
|
|
|
* @author Qiang Xue <[email protected]> |
32
|
|
|
* @author Alexander Kochetov <[email protected]> |
33
|
|
|
* @author Christophe Boulain <[email protected]> |
34
|
|
|
* @author Alexander Makarov <[email protected]> |
35
|
|
|
* @since 2.0 |
36
|
|
|
*/ |
37
|
|
|
class PhpManager extends BaseManager |
38
|
|
|
{ |
39
|
|
|
/** |
40
|
|
|
* @var string the path of the PHP script that contains the authorization items. |
41
|
|
|
* This can be either a file path or a [path alias](guide:concept-aliases) to the file. |
42
|
|
|
* Make sure this file is writable by the Web server process if the authorization needs to be changed online. |
43
|
|
|
* @see loadFromFile() |
44
|
|
|
* @see saveToFile() |
45
|
|
|
*/ |
46
|
|
|
public $itemFile = '@app/rbac/items.php'; |
47
|
|
|
/** |
48
|
|
|
* @var string the path of the PHP script that contains the authorization assignments. |
49
|
|
|
* This can be either a file path or a [path alias](guide:concept-aliases) to the file. |
50
|
|
|
* Make sure this file is writable by the Web server process if the authorization needs to be changed online. |
51
|
|
|
* @see loadFromFile() |
52
|
|
|
* @see saveToFile() |
53
|
|
|
*/ |
54
|
|
|
public $assignmentFile = '@app/rbac/assignments.php'; |
55
|
|
|
/** |
56
|
|
|
* @var string the path of the PHP script that contains the authorization rules. |
57
|
|
|
* This can be either a file path or a [path alias](guide:concept-aliases) to the file. |
58
|
|
|
* Make sure this file is writable by the Web server process if the authorization needs to be changed online. |
59
|
|
|
* @see loadFromFile() |
60
|
|
|
* @see saveToFile() |
61
|
|
|
*/ |
62
|
|
|
public $ruleFile = '@app/rbac/rules.php'; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @var Item[] |
66
|
|
|
*/ |
67
|
|
|
protected $items = []; // itemName => item |
68
|
|
|
/** |
69
|
|
|
* @var array |
70
|
|
|
*/ |
71
|
|
|
protected $children = []; // itemName, childName => child |
72
|
|
|
/** |
73
|
|
|
* @var array |
74
|
|
|
*/ |
75
|
|
|
protected $assignments = []; // userId, itemName => assignment |
76
|
|
|
/** |
77
|
|
|
* @var Rule[] |
78
|
|
|
*/ |
79
|
|
|
protected $rules = []; // ruleName => rule |
80
|
|
|
|
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Initializes the application component. |
84
|
|
|
* This method overrides parent implementation by loading the authorization data |
85
|
|
|
* from PHP script. |
86
|
|
|
*/ |
87
|
58 |
|
public function init() |
88
|
|
|
{ |
89
|
58 |
|
parent::init(); |
90
|
58 |
|
$this->itemFile = Yii::getAlias($this->itemFile); |
|
|
|
|
91
|
58 |
|
$this->assignmentFile = Yii::getAlias($this->assignmentFile); |
|
|
|
|
92
|
58 |
|
$this->ruleFile = Yii::getAlias($this->ruleFile); |
|
|
|
|
93
|
58 |
|
$this->load(); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* {@inheritdoc} |
98
|
|
|
*/ |
99
|
28 |
|
public function checkAccess($userId, $permissionName, $params = []) |
100
|
|
|
{ |
101
|
28 |
|
$assignments = $this->getAssignments($userId); |
102
|
|
|
|
103
|
28 |
|
if ($this->hasNoAssignments($assignments)) { |
104
|
10 |
|
return false; |
105
|
|
|
} |
106
|
|
|
|
107
|
20 |
|
return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* {@inheritdoc} |
112
|
|
|
*/ |
113
|
32 |
|
public function getAssignments($userId) |
114
|
|
|
{ |
115
|
32 |
|
return isset($this->assignments[$userId]) ? $this->assignments[$userId] : []; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Performs access check for the specified user. |
120
|
|
|
* This method is internally called by [[checkAccess()]]. |
121
|
|
|
* |
122
|
|
|
* @param string|int $user the user ID. This should can be either an integer or a string representing |
123
|
|
|
* the unique identifier of a user. See [[\yii\web\User::id]]. |
124
|
|
|
* @param string $itemName the name of the operation that need access check |
125
|
|
|
* @param array $params name-value pairs that would be passed to rules associated |
126
|
|
|
* with the tasks and roles assigned to the user. A param with name 'user' is added to this array, |
127
|
|
|
* which holds the value of `$userId`. |
128
|
|
|
* @param Assignment[] $assignments the assignments to the specified user |
129
|
|
|
* @return bool whether the operations can be performed by the user. |
130
|
|
|
*/ |
131
|
20 |
|
protected function checkAccessRecursive($user, $itemName, $params, $assignments) |
132
|
|
|
{ |
133
|
20 |
|
if (!isset($this->items[$itemName])) { |
134
|
1 |
|
return false; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/* @var $item Item */ |
138
|
20 |
|
$item = $this->items[$itemName]; |
139
|
20 |
|
Yii::debug($item instanceof Role ? "Checking role: $itemName" : "Checking permission : $itemName", __METHOD__); |
140
|
|
|
|
141
|
20 |
|
if (!$this->executeRule($user, $item, $params)) { |
142
|
9 |
|
return false; |
143
|
|
|
} |
144
|
|
|
|
145
|
20 |
|
if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) { |
146
|
16 |
|
return true; |
147
|
|
|
} |
148
|
|
|
|
149
|
18 |
|
foreach ($this->children as $parentName => $children) { |
150
|
16 |
|
if (isset($children[$itemName]) && $this->checkAccessRecursive($user, $parentName, $params, $assignments)) { |
151
|
14 |
|
return true; |
152
|
|
|
} |
153
|
|
|
} |
154
|
|
|
|
155
|
5 |
|
return false; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* {@inheritdoc} |
160
|
|
|
* @since 2.0.8 |
161
|
|
|
*/ |
162
|
1 |
|
public function canAddChild($parent, $child) |
163
|
|
|
{ |
164
|
1 |
|
return !$this->detectLoop($parent, $child); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* {@inheritdoc} |
169
|
|
|
*/ |
170
|
47 |
|
public function addChild($parent, $child) |
171
|
|
|
{ |
172
|
47 |
|
if (!isset($this->items[$parent->name], $this->items[$child->name])) { |
173
|
|
|
throw new InvalidArgumentException("Either '{$parent->name}' or '{$child->name}' does not exist."); |
174
|
|
|
} |
175
|
|
|
|
176
|
47 |
|
if ($parent->name === $child->name) { |
177
|
|
|
throw new InvalidArgumentException("Cannot add '{$parent->name} ' as a child of itself."); |
178
|
|
|
} |
179
|
47 |
|
if ($parent instanceof Permission && $child instanceof Role) { |
180
|
|
|
throw new InvalidArgumentException('Cannot add a role as a child of a permission.'); |
181
|
|
|
} |
182
|
|
|
|
183
|
47 |
|
if ($this->detectLoop($parent, $child)) { |
184
|
|
|
throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected."); |
185
|
|
|
} |
186
|
47 |
|
if (isset($this->children[$parent->name][$child->name])) { |
187
|
|
|
throw new InvalidCallException("The item '{$parent->name}' already has a child '{$child->name}'."); |
188
|
|
|
} |
189
|
47 |
|
$this->children[$parent->name][$child->name] = $this->items[$child->name]; |
190
|
47 |
|
$this->saveItems(); |
191
|
|
|
|
192
|
47 |
|
return true; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Checks whether there is a loop in the authorization item hierarchy. |
197
|
|
|
* |
198
|
|
|
* @param Item $parent parent item |
199
|
|
|
* @param Item $child the child item that is to be added to the hierarchy |
200
|
|
|
* @return bool whether a loop exists |
201
|
|
|
*/ |
202
|
47 |
|
protected function detectLoop($parent, $child) |
203
|
|
|
{ |
204
|
47 |
|
if ($child->name === $parent->name) { |
205
|
1 |
|
return true; |
206
|
|
|
} |
207
|
47 |
|
if (!isset($this->children[$child->name], $this->items[$parent->name])) { |
208
|
47 |
|
return false; |
209
|
|
|
} |
210
|
44 |
|
foreach ($this->children[$child->name] as $grandchild) { |
211
|
|
|
/* @var $grandchild Item */ |
212
|
44 |
|
if ($this->detectLoop($parent, $grandchild)) { |
213
|
1 |
|
return true; |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
217
|
44 |
|
return false; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* {@inheritdoc} |
222
|
|
|
*/ |
223
|
|
|
public function removeChild($parent, $child) |
224
|
|
|
{ |
225
|
|
|
if (isset($this->children[$parent->name][$child->name])) { |
226
|
|
|
unset($this->children[$parent->name][$child->name]); |
227
|
|
|
$this->saveItems(); |
228
|
|
|
return true; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
return false; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* {@inheritdoc} |
236
|
|
|
*/ |
237
|
|
|
public function removeChildren($parent) |
238
|
|
|
{ |
239
|
|
|
if (isset($this->children[$parent->name])) { |
240
|
|
|
unset($this->children[$parent->name]); |
241
|
|
|
$this->saveItems(); |
242
|
|
|
return true; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
return false; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* {@inheritdoc} |
250
|
|
|
*/ |
251
|
|
|
public function hasChild($parent, $child) |
252
|
|
|
{ |
253
|
|
|
return isset($this->children[$parent->name][$child->name]); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* {@inheritdoc} |
258
|
|
|
*/ |
259
|
51 |
|
public function assign($role, $userId) |
260
|
|
|
{ |
261
|
51 |
|
if (!isset($this->items[$role->name])) { |
262
|
|
|
throw new InvalidArgumentException("Unknown role '{$role->name}'."); |
263
|
51 |
|
} elseif (isset($this->assignments[$userId][$role->name])) { |
264
|
|
|
throw new InvalidArgumentException("Authorization item '{$role->name}' has already been assigned to user '$userId'."); |
265
|
|
|
} |
266
|
|
|
|
267
|
51 |
|
$this->assignments[$userId][$role->name] = new Assignment([ |
268
|
51 |
|
'userId' => $userId, |
269
|
51 |
|
'roleName' => $role->name, |
270
|
51 |
|
'createdAt' => time(), |
271
|
51 |
|
]); |
272
|
51 |
|
$this->saveAssignments(); |
273
|
|
|
|
274
|
51 |
|
return $this->assignments[$userId][$role->name]; |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* {@inheritdoc} |
279
|
|
|
*/ |
280
|
2 |
|
public function revoke($role, $userId) |
281
|
|
|
{ |
282
|
2 |
|
if (isset($this->assignments[$userId][$role->name])) { |
283
|
2 |
|
unset($this->assignments[$userId][$role->name]); |
284
|
2 |
|
$this->saveAssignments(); |
285
|
2 |
|
return true; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
return false; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
|
|
* {@inheritdoc} |
293
|
|
|
*/ |
294
|
|
|
public function revokeAll($userId) |
295
|
|
|
{ |
296
|
|
|
if (isset($this->assignments[$userId]) && is_array($this->assignments[$userId])) { |
297
|
|
|
foreach ($this->assignments[$userId] as $itemName => $value) { |
298
|
|
|
unset($this->assignments[$userId][$itemName]); |
299
|
|
|
} |
300
|
|
|
$this->saveAssignments(); |
301
|
|
|
return true; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
return false; |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
/** |
308
|
|
|
* {@inheritdoc} |
309
|
|
|
*/ |
310
|
|
|
public function getAssignment($roleName, $userId) |
311
|
|
|
{ |
312
|
|
|
return isset($this->assignments[$userId][$roleName]) ? $this->assignments[$userId][$roleName] : null; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* {@inheritdoc} |
317
|
|
|
*/ |
318
|
4 |
|
public function getItems($type) |
319
|
|
|
{ |
320
|
4 |
|
$items = []; |
321
|
|
|
|
322
|
4 |
|
foreach ($this->items as $name => $item) { |
323
|
|
|
/* @var $item Item */ |
324
|
4 |
|
if ($item->type == $type) { |
325
|
4 |
|
$items[$name] = $item; |
326
|
|
|
} |
327
|
|
|
} |
328
|
|
|
|
329
|
4 |
|
return $items; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
|
333
|
|
|
/** |
334
|
|
|
* {@inheritdoc} |
335
|
|
|
*/ |
336
|
2 |
|
public function removeItem($item) |
337
|
|
|
{ |
338
|
2 |
|
if (isset($this->items[$item->name])) { |
339
|
2 |
|
foreach ($this->children as &$children) { |
340
|
1 |
|
unset($children[$item->name]); |
341
|
|
|
} |
342
|
2 |
|
foreach ($this->assignments as &$assignments) { |
343
|
2 |
|
unset($assignments[$item->name]); |
344
|
|
|
} |
345
|
2 |
|
unset($this->items[$item->name]); |
346
|
2 |
|
$this->saveItems(); |
347
|
2 |
|
$this->saveAssignments(); |
348
|
2 |
|
return true; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
return false; |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* {@inheritdoc} |
356
|
|
|
*/ |
357
|
14 |
|
public function getItem($name) |
358
|
|
|
{ |
359
|
14 |
|
return isset($this->items[$name]) ? $this->items[$name] : null; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* {@inheritdoc} |
364
|
|
|
*/ |
365
|
1 |
|
public function updateRule($name, $rule) |
366
|
|
|
{ |
367
|
1 |
|
if ($rule->name !== $name) { |
368
|
1 |
|
unset($this->rules[$name]); |
369
|
|
|
} |
370
|
1 |
|
$this->rules[$rule->name] = $rule; |
371
|
1 |
|
$this->saveRules(); |
372
|
1 |
|
return true; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* {@inheritdoc} |
377
|
|
|
*/ |
378
|
49 |
|
public function getRule($name) |
379
|
|
|
{ |
380
|
49 |
|
return isset($this->rules[$name]) ? $this->rules[$name] : null; |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* {@inheritdoc} |
385
|
|
|
*/ |
386
|
5 |
|
public function getRules() |
387
|
|
|
{ |
388
|
5 |
|
return $this->rules; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* {@inheritdoc} |
393
|
|
|
* The roles returned by this method include the roles assigned via [[$defaultRoles]]. |
394
|
|
|
*/ |
395
|
2 |
|
public function getRolesByUser($userId) |
396
|
|
|
{ |
397
|
2 |
|
$roles = $this->getDefaultRoleInstances(); |
398
|
2 |
|
foreach ($this->getAssignments($userId) as $name => $assignment) { |
399
|
2 |
|
$role = $this->items[$assignment->roleName]; |
400
|
2 |
|
if ($role->type === Item::TYPE_ROLE) { |
401
|
2 |
|
$roles[$name] = $role; |
402
|
|
|
} |
403
|
|
|
} |
404
|
|
|
|
405
|
2 |
|
return $roles; |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* {@inheritdoc} |
410
|
|
|
*/ |
411
|
1 |
|
public function getChildRoles($roleName) |
412
|
|
|
{ |
413
|
1 |
|
$role = $this->getRole($roleName); |
414
|
|
|
|
415
|
1 |
|
if ($role === null) { |
416
|
|
|
throw new InvalidArgumentException("Role \"$roleName\" not found."); |
417
|
|
|
} |
418
|
|
|
|
419
|
1 |
|
$result = []; |
420
|
1 |
|
$this->getChildrenRecursive($roleName, $result); |
421
|
|
|
|
422
|
1 |
|
$roles = [$roleName => $role]; |
423
|
|
|
|
424
|
1 |
|
$roles += array_filter($this->getRoles(), function (Role $roleItem) use ($result) { |
425
|
1 |
|
return array_key_exists($roleItem->name, $result); |
426
|
1 |
|
}); |
427
|
|
|
|
428
|
1 |
|
return $roles; |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
/** |
432
|
|
|
* {@inheritdoc} |
433
|
|
|
*/ |
434
|
1 |
|
public function getPermissionsByRole($roleName) |
435
|
|
|
{ |
436
|
1 |
|
$result = []; |
437
|
1 |
|
$this->getChildrenRecursive($roleName, $result); |
438
|
1 |
|
if (empty($result)) { |
439
|
|
|
return []; |
440
|
|
|
} |
441
|
1 |
|
$permissions = []; |
442
|
1 |
|
foreach (array_keys($result) as $itemName) { |
443
|
1 |
|
if (isset($this->items[$itemName]) && $this->items[$itemName] instanceof Permission) { |
444
|
1 |
|
$permissions[$itemName] = $this->items[$itemName]; |
445
|
|
|
} |
446
|
|
|
} |
447
|
|
|
|
448
|
1 |
|
return $permissions; |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
/** |
452
|
|
|
* Recursively finds all children and grand children of the specified item. |
453
|
|
|
* |
454
|
|
|
* @param string $name the name of the item whose children are to be looked for. |
455
|
|
|
* @param array $result the children and grand children (in array keys) |
456
|
|
|
*/ |
457
|
3 |
|
protected function getChildrenRecursive($name, &$result) |
458
|
|
|
{ |
459
|
3 |
|
if (isset($this->children[$name])) { |
460
|
3 |
|
foreach ($this->children[$name] as $child) { |
461
|
3 |
|
$result[$child->name] = true; |
462
|
3 |
|
$this->getChildrenRecursive($child->name, $result); |
463
|
|
|
} |
464
|
|
|
} |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* {@inheritdoc} |
469
|
|
|
*/ |
470
|
1 |
|
public function getPermissionsByUser($userId) |
471
|
|
|
{ |
472
|
1 |
|
$directPermission = $this->getDirectPermissionsByUser($userId); |
473
|
1 |
|
$inheritedPermission = $this->getInheritedPermissionsByUser($userId); |
474
|
|
|
|
475
|
1 |
|
return array_merge($directPermission, $inheritedPermission); |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
/** |
479
|
|
|
* Returns all permissions that are directly assigned to user. |
480
|
|
|
* @param string|int $userId the user ID (see [[\yii\web\User::id]]) |
481
|
|
|
* @return Permission[] all direct permissions that the user has. The array is indexed by the permission names. |
482
|
|
|
* @since 2.0.7 |
483
|
|
|
*/ |
484
|
1 |
|
protected function getDirectPermissionsByUser($userId) |
485
|
|
|
{ |
486
|
1 |
|
$permissions = []; |
487
|
1 |
|
foreach ($this->getAssignments($userId) as $name => $assignment) { |
488
|
1 |
|
$permission = $this->items[$assignment->roleName]; |
489
|
1 |
|
if ($permission->type === Item::TYPE_PERMISSION) { |
490
|
1 |
|
$permissions[$name] = $permission; |
491
|
|
|
} |
492
|
|
|
} |
493
|
|
|
|
494
|
1 |
|
return $permissions; |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
/** |
498
|
|
|
* Returns all permissions that the user inherits from the roles assigned to him. |
499
|
|
|
* @param string|int $userId the user ID (see [[\yii\web\User::id]]) |
500
|
|
|
* @return Permission[] all inherited permissions that the user has. The array is indexed by the permission names. |
501
|
|
|
* @since 2.0.7 |
502
|
|
|
*/ |
503
|
1 |
|
protected function getInheritedPermissionsByUser($userId) |
504
|
|
|
{ |
505
|
1 |
|
$assignments = $this->getAssignments($userId); |
506
|
1 |
|
$result = []; |
507
|
1 |
|
foreach (array_keys($assignments) as $roleName) { |
508
|
1 |
|
$this->getChildrenRecursive($roleName, $result); |
509
|
|
|
} |
510
|
|
|
|
511
|
1 |
|
if (empty($result)) { |
512
|
|
|
return []; |
513
|
|
|
} |
514
|
|
|
|
515
|
1 |
|
$permissions = []; |
516
|
1 |
|
foreach (array_keys($result) as $itemName) { |
517
|
1 |
|
if (isset($this->items[$itemName]) && $this->items[$itemName] instanceof Permission) { |
518
|
1 |
|
$permissions[$itemName] = $this->items[$itemName]; |
519
|
|
|
} |
520
|
|
|
} |
521
|
|
|
|
522
|
1 |
|
return $permissions; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
/** |
526
|
|
|
* {@inheritdoc} |
527
|
|
|
*/ |
528
|
1 |
|
public function getChildren($name) |
529
|
|
|
{ |
530
|
1 |
|
return isset($this->children[$name]) ? $this->children[$name] : []; |
531
|
|
|
} |
532
|
|
|
|
533
|
|
|
/** |
534
|
|
|
* {@inheritdoc} |
535
|
|
|
*/ |
536
|
8 |
|
public function removeAll() |
537
|
|
|
{ |
538
|
8 |
|
$this->children = []; |
539
|
8 |
|
$this->items = []; |
540
|
8 |
|
$this->assignments = []; |
541
|
8 |
|
$this->rules = []; |
542
|
8 |
|
$this->save(); |
543
|
|
|
} |
544
|
|
|
|
545
|
|
|
/** |
546
|
|
|
* {@inheritdoc} |
547
|
|
|
*/ |
548
|
1 |
|
public function removeAllPermissions() |
549
|
|
|
{ |
550
|
1 |
|
$this->removeAllItems(Item::TYPE_PERMISSION); |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
/** |
554
|
|
|
* {@inheritdoc} |
555
|
|
|
*/ |
556
|
1 |
|
public function removeAllRoles() |
557
|
|
|
{ |
558
|
1 |
|
$this->removeAllItems(Item::TYPE_ROLE); |
559
|
|
|
} |
560
|
|
|
|
561
|
|
|
/** |
562
|
|
|
* Removes all auth items of the specified type. |
563
|
|
|
* @param int $type the auth item type (either Item::TYPE_PERMISSION or Item::TYPE_ROLE) |
564
|
|
|
*/ |
565
|
2 |
|
protected function removeAllItems($type) |
566
|
|
|
{ |
567
|
2 |
|
$names = []; |
568
|
2 |
|
foreach ($this->items as $name => $item) { |
569
|
2 |
|
if ($item->type == $type) { |
570
|
2 |
|
unset($this->items[$name]); |
571
|
2 |
|
$names[$name] = true; |
572
|
|
|
} |
573
|
|
|
} |
574
|
2 |
|
if (empty($names)) { |
575
|
|
|
return; |
576
|
|
|
} |
577
|
|
|
|
578
|
2 |
|
foreach ($this->assignments as $i => $assignments) { |
579
|
2 |
|
foreach ($assignments as $n => $assignment) { |
580
|
2 |
|
if (isset($names[$assignment->roleName])) { |
581
|
2 |
|
unset($this->assignments[$i][$n]); |
582
|
|
|
} |
583
|
|
|
} |
584
|
|
|
} |
585
|
2 |
|
foreach ($this->children as $name => $children) { |
586
|
2 |
|
if (isset($names[$name])) { |
587
|
1 |
|
unset($this->children[$name]); |
588
|
|
|
} else { |
589
|
1 |
|
foreach ($children as $childName => $item) { |
590
|
1 |
|
if (isset($names[$childName])) { |
591
|
1 |
|
unset($children[$childName]); |
592
|
|
|
} |
593
|
|
|
} |
594
|
1 |
|
$this->children[$name] = $children; |
595
|
|
|
} |
596
|
|
|
} |
597
|
|
|
|
598
|
2 |
|
$this->saveItems(); |
599
|
|
|
} |
600
|
|
|
|
601
|
|
|
/** |
602
|
|
|
* {@inheritdoc} |
603
|
|
|
*/ |
604
|
1 |
|
public function removeAllRules() |
605
|
|
|
{ |
606
|
1 |
|
foreach ($this->items as $item) { |
607
|
1 |
|
$item->ruleName = null; |
608
|
|
|
} |
609
|
1 |
|
$this->rules = []; |
610
|
1 |
|
$this->saveRules(); |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
/** |
614
|
|
|
* {@inheritdoc} |
615
|
|
|
*/ |
616
|
|
|
public function removeAllAssignments() |
617
|
|
|
{ |
618
|
|
|
$this->assignments = []; |
619
|
|
|
$this->saveAssignments(); |
620
|
|
|
} |
621
|
|
|
|
622
|
|
|
/** |
623
|
|
|
* {@inheritdoc} |
624
|
|
|
*/ |
625
|
1 |
|
protected function removeRule($rule) |
626
|
|
|
{ |
627
|
1 |
|
if (isset($this->rules[$rule->name])) { |
628
|
1 |
|
unset($this->rules[$rule->name]); |
629
|
1 |
|
foreach ($this->items as $item) { |
630
|
1 |
|
if ($item->ruleName === $rule->name) { |
631
|
1 |
|
$item->ruleName = null; |
632
|
|
|
} |
633
|
|
|
} |
634
|
1 |
|
$this->saveRules(); |
635
|
1 |
|
return true; |
636
|
|
|
} |
637
|
|
|
|
638
|
|
|
return false; |
639
|
|
|
} |
640
|
|
|
|
641
|
|
|
/** |
642
|
|
|
* {@inheritdoc} |
643
|
|
|
*/ |
644
|
50 |
|
protected function addRule($rule) |
645
|
|
|
{ |
646
|
50 |
|
$this->rules[$rule->name] = $rule; |
647
|
50 |
|
$this->saveRules(); |
648
|
50 |
|
return true; |
649
|
|
|
} |
650
|
|
|
|
651
|
|
|
/** |
652
|
|
|
* {@inheritdoc} |
653
|
|
|
*/ |
654
|
7 |
|
protected function updateItem($name, $item) |
655
|
|
|
{ |
656
|
7 |
|
if ($name !== $item->name) { |
657
|
6 |
|
if (isset($this->items[$item->name])) { |
658
|
1 |
|
throw new InvalidArgumentException("Unable to change the item name. The name '{$item->name}' is already used by another item."); |
659
|
|
|
} |
660
|
|
|
|
661
|
|
|
// Remove old item in case of renaming |
662
|
5 |
|
unset($this->items[$name]); |
663
|
|
|
|
664
|
5 |
|
if (isset($this->children[$name])) { |
665
|
|
|
$this->children[$item->name] = $this->children[$name]; |
666
|
|
|
unset($this->children[$name]); |
667
|
|
|
} |
668
|
5 |
|
foreach ($this->children as &$children) { |
669
|
2 |
|
if (isset($children[$name])) { |
670
|
2 |
|
$children[$item->name] = $children[$name]; |
671
|
2 |
|
unset($children[$name]); |
672
|
|
|
} |
673
|
|
|
} |
674
|
5 |
|
foreach ($this->assignments as &$assignments) { |
675
|
5 |
|
if (isset($assignments[$name])) { |
676
|
3 |
|
$assignments[$item->name] = $assignments[$name]; |
677
|
3 |
|
$assignments[$item->name]->roleName = $item->name; |
678
|
3 |
|
unset($assignments[$name]); |
679
|
|
|
} |
680
|
|
|
} |
681
|
5 |
|
$this->saveAssignments(); |
682
|
|
|
} |
683
|
|
|
|
684
|
6 |
|
$this->items[$item->name] = $item; |
685
|
|
|
|
686
|
6 |
|
$this->saveItems(); |
687
|
6 |
|
return true; |
688
|
|
|
} |
689
|
|
|
|
690
|
|
|
/** |
691
|
|
|
* {@inheritdoc} |
692
|
|
|
*/ |
693
|
53 |
|
protected function addItem($item) |
694
|
|
|
{ |
695
|
53 |
|
$time = time(); |
696
|
53 |
|
if ($item->createdAt === null) { |
697
|
53 |
|
$item->createdAt = $time; |
698
|
|
|
} |
699
|
53 |
|
if ($item->updatedAt === null) { |
700
|
53 |
|
$item->updatedAt = $time; |
701
|
|
|
} |
702
|
|
|
|
703
|
53 |
|
$this->items[$item->name] = $item; |
704
|
|
|
|
705
|
53 |
|
$this->saveItems(); |
706
|
|
|
|
707
|
53 |
|
return true; |
708
|
|
|
} |
709
|
|
|
|
710
|
|
|
/** |
711
|
|
|
* Loads authorization data from persistent storage. |
712
|
|
|
*/ |
713
|
58 |
|
protected function load() |
714
|
|
|
{ |
715
|
58 |
|
$this->children = []; |
716
|
58 |
|
$this->rules = []; |
717
|
58 |
|
$this->assignments = []; |
718
|
58 |
|
$this->items = []; |
719
|
|
|
|
720
|
58 |
|
$items = $this->loadFromFile($this->itemFile); |
721
|
58 |
|
$itemsMtime = @filemtime($this->itemFile); |
722
|
58 |
|
$assignments = $this->loadFromFile($this->assignmentFile); |
723
|
58 |
|
$assignmentsMtime = @filemtime($this->assignmentFile); |
724
|
58 |
|
$rules = $this->loadFromFile($this->ruleFile); |
725
|
|
|
|
726
|
58 |
|
foreach ($items as $name => $item) { |
727
|
6 |
|
$class = $item['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className(); |
|
|
|
|
728
|
|
|
|
729
|
6 |
|
$this->items[$name] = new $class([ |
730
|
6 |
|
'name' => $name, |
731
|
6 |
|
'description' => isset($item['description']) ? $item['description'] : null, |
732
|
6 |
|
'ruleName' => isset($item['ruleName']) ? $item['ruleName'] : null, |
733
|
6 |
|
'data' => isset($item['data']) ? $item['data'] : null, |
734
|
6 |
|
'createdAt' => $itemsMtime, |
735
|
6 |
|
'updatedAt' => $itemsMtime, |
736
|
6 |
|
]); |
737
|
|
|
} |
738
|
|
|
|
739
|
58 |
|
foreach ($items as $name => $item) { |
740
|
6 |
|
if (isset($item['children'])) { |
741
|
6 |
|
foreach ($item['children'] as $childName) { |
742
|
6 |
|
if (isset($this->items[$childName])) { |
743
|
6 |
|
$this->children[$name][$childName] = $this->items[$childName]; |
744
|
|
|
} |
745
|
|
|
} |
746
|
|
|
} |
747
|
|
|
} |
748
|
|
|
|
749
|
58 |
|
foreach ($assignments as $userId => $roles) { |
750
|
6 |
|
foreach ($roles as $role) { |
751
|
6 |
|
$this->assignments[$userId][$role] = new Assignment([ |
752
|
6 |
|
'userId' => $userId, |
753
|
6 |
|
'roleName' => $role, |
754
|
6 |
|
'createdAt' => $assignmentsMtime, |
755
|
6 |
|
]); |
756
|
|
|
} |
757
|
|
|
} |
758
|
|
|
|
759
|
58 |
|
foreach ($rules as $name => $ruleData) { |
760
|
4 |
|
$this->rules[$name] = unserialize($ruleData); |
761
|
|
|
} |
762
|
|
|
} |
763
|
|
|
|
764
|
|
|
/** |
765
|
|
|
* Saves authorization data into persistent storage. |
766
|
|
|
*/ |
767
|
9 |
|
protected function save() |
768
|
|
|
{ |
769
|
9 |
|
$this->saveItems(); |
770
|
9 |
|
$this->saveAssignments(); |
771
|
9 |
|
$this->saveRules(); |
772
|
|
|
} |
773
|
|
|
|
774
|
|
|
/** |
775
|
|
|
* Loads the authorization data from a PHP script file. |
776
|
|
|
* |
777
|
|
|
* @param string $file the file path. |
778
|
|
|
* @return array the authorization data |
779
|
|
|
* @see saveToFile() |
780
|
|
|
*/ |
781
|
58 |
|
protected function loadFromFile($file) |
782
|
|
|
{ |
783
|
58 |
|
if (is_file($file)) { |
784
|
6 |
|
return require $file; |
785
|
|
|
} |
786
|
|
|
|
787
|
57 |
|
return []; |
788
|
|
|
} |
789
|
|
|
|
790
|
|
|
/** |
791
|
|
|
* Saves the authorization data to a PHP script file. |
792
|
|
|
* |
793
|
|
|
* @param array $data the authorization data |
794
|
|
|
* @param string $file the file path. |
795
|
|
|
* @see loadFromFile() |
796
|
|
|
*/ |
797
|
33 |
|
protected function saveToFile($data, $file) |
798
|
|
|
{ |
799
|
33 |
|
file_put_contents($file, "<?php\n\nreturn " . VarDumper::export($data) . ";\n", LOCK_EX); |
800
|
33 |
|
$this->invalidateScriptCache($file); |
801
|
|
|
} |
802
|
|
|
|
803
|
|
|
/** |
804
|
|
|
* Invalidates precompiled script cache (such as OPCache or APC) for the given file. |
805
|
|
|
* @param string $file the file path. |
806
|
|
|
* @since 2.0.9 |
807
|
|
|
*/ |
808
|
33 |
|
protected function invalidateScriptCache($file) |
809
|
|
|
{ |
810
|
33 |
|
if (function_exists('opcache_invalidate')) { |
811
|
33 |
|
opcache_invalidate($file, true); |
812
|
|
|
} |
813
|
33 |
|
if (function_exists('apc_delete_file')) { |
814
|
|
|
@apc_delete_file($file); |
|
|
|
|
815
|
|
|
} |
816
|
|
|
} |
817
|
|
|
|
818
|
|
|
/** |
819
|
|
|
* Saves items data into persistent storage. |
820
|
|
|
*/ |
821
|
54 |
|
protected function saveItems() |
822
|
|
|
{ |
823
|
54 |
|
$items = []; |
824
|
54 |
|
foreach ($this->items as $name => $item) { |
825
|
|
|
/* @var $item Item */ |
826
|
53 |
|
$items[$name] = array_filter( |
827
|
53 |
|
[ |
828
|
53 |
|
'type' => $item->type, |
829
|
53 |
|
'description' => $item->description, |
830
|
53 |
|
'ruleName' => $item->ruleName, |
831
|
53 |
|
'data' => $item->data, |
832
|
53 |
|
] |
833
|
53 |
|
); |
834
|
53 |
|
if (isset($this->children[$name])) { |
835
|
47 |
|
foreach ($this->children[$name] as $child) { |
836
|
|
|
/* @var $child Item */ |
837
|
47 |
|
$items[$name]['children'][] = $child->name; |
838
|
|
|
} |
839
|
|
|
} |
840
|
|
|
} |
841
|
54 |
|
$this->saveToFile($items, $this->itemFile); |
842
|
|
|
} |
843
|
|
|
|
844
|
|
|
/** |
845
|
|
|
* Saves assignments data into persistent storage. |
846
|
|
|
*/ |
847
|
52 |
|
protected function saveAssignments() |
848
|
|
|
{ |
849
|
52 |
|
$assignmentData = []; |
850
|
52 |
|
foreach ($this->assignments as $userId => $assignments) { |
851
|
51 |
|
foreach ($assignments as $name => $assignment) { |
852
|
|
|
/* @var $assignment Assignment */ |
853
|
51 |
|
$assignmentData[$userId][] = $assignment->roleName; |
854
|
|
|
} |
855
|
|
|
} |
856
|
52 |
|
$this->saveToFile($assignmentData, $this->assignmentFile); |
857
|
|
|
} |
858
|
|
|
|
859
|
|
|
/** |
860
|
|
|
* Saves rules data into persistent storage. |
861
|
|
|
*/ |
862
|
53 |
|
protected function saveRules() |
863
|
|
|
{ |
864
|
53 |
|
$rules = []; |
865
|
53 |
|
foreach ($this->rules as $name => $rule) { |
866
|
50 |
|
$rules[$name] = serialize($rule); |
867
|
|
|
} |
868
|
53 |
|
$this->saveToFile($rules, $this->ruleFile); |
869
|
|
|
} |
870
|
|
|
|
871
|
|
|
/** |
872
|
|
|
* {@inheritdoc} |
873
|
|
|
* @since 2.0.7 |
874
|
|
|
*/ |
875
|
1 |
|
public function getUserIdsByRole($roleName) |
876
|
|
|
{ |
877
|
1 |
|
$result = []; |
878
|
1 |
|
foreach ($this->assignments as $userID => $assignments) { |
879
|
1 |
|
foreach ($assignments as $userAssignment) { |
880
|
1 |
|
if ($userAssignment->roleName === $roleName && $userAssignment->userId == $userID) { |
881
|
1 |
|
$result[] = (string) $userID; |
882
|
|
|
} |
883
|
|
|
} |
884
|
|
|
} |
885
|
|
|
|
886
|
1 |
|
return $result; |
887
|
|
|
} |
888
|
|
|
} |
889
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.