PhpManager   F
last analyzed

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 300
cts 343
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 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 getChildRoles() 0 18 2
A detectLoop() 0 16 5
A removeAllAssignments() 0 4 1
A loadFromFile() 0 7 2
A getRolesByUser() 0 11 3
A revoke() 0 9 2
A saveItems() 0 21 4
C load() 0 48 13
A assign() 0 16 3

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 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);
0 ignored issues
show
Documentation Bug introduced by
It seems like Yii::getAlias($this->itemFile) can also be of type false. 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 58
        $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 false. 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 58
        $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 false. 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 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();
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 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);
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
    }
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