Completed
Push — 2.1 ( 224aac...d335fd )
by Dmitry
54:21 queued 13:00
created

PhpManager::getChildRoles()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2.003

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 10
cts 11
cp 0.9091
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 10
nc 2
nop 1
crap 2.003
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\rbac;
9
10
use yii\base\InvalidCallException;
11
use yii\base\InvalidParamException;
12
use Yii;
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](http://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 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 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 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 26
     * from PHP script.
86
     */
87 26
    public function init()
88 26
    {
89 26
        parent::init();
90 26
        $this->itemFile = Yii::getAlias($this->itemFile);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Yii::getAlias($this->itemFile) can also be of type boolean. However, the property $itemFile is declared as type string. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
91 26
        $this->assignmentFile = Yii::getAlias($this->assignmentFile);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Yii::getAlias($this->assignmentFile) can also be of type boolean. However, the property $assignmentFile is declared as type string. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
92 26
        $this->ruleFile = Yii::getAlias($this->ruleFile);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Yii::getAlias($this->ruleFile) can also be of type boolean. However, the property $ruleFile is declared as type string. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
93
        $this->load();
94
    }
95
96
    /**
97 2
     * @inheritdoc
98
     */
99 2
    public function checkAccess($userId, $permissionName, $params = [])
100 2
    {
101
        $assignments = $this->getAssignments($userId);
102
        return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments);
103
    }
104
105
    /**
106 6
     * @inheritdoc
107
     */
108 6
    public function getAssignments($userId)
109
    {
110
        return isset($this->assignments[$userId]) ? $this->assignments[$userId] : [];
111
    }
112
113
    /**
114
     * Performs access check for the specified user.
115
     * This method is internally called by [[checkAccess()]].
116
     *
117
     * @param string|int $user the user ID. This should can be either an integer or a string representing
118
     * the unique identifier of a user. See [[\yii\web\User::id]].
119
     * @param string $itemName the name of the operation that need access check
120
     * @param array $params name-value pairs that would be passed to rules associated
121
     * with the tasks and roles assigned to the user. A param with name 'user' is added to this array,
122
     * which holds the value of `$userId`.
123
     * @param Assignment[] $assignments the assignments to the specified user
124 2
     * @return bool whether the operations can be performed by the user.
125
     */
126 2
    protected function checkAccessRecursive($user, $itemName, $params, $assignments)
127 1
    {
128
        if (!isset($this->items[$itemName])) {
129
            return false;
130
        }
131 2
132 2
        /* @var $item Item */
133
        $item = $this->items[$itemName];
134 2
        Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission : $itemName", __METHOD__);
135 2
136
        if (!$this->executeRule($user, $item, $params)) {
137
            return false;
138 2
        }
139 2
140
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
141
            return true;
142 1
        }
143 1
144 1
        foreach ($this->children as $parentName => $children) {
145
            if (isset($children[$itemName]) && $this->checkAccessRecursive($user, $parentName, $params, $assignments)) {
146 1
                return true;
147
            }
148 1
        }
149
150
        return false;
151
    }
152
153
    /**
154
     * @inheritdoc
155 1
     * @since 2.0.8
156
     */
157 1
    public function canAddChild($parent, $child)
158
    {
159
        return !$this->detectLoop($parent, $child);
160
    }
161
162
    /**
163 21
     * @inheritdoc
164
     */
165 21
    public function addChild($parent, $child)
166
    {
167
        if (!isset($this->items[$parent->name], $this->items[$child->name])) {
168
            throw new InvalidParamException("Either '{$parent->name}' or '{$child->name}' does not exist.");
169 21
        }
170
171
        if ($parent->name === $child->name) {
172 21
            throw new InvalidParamException("Cannot add '{$parent->name} ' as a child of itself.");
173
        }
174
        if ($parent instanceof Permission && $child instanceof Role) {
175
            throw new InvalidParamException('Cannot add a role as a child of a permission.');
176 21
        }
177
178
        if ($this->detectLoop($parent, $child)) {
179 21
            throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected.");
180
        }
181
        if (isset($this->children[$parent->name][$child->name])) {
182 21
            throw new InvalidCallException("The item '{$parent->name}' already has a child '{$child->name}'.");
183 21
        }
184
        $this->children[$parent->name][$child->name] = $this->items[$child->name];
185 21
        $this->saveItems();
186
187
        return true;
188
    }
189
190
    /**
191
     * Checks whether there is a loop in the authorization item hierarchy.
192
     *
193
     * @param Item $parent parent item
194
     * @param Item $child the child item that is to be added to the hierarchy
195 21
     * @return bool whether a loop exists
196
     */
197 21
    protected function detectLoop($parent, $child)
198 1
    {
199
        if ($child->name === $parent->name) {
200 21
            return true;
201 21
        }
202
        if (!isset($this->children[$child->name], $this->items[$parent->name])) {
203 20
            return false;
204
        }
205 20
        foreach ($this->children[$child->name] as $grandchild) {
206 1
            /* @var $grandchild Item */
207
            if ($this->detectLoop($parent, $grandchild)) {
208 20
                return true;
209
            }
210 20
        }
211
212
        return false;
213
    }
214
215
    /**
216
     * @inheritdoc
217
     */
218
    public function removeChild($parent, $child)
219
    {
220
        if (isset($this->children[$parent->name][$child->name])) {
221
            unset($this->children[$parent->name][$child->name]);
222
            $this->saveItems();
223
            return true;
224
        } else {
225
            return false;
226
        }
227
    }
228
229
    /**
230
     * @inheritdoc
231
     */
232
    public function removeChildren($parent)
233
    {
234
        if (isset($this->children[$parent->name])) {
235
            unset($this->children[$parent->name]);
236
            $this->saveItems();
237
            return true;
238
        } else {
239
            return false;
240
        }
241
    }
242
243
    /**
244
     * @inheritdoc
245
     */
246
    public function hasChild($parent, $child)
247
    {
248
        return isset($this->children[$parent->name][$child->name]);
249
    }
250
251
    /**
252 22
     * @inheritdoc
253
     */
254 22
    public function assign($role, $userId)
255
    {
256 22
        if (!isset($this->items[$role->name])) {
257
            throw new InvalidParamException("Unknown role '{$role->name}'.");
258
        } elseif (isset($this->assignments[$userId][$role->name])) {
259 22
            throw new InvalidParamException("Authorization item '{$role->name}' has already been assigned to user '$userId'.");
260 22
        } else {
261 22
            $this->assignments[$userId][$role->name] = new Assignment([
262 22
                'userId' => $userId,
263 22
                'roleName' => $role->name,
264 22
                'createdAt' => time(),
265 22
            ]);
266
            $this->saveAssignments();
267
            return $this->assignments[$userId][$role->name];
268
        }
269
    }
270
271
    /**
272
     * @inheritdoc
273
     */
274
    public function revoke($role, $userId)
275
    {
276
        if (isset($this->assignments[$userId][$role->name])) {
277
            unset($this->assignments[$userId][$role->name]);
278
            $this->saveAssignments();
279
            return true;
280
        } else {
281
            return false;
282
        }
283
    }
284
285
    /**
286
     * @inheritdoc
287
     */
288
    public function revokeAll($userId)
289
    {
290
        if (isset($this->assignments[$userId]) && is_array($this->assignments[$userId])) {
291
            foreach ($this->assignments[$userId] as $itemName => $value) {
292
                unset($this->assignments[$userId][$itemName]);
293
            }
294
            $this->saveAssignments();
295
            return true;
296
        } else {
297
            return false;
298
        }
299
    }
300
301
    /**
302
     * @inheritdoc
303
     */
304
    public function getAssignment($roleName, $userId)
305
    {
306
        return isset($this->assignments[$userId][$roleName]) ? $this->assignments[$userId][$roleName] : null;
307
    }
308
309
    /**
310 3
     * @inheritdoc
311
     */
312 3
    public function getItems($type)
313
    {
314 3
        $items = [];
315
316 3
        foreach ($this->items as $name => $item) {
317 3
            /* @var $item Item */
318 3
            if ($item->type == $type) {
319 3
                $items[$name] = $item;
320
            }
321 3
        }
322
323
        return $items;
324
    }
325
326
327
    /**
328 2
     * @inheritdoc
329
     */
330 2
    public function removeItem($item)
331 2
    {
332 1
        if (isset($this->items[$item->name])) {
333 2
            foreach ($this->children as &$children) {
334 2
                unset($children[$item->name]);
335 2
            }
336 2
            foreach ($this->assignments as &$assignments) {
337 2
                unset($assignments[$item->name]);
338 2
            }
339 2
            unset($this->items[$item->name]);
340 2
            $this->saveItems();
341
            $this->saveAssignments();
342
            return true;
343
        } else {
344
            return false;
345
        }
346
    }
347
348
    /**
349 10
     * @inheritdoc
350
     */
351 10
    public function getItem($name)
352
    {
353
        return isset($this->items[$name]) ? $this->items[$name] : null;
354
    }
355
356
    /**
357 1
     * @inheritdoc
358
     */
359 1
    public function updateRule($name, $rule)
360 1
    {
361 1
        if ($rule->name !== $name) {
362 1
            unset($this->rules[$name]);
363 1
        }
364 1
        $this->rules[$rule->name] = $rule;
365
        $this->saveRules();
366
        return true;
367
    }
368
369
    /**
370 21
     * @inheritdoc
371
     */
372 21
    public function getRule($name)
373
    {
374
        return isset($this->rules[$name]) ? $this->rules[$name] : null;
375
    }
376
377
    /**
378 5
     * @inheritdoc
379
     */
380 5
    public function getRules()
381
    {
382
        return $this->rules;
383
    }
384
385
    /**
386 2
     * @inheritdoc
387
     */
388 2
    public function getRolesByUser($userId)
389 2
    {
390 2
        $roles = [];
391 2
        foreach ($this->getAssignments($userId) as $name => $assignment) {
392 2
            $role = $this->items[$assignment->roleName];
393 2
            if ($role->type === Item::TYPE_ROLE) {
394 2
                $roles[$name] = $role;
395
            }
396 2
        }
397
398
        return $roles;
399
    }
400
401
    /**
402 1
     * @inheritdoc
403
     */
404 1
    public function getChildRoles($roleName)
405 1
    {
406 1
        $role = $this->getRole($roleName);
407
408
        if (is_null($role)) {
409 1
            throw new InvalidParamException("Role \"$roleName\" not found.");
410 1
        }
411 1
412 1
        $result = [];
413 1
        $this->getChildrenRecursive($roleName, $result);
414 1
415 1
        $roles = [$roleName => $role];
416
417
        $roles += array_filter($this->getRoles(), function (Role $roleItem) use ($result) {
418
            return array_key_exists($roleItem->name, $result);
419
        });
420
421
        return $roles;
422
    }
423
424 2
    /**
425
     * @inheritdoc
426 2
     */
427 2
    public function getPermissionsByRole($roleName)
428 2
    {
429 2
        $result = [];
430 2
        $this->getChildrenRecursive($roleName, $result);
431 2
        if (empty($result)) {
432 2
            return [];
433
        }
434
        $permissions = [];
435
        foreach (array_keys($result) as $itemName) {
436
            if (isset($this->items[$itemName]) && $this->items[$itemName] instanceof Permission) {
437 1
                $permissions[$itemName] = $this->items[$itemName];
438
            }
439 1
        }
440 1
        return $permissions;
441
    }
442 1
443
    /**
444
     * Recursively finds all children and grand children of the specified item.
445
     *
446
     * @param string $name the name of the item whose children are to be looked for.
447
     * @param array $result the children and grand children (in array keys)
448
     */
449
    protected function getChildrenRecursive($name, &$result)
450
    {
451 1
        if (isset($this->children[$name])) {
452
            foreach ($this->children[$name] as $child) {
453 1
                $result[$child->name] = true;
454 1
                $this->getChildrenRecursive($child->name, $result);
455 1
            }
456 1
        }
457 1
    }
458 1
459 1
    /**
460
     * @inheritdoc
461 1
     */
462
    public function getPermissionsByUser($userId)
463
    {
464
        $directPermission = $this->getDirectPermissionsByUser($userId);
465
        $inheritedPermission = $this->getInheritedPermissionsByUser($userId);
466
467
        return array_merge($directPermission, $inheritedPermission);
468
    }
469
470 1
    /**
471
     * Returns all permissions that are directly assigned to user.
472 1
     * @param string|int $userId the user ID (see [[\yii\web\User::id]])
473 1
     * @return Permission[] all direct permissions that the user has. The array is indexed by the permission names.
474 1
     * @since 2.0.7
475 1
     */
476 1
    protected function getDirectPermissionsByUser($userId)
477
    {
478 1
        $permissions = [];
479
        foreach ($this->getAssignments($userId) as $name => $assignment) {
480
            $permission = $this->items[$assignment->roleName];
481
            if ($permission->type === Item::TYPE_PERMISSION) {
482 1
                $permissions[$name] = $permission;
483 1
            }
484 1
        }
485 1
486 1
        return $permissions;
487 1
    }
488 1
489
    /**
490
     * Returns all permissions that the user inherits from the roles assigned to him.
491
     * @param string|int $userId the user ID (see [[\yii\web\User::id]])
492
     * @return Permission[] all inherited permissions that the user has. The array is indexed by the permission names.
493
     * @since 2.0.7
494 1
     */
495
    protected function getInheritedPermissionsByUser($userId)
496 1
    {
497
        $assignments = $this->getAssignments($userId);
498
        $result = [];
499
        foreach (array_keys($assignments) as $roleName) {
500
            $this->getChildrenRecursive($roleName, $result);
501
        }
502 2
503
        if (empty($result)) {
504 2
            return [];
505 2
        }
506 2
507 2
        $permissions = [];
508 2
        foreach (array_keys($result) as $itemName) {
509 2
            if (isset($this->items[$itemName]) && $this->items[$itemName] instanceof Permission) {
510
                $permissions[$itemName] = $this->items[$itemName];
511
            }
512
        }
513
        return $permissions;
514 1
    }
515
516 1
    /**
517 1
     * @inheritdoc
518
     */
519
    public function getChildren($name)
520
    {
521
        return isset($this->children[$name]) ? $this->children[$name] : [];
522 1
    }
523
524 1
    /**
525 1
     * @inheritdoc
526
     */
527
    public function removeAll()
528
    {
529
        $this->children = [];
530
        $this->items = [];
531 2
        $this->assignments = [];
532
        $this->rules = [];
533 2
        $this->save();
534 2
    }
535 2
536 2
    /**
537 2
     * @inheritdoc
538 2
     */
539 2
    public function removeAllPermissions()
540 2
    {
541
        $this->removeAllItems(Item::TYPE_PERMISSION);
542
    }
543
544 2
    /**
545 2
     * @inheritdoc
546 2
     */
547 2
    public function removeAllRoles()
548 2
    {
549 2
        $this->removeAllItems(Item::TYPE_ROLE);
550 2
    }
551 2
552 2
    /**
553 1
     * Removes all auth items of the specified type.
554 1
     * @param int $type the auth item type (either Item::TYPE_PERMISSION or Item::TYPE_ROLE)
555 1
     */
556 1
    protected function removeAllItems($type)
557 1
    {
558 1
        $names = [];
559 1
        foreach ($this->items as $name => $item) {
560 1
            if ($item->type == $type) {
561
                unset($this->items[$name]);
562 2
                $names[$name] = true;
563
            }
564 2
        }
565 2
        if (empty($names)) {
566
            return;
567
        }
568
569
        foreach ($this->assignments as $i => $assignments) {
570 1
            foreach ($assignments as $n => $assignment) {
571
                if (isset($names[$assignment->roleName])) {
572 1
                    unset($this->assignments[$i][$n]);
573 1
                }
574 1
            }
575 1
        }
576 1
        foreach ($this->children as $name => $children) {
577 1
            if (isset($names[$name])) {
578
                unset($this->children[$name]);
579
            } else {
580
                foreach ($children as $childName => $item) {
581
                    if (isset($names[$childName])) {
582
                        unset($children[$childName]);
583
                    }
584
                }
585
                $this->children[$name] = $children;
586
            }
587
        }
588
589
        $this->saveItems();
590
    }
591 1
592
    /**
593 1
     * @inheritdoc
594 1
     */
595 1
    public function removeAllRules()
596 1
    {
597 1
        foreach ($this->items as $item) {
598 1
            $item->ruleName = null;
599 1
        }
600 1
        $this->rules = [];
601 1
        $this->saveRules();
602
    }
603
604
    /**
605
     * @inheritdoc
606
     */
607
    public function removeAllAssignments()
608
    {
609
        $this->assignments = [];
610 22
        $this->saveAssignments();
611
    }
612 22
613 22
    /**
614 22
     * @inheritdoc
615
     */
616
    protected function removeRule($rule)
617
    {
618
        if (isset($this->rules[$rule->name])) {
619
            unset($this->rules[$rule->name]);
620 6
            foreach ($this->items as $item) {
621
                if ($item->ruleName === $rule->name) {
622 6
                    $item->ruleName = null;
623 5
                }
624 1
            }
625
            $this->saveRules();
626
            return true;
627 4
        } else {
628
            return false;
629 4
        }
630
    }
631
632
    /**
633 4
     * @inheritdoc
634 2
     */
635 2
    protected function addRule($rule)
636 2
    {
637 2
        $this->rules[$rule->name] = $rule;
638 4
        $this->saveRules();
639 4
        return true;
640 4
    }
641 2
642 2
    /**
643 2
     * @inheritdoc
644 2
     */
645 4
    protected function updateItem($name, $item)
646 4
    {
647
        if ($name !== $item->name) {
648 4
            if (isset($this->items[$item->name])) {
649
                throw new InvalidParamException("Unable to change the item name. The name '{$item->name}' is already used by another item.");
650 5
            } else {
651
                // Remove old item in case of renaming
652 5
                unset($this->items[$name]);
653 5
654
                if (isset($this->children[$name])) {
655
                    $this->children[$item->name] = $this->children[$name];
656
                    unset($this->children[$name]);
657
                }
658
                foreach ($this->children as &$children) {
659 24
                    if (isset($children[$name])) {
660
                        $children[$item->name] = $children[$name];
661 24
                        unset($children[$name]);
662 24
                    }
663 24
                }
664 24
                foreach ($this->assignments as &$assignments) {
665 24
                    if (isset($assignments[$name])) {
666 24
                        $assignments[$item->name] = $assignments[$name];
667 24
                        $assignments[$item->name]->roleName = $item->name;
668
                        unset($assignments[$name]);
669 24
                    }
670
                }
671 24
                $this->saveAssignments();
672
            }
673 24
        }
674
675
        $this->items[$item->name] = $item;
676
677
        $this->saveItems();
678
        return true;
679
    }
680 26
681
    /**
682 26
     * @inheritdoc
683 26
     */
684 26
    protected function addItem($item)
685 26
    {
686
        $time = time();
687 26
        if ($item->createdAt === null) {
688 26
            $item->createdAt = $time;
689 26
        }
690 26
        if ($item->updatedAt === null) {
691 26
            $item->updatedAt = $time;
692
        }
693 26
694 4
        $this->items[$item->name] = $item;
695
696 4
        $this->saveItems();
697 4
698 4
        return true;
699 4
700 4
    }
701 4
702 4
    /**
703 4
     * Loads authorization data from persistent storage.
704 26
     */
705
    protected function load()
706 26
    {
707 4
        $this->children = [];
708 4
        $this->rules = [];
709 4
        $this->assignments = [];
710 4
        $this->items = [];
711 4
712 4
        $items = $this->loadFromFile($this->itemFile);
713 4
        $itemsMtime = @filemtime($this->itemFile);
714 26
        $assignments = $this->loadFromFile($this->assignmentFile);
715
        $assignmentsMtime = @filemtime($this->assignmentFile);
716 26
        $rules = $this->loadFromFile($this->ruleFile);
717 4
718 4
        foreach ($items as $name => $item) {
719 4
            $class = $item['type'] == Item::TYPE_PERMISSION ? Permission::class : Role::class;
720 4
721 4
            $this->items[$name] = new $class([
722 4
                'name' => $name,
723 4
                'description' => isset($item['description']) ? $item['description'] : null,
724 26
                'ruleName' => isset($item['ruleName']) ? $item['ruleName'] : null,
725
                'data' => isset($item['data']) ? $item['data'] : null,
726 26
                'createdAt' => $itemsMtime,
727 4
                'updatedAt' => $itemsMtime,
728 26
            ]);
729 26
        }
730
731
        foreach ($items as $name => $item) {
732
            if (isset($item['children'])) {
733
                foreach ($item['children'] as $childName) {
734 3
                    if (isset($this->items[$childName])) {
735
                        $this->children[$name][$childName] = $this->items[$childName];
736 3
                    }
737 3
                }
738 3
            }
739 3
        }
740
741
        foreach ($assignments as $userId => $roles) {
742
            foreach ($roles as $role) {
743
                $this->assignments[$userId][$role] = new Assignment([
744
                    'userId' => $userId,
745
                    'roleName' => $role,
746
                    'createdAt' => $assignmentsMtime,
747
                ]);
748 26
            }
749
        }
750 26
751 4
        foreach ($rules as $name => $ruleData) {
752
            $this->rules[$name] = unserialize($ruleData);
753 26
        }
754
    }
755
756
    /**
757
     * Saves authorization data into persistent storage.
758
     */
759
    protected function save()
760
    {
761
        $this->saveItems();
762
        $this->saveAssignments();
763
        $this->saveRules();
764 24
    }
765
766 24
    /**
767 24
     * Loads the authorization data from a PHP script file.
768 24
     *
769
     * @param string $file the file path.
770
     * @return array the authorization data
771
     * @see saveToFile()
772
     */
773
    protected function loadFromFile($file)
774
    {
775 24
        if (is_file($file)) {
776
            return require($file);
777 24
        } else {
778 24
            return [];
779 24
        }
780 24
    }
781
782
    /**
783 24
     * Saves the authorization data to a PHP script file.
784
     *
785
     * @param array $data the authorization data
786
     * @param string $file the file path.
787
     * @see loadFromFile()
788 24
     */
789
    protected function saveToFile($data, $file)
790 24
    {
791 24
        file_put_contents($file, "<?php\nreturn " . VarDumper::export($data) . ";\n", LOCK_EX);
792
        $this->invalidateScriptCache($file);
793 24
    }
794
795 24
    /**
796 24
     * Invalidates precompiled script cache (such as OPCache or APC) for the given file.
797 24
     * @param string $file the file path.
798 24
     * @since 2.0.9
799
     */
800 24
    protected function invalidateScriptCache($file)
801 24
    {
802 21
        if (function_exists('opcache_invalidate')) {
803
            opcache_invalidate($file, true);
804 21
        }
805 21
        if (function_exists('apc_delete_file')) {
806 21
            @apc_delete_file($file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
807 24
        }
808 24
    }
809 24
810
    /**
811
     * Saves items data into persistent storage.
812
     */
813
    protected function saveItems()
814 22
    {
815
        $items = [];
816 22
        foreach ($this->items as $name => $item) {
817 22
            /* @var $item Item */
818 22
            $items[$name] = array_filter(
819
                [
820 22
                    'type' => $item->type,
821 22
                    'description' => $item->description,
822 22
                    'ruleName' => $item->ruleName,
823 22
                    'data' => $item->data,
824 22
                ]
825
            );
826
            if (isset($this->children[$name])) {
827
                foreach ($this->children[$name] as $child) {
828
                    /* @var $child Item */
829 23
                    $items[$name]['children'][] = $child->name;
830
                }
831 23
            }
832 23
        }
833 22
        $this->saveToFile($items, $this->itemFile);
834 23
    }
835 23
836 23
    /**
837
     * Saves assignments data into persistent storage.
838
     */
839
    protected function saveAssignments()
840
    {
841
        $assignmentData = [];
842 1
        foreach ($this->assignments as $userId => $assignments) {
843
            foreach ($assignments as $name => $assignment) {
844 1
                /* @var $assignment Assignment */
845 1
                $assignmentData[$userId][] = $assignment->roleName;
846 1
            }
847 1
        }
848 1
        $this->saveToFile($assignmentData, $this->assignmentFile);
849 1
    }
850 1
851 1
    /**
852 1
     * Saves rules data into persistent storage.
853
     */
854
    protected function saveRules()
855
    {
856
        $rules = [];
857
        foreach ($this->rules as $name => $rule) {
858
            $rules[$name] = serialize($rule);
859
        }
860
        $this->saveToFile($rules, $this->ruleFile);
861
    }
862
863
    /**
864
     * @inheritdoc
865
     * @since 2.0.7
866
     */
867
    public function getUserIdsByRole($roleName)
868
    {
869
        $result = [];
870
        foreach ($this->assignments as $userID => $assignments) {
871
            foreach ($assignments as $userAssignment) {
872
                if ($userAssignment->roleName === $roleName && $userAssignment->userId == $userID) {
873
                    $result[] = (string)$userID;
874
                }
875
            }
876
        }
877
        return $result;
878
    }
879
}
880