Completed
Push — php-74-ci ( 3d1af5...9001b7 )
by Alexander
24:14 queued 23:56
created

PhpManager   F

Complexity

Total Complexity 149

Size/Duplication

Total Lines 850
Duplicated Lines 0 %

Test Coverage

Coverage 87.46%

Importance

Changes 0
Metric Value
eloc 298
dl 0
loc 850
ccs 307
cts 351
cp 0.8746
rs 2
c 0
b 0
f 0
wmc 149

47 Methods

Rating   Name   Duplication   Size   Complexity  
A checkAccess() 0 9 2
A getItem() 0 3 2
A addItem() 0 15 3
A getChildren() 0 3 2
A canAddChild() 0 3 1
A getInheritedPermissionsByUser() 0 20 6
A getChildrenRecursive() 0 6 3
A getItems() 0 12 3
A saveToFile() 0 4 1
A getAssignments() 0 3 2
A removeChildren() 0 9 2
A removeItem() 0 16 4
B addChild() 0 23 7
A removeAll() 0 7 1
B checkAccessRecursive() 0 25 9
B removeAllItems() 0 34 11
A getAssignment() 0 3 2
A getPermissionsByUser() 0 6 1
A updateRule() 0 8 2
A save() 0 5 1
A removeAllRoles() 0 3 1
A removeRule() 0 14 4
A getDirectPermissionsByUser() 0 11 3
A getUserIdsByRole() 0 12 5
A removeAllPermissions() 0 3 1
A getPermissionsByRole() 0 15 5
A saveRules() 0 7 2
A saveAssignments() 0 10 3
A removeChild() 0 9 2
A hasChild() 0 3 1
A revokeAll() 0 11 4
A removeAllRules() 0 7 2
A getRules() 0 3 1
A saveItems() 0 21 4
A getRule() 0 3 2
A invalidateScriptCache() 0 7 3
B updateItem() 0 34 8
A addRule() 0 5 1
A init() 0 7 1
A detectLoop() 0 16 5
A removeAllAssignments() 0 4 1
C load() 0 48 13
A assign() 0 16 3
A loadFromFile() 0 7 2
A getRolesByUser() 0 11 3
A revoke() 0 9 2
A getChildRoles() 0 18 2

How to fix   Complexity   

Complex Class

Complex classes like PhpManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PhpManager, and based on these observations, apply Extract Interface, too.

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;
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](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](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 57
    public function init()
88
    {
89 57
        parent::init();
90 57
        $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 57
        $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 57
        $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 57
        $this->load();
94 57
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99 27
    public function checkAccess($userId, $permissionName, $params = [])
100
    {
101 27
        $assignments = $this->getAssignments($userId);
102
103 27
        if ($this->hasNoAssignments($assignments)) {
104 9
            return false;
105
        }
106
107 19
        return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments);
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113 31
    public function getAssignments($userId)
114
    {
115 31
        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 19
    protected function checkAccessRecursive($user, $itemName, $params, $assignments)
132
    {
133 19
        if (!isset($this->items[$itemName])) {
134 1
            return false;
135
        }
136
137
        /* @var $item Item */
138 19
        $item = $this->items[$itemName];
139 19
        Yii::debug($item instanceof Role ? "Checking role: $itemName" : "Checking permission : $itemName", __METHOD__);
140
141 19
        if (!$this->executeRule($user, $item, $params)) {
142 9
            return false;
143
        }
144
145 19
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
146 15
            return true;
147
        }
148
149 17
        foreach ($this->children as $parentName => $children) {
150 15
            if (isset($children[$itemName]) && $this->checkAccessRecursive($user, $parentName, $params, $assignments)) {
151 13
                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 46
    public function addChild($parent, $child)
171
    {
172 46
        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 46
        if ($parent->name === $child->name) {
177
            throw new InvalidArgumentException("Cannot add '{$parent->name} ' as a child of itself.");
178
        }
179 46
        if ($parent instanceof Permission && $child instanceof Role) {
180
            throw new InvalidArgumentException('Cannot add a role as a child of a permission.');
181
        }
182
183 46
        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 46
        if (isset($this->children[$parent->name][$child->name])) {
187
            throw new InvalidCallException("The item '{$parent->name}' already has a child '{$child->name}'.");
188
        }
189 46
        $this->children[$parent->name][$child->name] = $this->items[$child->name];
190 46
        $this->saveItems();
191
192 46
        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 46
    protected function detectLoop($parent, $child)
203
    {
204 46
        if ($child->name === $parent->name) {
205 1
            return true;
206
        }
207 46
        if (!isset($this->children[$child->name], $this->items[$parent->name])) {
208 46
            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 50
    public function assign($role, $userId)
260
    {
261 50
        if (!isset($this->items[$role->name])) {
262
            throw new InvalidArgumentException("Unknown role '{$role->name}'.");
263 50
        } 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 50
        $this->assignments[$userId][$role->name] = new Assignment([
268 50
            'userId' => $userId,
269 50
            'roleName' => $role->name,
270 50
            'createdAt' => time(),
271
        ]);
272 50
        $this->saveAssignments();
273
274 50
        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
        $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 3
    }
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 7
    public function removeAll()
537
    {
538 7
        $this->children = [];
539 7
        $this->items = [];
540 7
        $this->assignments = [];
541 7
        $this->rules = [];
542 7
        $this->save();
543 7
    }
544
545
    /**
546
     * {@inheritdoc}
547
     */
548 1
    public function removeAllPermissions()
549
    {
550 1
        $this->removeAllItems(Item::TYPE_PERMISSION);
551 1
    }
552
553
    /**
554
     * {@inheritdoc}
555
     */
556 1
    public function removeAllRoles()
557
    {
558 1
        $this->removeAllItems(Item::TYPE_ROLE);
559 1
    }
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 2
    }
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 1
    }
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 52
    protected function addItem($item)
694
    {
695 52
        $time = time();
696 52
        if ($item->createdAt === null) {
697 52
            $item->createdAt = $time;
698
        }
699 52
        if ($item->updatedAt === null) {
700 52
            $item->updatedAt = $time;
701
        }
702
703 52
        $this->items[$item->name] = $item;
704
705 52
        $this->saveItems();
706
707 52
        return true;
708
    }
709
710
    /**
711
     * Loads authorization data from persistent storage.
712
     */
713 57
    protected function load()
714
    {
715 57
        $this->children = [];
716 57
        $this->rules = [];
717 57
        $this->assignments = [];
718 57
        $this->items = [];
719
720 57
        $items = $this->loadFromFile($this->itemFile);
721 57
        $itemsMtime = @filemtime($this->itemFile);
722 57
        $assignments = $this->loadFromFile($this->assignmentFile);
723 57
        $assignmentsMtime = @filemtime($this->assignmentFile);
724 57
        $rules = $this->loadFromFile($this->ruleFile);
725
726 57
        foreach ($items as $name => $item) {
727 4
            $class = $item['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className();
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

727
            $class = $item['type'] == Item::TYPE_PERMISSION ? /** @scrutinizer ignore-deprecated */ Permission::className() : Role::className();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
728
729 4
            $this->items[$name] = new $class([
730 4
                'name' => $name,
731 4
                'description' => isset($item['description']) ? $item['description'] : null,
732 4
                'ruleName' => isset($item['ruleName']) ? $item['ruleName'] : null,
733 4
                'data' => isset($item['data']) ? $item['data'] : null,
734 4
                'createdAt' => $itemsMtime,
735 4
                'updatedAt' => $itemsMtime,
736
            ]);
737
        }
738
739 57
        foreach ($items as $name => $item) {
740 4
            if (isset($item['children'])) {
741 4
                foreach ($item['children'] as $childName) {
742 4
                    if (isset($this->items[$childName])) {
743 4
                        $this->children[$name][$childName] = $this->items[$childName];
744
                    }
745
                }
746
            }
747
        }
748
749 57
        foreach ($assignments as $userId => $roles) {
750 4
            foreach ($roles as $role) {
751 4
                $this->assignments[$userId][$role] = new Assignment([
752 4
                    'userId' => $userId,
753 4
                    'roleName' => $role,
754 4
                    'createdAt' => $assignmentsMtime,
755
                ]);
756
            }
757
        }
758
759 57
        foreach ($rules as $name => $ruleData) {
760 4
            $this->rules[$name] = unserialize($ruleData);
761
        }
762 57
    }
763
764
    /**
765
     * Saves authorization data into persistent storage.
766
     */
767 8
    protected function save()
768
    {
769 8
        $this->saveItems();
770 8
        $this->saveAssignments();
771 8
        $this->saveRules();
772 8
    }
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 57
    protected function loadFromFile($file)
782
    {
783 57
        if (is_file($file)) {
784 4
            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 32
    protected function saveToFile($data, $file)
798
    {
799 32
        file_put_contents($file, "<?php\nreturn " . VarDumper::export($data) . ";\n", LOCK_EX);
800 32
        $this->invalidateScriptCache($file);
801 32
    }
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 32
    protected function invalidateScriptCache($file)
809
    {
810 32
        if (function_exists('opcache_invalidate')) {
811 32
            opcache_invalidate($file, true);
812
        }
813 32
        if (function_exists('apc_delete_file')) {
814
            @apc_delete_file($file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for apc_delete_file(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

814
            /** @scrutinizer ignore-unhandled */ @apc_delete_file($file);

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...
815
        }
816 32
    }
817
818
    /**
819
     * Saves items data into persistent storage.
820
     */
821 53
    protected function saveItems()
822
    {
823 53
        $items = [];
824 53
        foreach ($this->items as $name => $item) {
825
            /* @var $item Item */
826 52
            $items[$name] = array_filter(
827
                [
828 52
                    'type' => $item->type,
829 52
                    'description' => $item->description,
830 52
                    'ruleName' => $item->ruleName,
831 52
                    'data' => $item->data,
832
                ]
833
            );
834 52
            if (isset($this->children[$name])) {
835 46
                foreach ($this->children[$name] as $child) {
836
                    /* @var $child Item */
837 46
                    $items[$name]['children'][] = $child->name;
838
                }
839
            }
840
        }
841 53
        $this->saveToFile($items, $this->itemFile);
842 53
    }
843
844
    /**
845
     * Saves assignments data into persistent storage.
846
     */
847 51
    protected function saveAssignments()
848
    {
849 51
        $assignmentData = [];
850 51
        foreach ($this->assignments as $userId => $assignments) {
851 50
            foreach ($assignments as $name => $assignment) {
852
                /* @var $assignment Assignment */
853 50
                $assignmentData[$userId][] = $assignment->roleName;
854
            }
855
        }
856 51
        $this->saveToFile($assignmentData, $this->assignmentFile);
857 51
    }
858
859
    /**
860
     * Saves rules data into persistent storage.
861
     */
862 52
    protected function saveRules()
863
    {
864 52
        $rules = [];
865 52
        foreach ($this->rules as $name => $rule) {
866 50
            $rules[$name] = serialize($rule);
867
        }
868 52
        $this->saveToFile($rules, $this->ruleFile);
869 52
    }
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