GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 66e815...df733d )
by Robert
16:26
created

DbManager::detectLoop()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.2
c 0
b 0
f 0
ccs 8
cts 8
cp 1
cc 4
eloc 7
nc 4
nop 2
crap 4
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\caching\Cache;
12
use yii\db\Connection;
13
use yii\db\Query;
14
use yii\db\Expression;
15
use yii\base\InvalidCallException;
16
use yii\base\InvalidParamException;
17
use yii\di\Instance;
18
19
/**
20
 * DbManager represents an authorization manager that stores authorization information in database.
21
 *
22
 * The database connection is specified by [[db]]. The database schema could be initialized by applying migration:
23
 *
24
 * ```
25
 * yii migrate --migrationPath=@yii/rbac/migrations/
26
 * ```
27
 *
28
 * If you don't want to use migration and need SQL instead, files for all databases are in migrations directory.
29
 *
30
 * You may change the names of the tables used to store the authorization and rule data by setting [[itemTable]],
31
 * [[itemChildTable]], [[assignmentTable]] and [[ruleTable]].
32
 *
33
 * For more details and usage information on DbManager, see the [guide article on security authorization](guide:security-authorization).
34
 *
35
 * @author Qiang Xue <[email protected]>
36
 * @author Alexander Kochetov <[email protected]>
37
 * @since 2.0
38
 */
39
class DbManager extends BaseManager
40
{
41
    /**
42
     * @var Connection|array|string the DB connection object or the application component ID of the DB connection.
43
     * After the DbManager object is created, if you want to change this property, you should only assign it
44
     * with a DB connection object.
45
     * Starting from version 2.0.2, this can also be a configuration array for creating the object.
46
     */
47
    public $db = 'db';
48
    /**
49
     * @var string the name of the table storing authorization items. Defaults to "auth_item".
50
     */
51
    public $itemTable = '{{%auth_item}}';
52
    /**
53
     * @var string the name of the table storing authorization item hierarchy. Defaults to "auth_item_child".
54
     */
55
    public $itemChildTable = '{{%auth_item_child}}';
56
    /**
57
     * @var string the name of the table storing authorization item assignments. Defaults to "auth_assignment".
58
     */
59
    public $assignmentTable = '{{%auth_assignment}}';
60
    /**
61
     * @var string the name of the table storing rules. Defaults to "auth_rule".
62
     */
63
    public $ruleTable = '{{%auth_rule}}';
64
    /**
65
     * @var Cache|array|string the cache used to improve RBAC performance. This can be one of the following:
66
     *
67
     * - an application component ID (e.g. `cache`)
68
     * - a configuration array
69
     * - a [[\yii\caching\Cache]] object
70
     *
71
     * When this is not set, it means caching is not enabled.
72
     *
73
     * Note that by enabling RBAC cache, all auth items, rules and auth item parent-child relationships will
74
     * be cached and loaded into memory. This will improve the performance of RBAC permission check. However,
75
     * it does require extra memory and as a result may not be appropriate if your RBAC system contains too many
76
     * auth items. You should seek other RBAC implementations (e.g. RBAC based on Redis storage) in this case.
77
     *
78
     * Also note that if you modify RBAC items, rules or parent-child relationships from outside of this component,
79
     * you have to manually call [[invalidateCache()]] to ensure data consistency.
80
     *
81
     * @since 2.0.3
82
     */
83
    public $cache;
84
    /**
85
     * @var string the key used to store RBAC data in cache
86
     * @see cache
87
     * @since 2.0.3
88
     */
89
    public $cacheKey = 'rbac';
90
91
    /**
92
     * @var Item[] all auth items (name => Item)
93
     */
94
    protected $items;
95
    /**
96
     * @var Rule[] all auth rules (name => Rule)
97
     */
98
    protected $rules;
99
    /**
100
     * @var array auth item parent-child relationships (childName => list of parents)
101
     */
102
    protected $parents;
103
104
105
    /**
106
     * Initializes the application component.
107
     * This method overrides the parent implementation by establishing the database connection.
108
     */
109 92
    public function init()
110
    {
111 92
        parent::init();
112 92
        $this->db = Instance::ensure($this->db, Connection::className());
113 92
        if ($this->cache !== null) {
114 23
            $this->cache = Instance::ensure($this->cache, Cache::className());
115 23
        }
116 92
    }
117
118
    /**
119
     * @inheritdoc
120
     */
121 8
    public function checkAccess($userId, $permissionName, $params = [])
122
    {
123 8
        $assignments = $this->getAssignments($userId);
124
125 8
        if ($this->hasNoAssignments($assignments)) {
126 4
            return false;
127
        }
128
129 8
        $this->loadFromCache();
130 8
        if ($this->items !== null) {
131 2
            return $this->checkAccessFromCache($userId, $permissionName, $params, $assignments);
132
        } else {
133 6
            return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments);
134
        }
135
    }
136
137
    /**
138
     * Performs access check for the specified user based on the data loaded from cache.
139
     * This method is internally called by [[checkAccess()]] when [[cache]] is enabled.
140
     * @param string|int $user the user ID. This should can be either an integer or a string representing
141
     * the unique identifier of a user. See [[\yii\web\User::id]].
142
     * @param string $itemName the name of the operation that need access check
143
     * @param array $params name-value pairs that would be passed to rules associated
144
     * with the tasks and roles assigned to the user. A param with name 'user' is added to this array,
145
     * which holds the value of `$userId`.
146
     * @param Assignment[] $assignments the assignments to the specified user
147
     * @return bool whether the operations can be performed by the user.
148
     * @since 2.0.3
149
     */
150 2
    protected function checkAccessFromCache($user, $itemName, $params, $assignments)
151
    {
152 2
        if (!isset($this->items[$itemName])) {
153 1
            return false;
154
        }
155
156 2
        $item = $this->items[$itemName];
157
158 2
        Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
159
160 2
        if (!$this->executeRule($user, $item, $params)) {
161 2
            return false;
162
        }
163
164 2
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
165 2
            return true;
166
        }
167
168 1
        if (!empty($this->parents[$itemName])) {
169 1
            foreach ($this->parents[$itemName] as $parent) {
170 1
                if ($this->checkAccessFromCache($user, $parent, $params, $assignments)) {
171 1
                    return true;
172
                }
173 1
            }
174 1
        }
175
176 1
        return false;
177
    }
178
179
    /**
180
     * Performs access check for the specified user.
181
     * This method is internally called by [[checkAccess()]].
182
     * @param string|int $user the user ID. This should can be either an integer or a string representing
183
     * the unique identifier of a user. See [[\yii\web\User::id]].
184
     * @param string $itemName the name of the operation that need access check
185
     * @param array $params name-value pairs that would be passed to rules associated
186
     * with the tasks and roles assigned to the user. A param with name 'user' is added to this array,
187
     * which holds the value of `$userId`.
188
     * @param Assignment[] $assignments the assignments to the specified user
189
     * @return bool whether the operations can be performed by the user.
190
     */
191 6
    protected function checkAccessRecursive($user, $itemName, $params, $assignments)
192
    {
193 6
        if (($item = $this->getItem($itemName)) === null) {
194 3
            return false;
195
        }
196
197 6
        Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
198
199 6
        if (!$this->executeRule($user, $item, $params)) {
200 6
            return false;
201
        }
202
203 6
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
204 6
            return true;
205
        }
206
207 3
        $query = new Query;
208 3
        $parents = $query->select(['parent'])
209 3
            ->from($this->itemChildTable)
210 3
            ->where(['child' => $itemName])
211 3
            ->column($this->db);
212 3
        foreach ($parents as $parent) {
213 3
            if ($this->checkAccessRecursive($user, $parent, $params, $assignments)) {
214 3
                return true;
215
            }
216 3
        }
217
218 3
        return false;
219
    }
220
221
    /**
222
     * @inheritdoc
223
     */
224 35
    protected function getItem($name)
225
    {
226 35
        if (empty($name)) {
227 3
            return null;
228
        }
229
230 35
        if (!empty($this->items[$name])) {
231 1
            return $this->items[$name];
232
        }
233
234 34
        $row = (new Query)->from($this->itemTable)
235 34
            ->where(['name' => $name])
236 34
            ->one($this->db);
237
238 34
        if ($row === false) {
239 11
            return null;
240
        }
241
242 34
        return $this->populateItem($row);
243
    }
244
245
    /**
246
     * Returns a value indicating whether the database supports cascading update and delete.
247
     * The default implementation will return false for SQLite database and true for all other databases.
248
     * @return bool whether the database supports cascading update and delete.
249
     */
250 24
    protected function supportsCascadeUpdate()
251
    {
252 24
        return strncmp($this->db->getDriverName(), 'sqlite', 6) !== 0;
253
    }
254
255
    /**
256
     * @inheritdoc
257
     */
258 80
    protected function addItem($item)
259
    {
260 80
        $time = time();
261 80
        if ($item->createdAt === null) {
262 80
            $item->createdAt = $time;
263 80
        }
264 80
        if ($item->updatedAt === null) {
265 80
            $item->updatedAt = $time;
266 80
        }
267 80
        $this->db->createCommand()
268 80
            ->insert($this->itemTable, [
269 80
                'name' => $item->name,
270 80
                'type' => $item->type,
271 80
                'description' => $item->description,
272 80
                'rule_name' => $item->ruleName,
273 80
                'data' => $item->data === null ? null : serialize($item->data),
274 80
                'created_at' => $item->createdAt,
275 80
                'updated_at' => $item->updatedAt,
276 80
            ])->execute();
277
278 80
        $this->invalidateCache();
279
280 80
        return true;
281
    }
282
283
    /**
284
     * @inheritdoc
285
     */
286 4
    protected function removeItem($item)
287
    {
288 4
        if (!$this->supportsCascadeUpdate()) {
289
            $this->db->createCommand()
290
                ->delete($this->itemChildTable, ['or', '[[parent]]=:name', '[[child]]=:name'], [':name' => $item->name])
291
                ->execute();
292
            $this->db->createCommand()
293
                ->delete($this->assignmentTable, ['item_name' => $item->name])
294
                ->execute();
295
        }
296
297 4
        $this->db->createCommand()
298 4
            ->delete($this->itemTable, ['name' => $item->name])
299 4
            ->execute();
300
301 4
        $this->invalidateCache();
302
303 4
        return true;
304
    }
305
306
    /**
307
     * @inheritdoc
308
     */
309 8
    protected function updateItem($name, $item)
310
    {
311 8
        if ($item->name !== $name && !$this->supportsCascadeUpdate()) {
312
            $this->db->createCommand()
313
                ->update($this->itemChildTable, ['parent' => $item->name], ['parent' => $name])
314
                ->execute();
315
            $this->db->createCommand()
316
                ->update($this->itemChildTable, ['child' => $item->name], ['child' => $name])
317
                ->execute();
318
            $this->db->createCommand()
319
                ->update($this->assignmentTable, ['item_name' => $item->name], ['item_name' => $name])
320
                ->execute();
321
        }
322
323 8
        $item->updatedAt = time();
324
325 8
        $this->db->createCommand()
326 8
            ->update($this->itemTable, [
327 8
                'name' => $item->name,
328 8
                'description' => $item->description,
329 8
                'rule_name' => $item->ruleName,
330 8
                'data' => $item->data === null ? null : serialize($item->data),
331 8
                'updated_at' => $item->updatedAt,
332 8
            ], [
333 8
                'name' => $name,
334 8
            ])->execute();
335
336 8
        $this->invalidateCache();
337
338 8
        return true;
339
    }
340
341
    /**
342
     * @inheritdoc
343
     */
344 80
    protected function addRule($rule)
345
    {
346 80
        $time = time();
347 80
        if ($rule->createdAt === null) {
348 80
            $rule->createdAt = $time;
349 80
        }
350 80
        if ($rule->updatedAt === null) {
351 80
            $rule->updatedAt = $time;
352 80
        }
353 80
        $this->db->createCommand()
354 80
            ->insert($this->ruleTable, [
355 80
                'name' => $rule->name,
356 80
                'data' => serialize($rule),
357 80
                'created_at' => $rule->createdAt,
358 80
                'updated_at' => $rule->updatedAt,
359 80
            ])->execute();
360
361 80
        $this->invalidateCache();
362
363 80
        return true;
364
    }
365
366
    /**
367
     * @inheritdoc
368
     */
369 4
    protected function updateRule($name, $rule)
370
    {
371 4
        if ($rule->name !== $name && !$this->supportsCascadeUpdate()) {
372
            $this->db->createCommand()
373
                ->update($this->itemTable, ['rule_name' => $rule->name], ['rule_name' => $name])
374
                ->execute();
375
        }
376
377 4
        $rule->updatedAt = time();
378
379 4
        $this->db->createCommand()
380 4
            ->update($this->ruleTable, [
381 4
                'name' => $rule->name,
382 4
                'data' => serialize($rule),
383 4
                'updated_at' => $rule->updatedAt,
384 4
            ], [
385 4
                'name' => $name,
386 4
            ])->execute();
387
388 4
        $this->invalidateCache();
389
390 4
        return true;
391
    }
392
393
    /**
394
     * @inheritdoc
395
     */
396 4
    protected function removeRule($rule)
397
    {
398 4
        if (!$this->supportsCascadeUpdate()) {
399
            $this->db->createCommand()
400
                ->update($this->itemTable, ['rule_name' => null], ['rule_name' => $rule->name])
401
                ->execute();
402
        }
403
404 4
        $this->db->createCommand()
405 4
            ->delete($this->ruleTable, ['name' => $rule->name])
406 4
            ->execute();
407
408 4
        $this->invalidateCache();
409
410 4
        return true;
411
    }
412
413
    /**
414
     * @inheritdoc
415
     */
416 16
    protected function getItems($type)
417
    {
418 16
        $query = (new Query)
419 16
            ->from($this->itemTable)
420 16
            ->where(['type' => $type]);
421
422 16
        $items = [];
423 16
        foreach ($query->all($this->db) as $row) {
424 16
            $items[$row['name']] = $this->populateItem($row);
425 16
        }
426
427 16
        return $items;
428
    }
429
430
    /**
431
     * Populates an auth item with the data fetched from database
432
     * @param array $row the data from the auth item table
433
     * @return Item the populated auth item instance (either Role or Permission)
434
     */
435 76
    protected function populateItem($row)
436
    {
437 76
        $class = $row['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className();
438
439 76
        if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
440 76
            $data = null;
441 76
        }
442
443 76
        return new $class([
444 76
            'name' => $row['name'],
445 76
            'type' => $row['type'],
446 76
            'description' => $row['description'],
447 76
            'ruleName' => $row['rule_name'],
448 76
            'data' => $data,
449 76
            'createdAt' => $row['created_at'],
450 76
            'updatedAt' => $row['updated_at'],
451 76
        ]);
452
    }
453
454
    /**
455
     * @inheritdoc
456
     */
457 8
    public function getRolesByUser($userId)
458
    {
459 8
        if (!isset($userId) || $userId === '') {
460
            return [];
461
        }
462
463 8
        $query = (new Query)->select('b.*')
464 8
            ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
465 8
            ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
466 8
            ->andWhere(['a.user_id' => (string) $userId])
467 8
            ->andWhere(['b.type' => Item::TYPE_ROLE]);
468
469 8
        $roles = [];
470 8
        foreach ($query->all($this->db) as $row) {
471 8
            $roles[$row['name']] = $this->populateItem($row);
472 8
        }
473 8
        return $roles;
474
    }
475
476
    /**
477
     * @inheritdoc
478
     */
479 4
    public function getChildRoles($roleName)
480
    {
481 4
        $role = $this->getRole($roleName);
482
483 4
        if (is_null($role)) {
484
            throw new InvalidParamException("Role \"$roleName\" not found.");
485
        }
486
487 4
        $result = [];
488 4
        $this->getChildrenRecursive($roleName, $this->getChildrenList(), $result);
489
490 4
        $roles = [$roleName => $role];
491
492 4
        $roles += array_filter($this->getRoles(), function (Role $roleItem) use ($result) {
493 4
            return array_key_exists($roleItem->name, $result);
494 4
        });
495
496 4
        return $roles;
497
    }
498
499
    /**
500
     * @inheritdoc
501
     */
502 4
    public function getPermissionsByRole($roleName)
503
    {
504 4
        $childrenList = $this->getChildrenList();
505 4
        $result = [];
506 4
        $this->getChildrenRecursive($roleName, $childrenList, $result);
507 4
        if (empty($result)) {
508
            return [];
509
        }
510 4
        $query = (new Query)->from($this->itemTable)->where([
511 4
            'type' => Item::TYPE_PERMISSION,
512 4
            'name' => array_keys($result),
513 4
        ]);
514 4
        $permissions = [];
515 4
        foreach ($query->all($this->db) as $row) {
516 4
            $permissions[$row['name']] = $this->populateItem($row);
517 4
        }
518 4
        return $permissions;
519
    }
520
521
    /**
522
     * @inheritdoc
523
     */
524 4
    public function getPermissionsByUser($userId)
525
    {
526 4
        if (empty($userId)) {
527
            return [];
528
        }
529
530 4
        $directPermission = $this->getDirectPermissionsByUser($userId);
531 4
        $inheritedPermission = $this->getInheritedPermissionsByUser($userId);
532
533 4
        return array_merge($directPermission, $inheritedPermission);
534
    }
535
536
    /**
537
     * Returns all permissions that are directly assigned to user.
538
     * @param string|int $userId the user ID (see [[\yii\web\User::id]])
539
     * @return Permission[] all direct permissions that the user has. The array is indexed by the permission names.
540
     * @since 2.0.7
541
     */
542 4
    protected function getDirectPermissionsByUser($userId)
543
    {
544 4
        $query = (new Query)->select('b.*')
545 4
            ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
546 4
            ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
547 4
            ->andWhere(['a.user_id' => (string) $userId])
548 4
            ->andWhere(['b.type' => Item::TYPE_PERMISSION]);
549
550 4
        $permissions = [];
551 4
        foreach ($query->all($this->db) as $row) {
552 4
            $permissions[$row['name']] = $this->populateItem($row);
553 4
        }
554 4
        return $permissions;
555
    }
556
557
    /**
558
     * Returns all permissions that the user inherits from the roles assigned to him.
559
     * @param string|int $userId the user ID (see [[\yii\web\User::id]])
560
     * @return Permission[] all inherited permissions that the user has. The array is indexed by the permission names.
561
     * @since 2.0.7
562
     */
563 4
    protected function getInheritedPermissionsByUser($userId)
564
    {
565 4
        $query = (new Query)->select('item_name')
566 4
            ->from($this->assignmentTable)
567 4
            ->where(['user_id' => (string) $userId]);
568
569 4
        $childrenList = $this->getChildrenList();
570 4
        $result = [];
571 4
        foreach ($query->column($this->db) as $roleName) {
572 4
            $this->getChildrenRecursive($roleName, $childrenList, $result);
573 4
        }
574
575 4
        if (empty($result)) {
576
            return [];
577
        }
578
579 4
        $query = (new Query)->from($this->itemTable)->where([
580 4
            'type' => Item::TYPE_PERMISSION,
581 4
            'name' => array_keys($result),
582 4
        ]);
583 4
        $permissions = [];
584 4
        foreach ($query->all($this->db) as $row) {
585 4
            $permissions[$row['name']] = $this->populateItem($row);
586 4
        }
587 4
        return $permissions;
588
    }
589
590
    /**
591
     * Returns the children for every parent.
592
     * @return array the children list. Each array key is a parent item name,
593
     * and the corresponding array value is a list of child item names.
594
     */
595 12
    protected function getChildrenList()
596
    {
597 12
        $query = (new Query)->from($this->itemChildTable);
598 12
        $parents = [];
599 12
        foreach ($query->all($this->db) as $row) {
600 12
            $parents[$row['parent']][] = $row['child'];
601 12
        }
602 12
        return $parents;
603
    }
604
605
    /**
606
     * Recursively finds all children and grand children of the specified item.
607
     * @param string $name the name of the item whose children are to be looked for.
608
     * @param array $childrenList the child list built via [[getChildrenList()]]
609
     * @param array $result the children and grand children (in array keys)
610
     */
611 12
    protected function getChildrenRecursive($name, $childrenList, &$result)
612
    {
613 12
        if (isset($childrenList[$name])) {
614 12
            foreach ($childrenList[$name] as $child) {
615 12
                $result[$child] = true;
616 12
                $this->getChildrenRecursive($child, $childrenList, $result);
617 12
            }
618 12
        }
619 12
    }
620
621
    /**
622
     * @inheritdoc
623
     */
624 76
    public function getRule($name)
625
    {
626 76
        if ($this->rules !== null) {
627 2
            return isset($this->rules[$name]) ? $this->rules[$name] : null;
628
        }
629
630 76
        $row = (new Query)->select(['data'])
631 76
            ->from($this->ruleTable)
632 76
            ->where(['name' => $name])
633 76
            ->one($this->db);
634 76
        return $row === false ? null : unserialize($row['data']);
635
    }
636
637
    /**
638
     * @inheritdoc
639
     */
640 20
    public function getRules()
641
    {
642 20
        if ($this->rules !== null) {
643
            return $this->rules;
644
        }
645
646 20
        $query = (new Query)->from($this->ruleTable);
647
648 20
        $rules = [];
649 20
        foreach ($query->all($this->db) as $row) {
650 12
            $rules[$row['name']] = unserialize($row['data']);
651 20
        }
652
653 20
        return $rules;
654
    }
655
656
    /**
657
     * @inheritdoc
658
     */
659
    public function getAssignment($roleName, $userId)
660
    {
661
        if (empty($userId)) {
662
            return null;
663
        }
664
665
        $row = (new Query)->from($this->assignmentTable)
666
            ->where(['user_id' => (string) $userId, 'item_name' => $roleName])
667
            ->one($this->db);
668
669
        if ($row === false) {
670
            return null;
671
        }
672
673
        return new Assignment([
674
            'userId' => $row['user_id'],
675
            'roleName' => $row['item_name'],
676
            'createdAt' => $row['created_at'],
677
        ]);
678
    }
679
680
    /**
681
     * @inheritdoc
682
     */
683 12
    public function getAssignments($userId)
684
    {
685 12
        if (empty($userId)) {
686 4
            return [];
687
        }
688
689 12
        $query = (new Query)
690 12
            ->from($this->assignmentTable)
691 12
            ->where(['user_id' => (string) $userId]);
692
693 12
        $assignments = [];
694 12
        foreach ($query->all($this->db) as $row) {
695 12
            $assignments[$row['item_name']] = new Assignment([
696 12
                'userId' => $row['user_id'],
697 12
                'roleName' => $row['item_name'],
698 12
                'createdAt' => $row['created_at'],
699 12
            ]);
700 12
        }
701
702 12
        return $assignments;
703
    }
704
705
    /**
706
     * @inheritdoc
707
     * @since 2.0.8
708
     */
709 4
    public function canAddChild($parent, $child)
710
    {
711 4
        return !$this->detectLoop($parent, $child);
712
    }
713
714
    /**
715
     * @inheritdoc
716
     */
717 72
    public function addChild($parent, $child)
718
    {
719 72
        if ($parent->name === $child->name) {
720
            throw new InvalidParamException("Cannot add '{$parent->name}' as a child of itself.");
721
        }
722
723 72
        if ($parent instanceof Permission && $child instanceof Role) {
724
            throw new InvalidParamException('Cannot add a role as a child of a permission.');
725
        }
726
727 72
        if ($this->detectLoop($parent, $child)) {
728
            throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected.");
729
        }
730
731 72
        $this->db->createCommand()
732 72
            ->insert($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
733 72
            ->execute();
734
735 72
        $this->invalidateCache();
736
737 72
        return true;
738
    }
739
740
    /**
741
     * @inheritdoc
742
     */
743
    public function removeChild($parent, $child)
744
    {
745
        $result = $this->db->createCommand()
746
            ->delete($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
747
            ->execute() > 0;
748
749
        $this->invalidateCache();
750
751
        return $result;
752
    }
753
754
    /**
755
     * @inheritdoc
756
     */
757
    public function removeChildren($parent)
758
    {
759
        $result = $this->db->createCommand()
760
            ->delete($this->itemChildTable, ['parent' => $parent->name])
761
            ->execute() > 0;
762
763
        $this->invalidateCache();
764
765
        return $result;
766
    }
767
768
    /**
769
     * @inheritdoc
770
     */
771
    public function hasChild($parent, $child)
772
    {
773
        return (new Query)
774
            ->from($this->itemChildTable)
775
            ->where(['parent' => $parent->name, 'child' => $child->name])
776
            ->one($this->db) !== false;
777
    }
778
779
    /**
780
     * @inheritdoc
781
     */
782 72
    public function getChildren($name)
783
    {
784 72
        $query = (new Query)
785 72
            ->select(['name', 'type', 'description', 'rule_name', 'data', 'created_at', 'updated_at'])
786 72
            ->from([$this->itemTable, $this->itemChildTable])
787 72
            ->where(['parent' => $name, 'name' => new Expression('[[child]]')]);
788
789 72
        $children = [];
790 72
        foreach ($query->all($this->db) as $row) {
791 72
            $children[$row['name']] = $this->populateItem($row);
792 72
        }
793
794 72
        return $children;
795
    }
796
797
    /**
798
     * Checks whether there is a loop in the authorization item hierarchy.
799
     * @param Item $parent the parent item
800
     * @param Item $child the child item to be added to the hierarchy
801
     * @return bool whether a loop exists
802
     */
803 72
    protected function detectLoop($parent, $child)
804
    {
805 72
        if ($child->name === $parent->name) {
806 4
            return true;
807
        }
808 72
        foreach ($this->getChildren($child->name) as $grandchild) {
809 68
            if ($this->detectLoop($parent, $grandchild)) {
810 4
                return true;
811
            }
812 72
        }
813 72
        return false;
814
    }
815
816
    /**
817
     * @inheritdoc
818
     */
819 72
    public function assign($role, $userId)
820
    {
821 72
        $assignment = new Assignment([
822 72
            'userId' => $userId,
823 72
            'roleName' => $role->name,
824 72
            'createdAt' => time(),
825 72
        ]);
826
827 72
        $this->db->createCommand()
828 72
            ->insert($this->assignmentTable, [
829 72
                'user_id' => $assignment->userId,
830 72
                'item_name' => $assignment->roleName,
831 72
                'created_at' => $assignment->createdAt,
832 72
            ])->execute();
833
834 72
        return $assignment;
835
    }
836
837
    /**
838
     * @inheritdoc
839
     */
840
    public function revoke($role, $userId)
841
    {
842
        if (empty($userId)) {
843
            return false;
844
        }
845
846
        return $this->db->createCommand()
847
            ->delete($this->assignmentTable, ['user_id' => (string) $userId, 'item_name' => $role->name])
848
            ->execute() > 0;
849
    }
850
851
    /**
852
     * @inheritdoc
853
     */
854
    public function revokeAll($userId)
855
    {
856
        if (empty($userId)) {
857
            return false;
858
        }
859
860
        return $this->db->createCommand()
861
            ->delete($this->assignmentTable, ['user_id' => (string) $userId])
862
            ->execute() > 0;
863
    }
864
865
    /**
866
     * @inheritdoc
867
     */
868 92
    public function removeAll()
869
    {
870 92
        $this->removeAllAssignments();
871 92
        $this->db->createCommand()->delete($this->itemChildTable)->execute();
872 92
        $this->db->createCommand()->delete($this->itemTable)->execute();
873 92
        $this->db->createCommand()->delete($this->ruleTable)->execute();
874 92
        $this->invalidateCache();
875 92
    }
876
877
    /**
878
     * @inheritdoc
879
     */
880 4
    public function removeAllPermissions()
881
    {
882 4
        $this->removeAllItems(Item::TYPE_PERMISSION);
883 4
    }
884
885
    /**
886
     * @inheritdoc
887
     */
888 4
    public function removeAllRoles()
889
    {
890 4
        $this->removeAllItems(Item::TYPE_ROLE);
891 4
    }
892
893
    /**
894
     * Removes all auth items of the specified type.
895
     * @param int $type the auth item type (either Item::TYPE_PERMISSION or Item::TYPE_ROLE)
896
     */
897 8
    protected function removeAllItems($type)
898
    {
899 8
        if (!$this->supportsCascadeUpdate()) {
900
            $names = (new Query)
901
                ->select(['name'])
902
                ->from($this->itemTable)
903
                ->where(['type' => $type])
904
                ->column($this->db);
905
            if (empty($names)) {
906
                return;
907
            }
908
            $key = $type == Item::TYPE_PERMISSION ? 'child' : 'parent';
909
            $this->db->createCommand()
910
                ->delete($this->itemChildTable, [$key => $names])
911
                ->execute();
912
            $this->db->createCommand()
913
                ->delete($this->assignmentTable, ['item_name' => $names])
914
                ->execute();
915
        }
916 8
        $this->db->createCommand()
917 8
            ->delete($this->itemTable, ['type' => $type])
918 8
            ->execute();
919
920 8
        $this->invalidateCache();
921 8
    }
922
923
    /**
924
     * @inheritdoc
925
     */
926 4
    public function removeAllRules()
927
    {
928 4
        if (!$this->supportsCascadeUpdate()) {
929
            $this->db->createCommand()
930
                ->update($this->itemTable, ['rule_name' => null])
931
                ->execute();
932
        }
933
934 4
        $this->db->createCommand()->delete($this->ruleTable)->execute();
935
936 4
        $this->invalidateCache();
937 4
    }
938
939
    /**
940
     * @inheritdoc
941
     */
942 92
    public function removeAllAssignments()
943
    {
944 92
        $this->db->createCommand()->delete($this->assignmentTable)->execute();
945 92
    }
946
947 92
    public function invalidateCache()
948
    {
949 92
        if ($this->cache !== null) {
950 23
            $this->cache->delete($this->cacheKey);
951 23
            $this->items = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array<integer,object<yii\rbac\Item>> of property $items.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
952 23
            $this->rules = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array<integer,object<yii\rbac\Rule>> of property $rules.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
953 23
            $this->parents = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $parents.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
954 23
        }
955 92
    }
956
957 8
    public function loadFromCache()
958
    {
959 8
        if ($this->items !== null || !$this->cache instanceof Cache) {
960 8
            return;
961
        }
962
963 2
        $data = $this->cache->get($this->cacheKey);
964 2
        if (is_array($data) && isset($data[0], $data[1], $data[2])) {
965
            list ($this->items, $this->rules, $this->parents) = $data;
966
            return;
967
        }
968
969 2
        $query = (new Query)->from($this->itemTable);
970 2
        $this->items = [];
971 2
        foreach ($query->all($this->db) as $row) {
972 2
            $this->items[$row['name']] = $this->populateItem($row);
973 2
        }
974
975 2
        $query = (new Query)->from($this->ruleTable);
976 2
        $this->rules = [];
977 2
        foreach ($query->all($this->db) as $row) {
978 2
            $this->rules[$row['name']] = unserialize($row['data']);
979 2
        }
980
981 2
        $query = (new Query)->from($this->itemChildTable);
982 2
        $this->parents = [];
983 2
        foreach ($query->all($this->db) as $row) {
984 1
            if (isset($this->items[$row['child']])) {
985 1
                $this->parents[$row['child']][] = $row['parent'];
986 1
            }
987 2
        }
988
989 2
        $this->cache->set($this->cacheKey, [$this->items, $this->rules, $this->parents]);
990 2
    }
991
992
    /**
993
     * Returns all role assignment information for the specified role.
994
     * @param string $roleName
995
     * @return Assignment[] the assignments. An empty array will be
996
     * returned if role is not assigned to any user.
997
     * @since 2.0.7
998
     */
999 4
    public function getUserIdsByRole($roleName)
1000
    {
1001 4
        if (empty($roleName)) {
1002
            return [];
1003
        }
1004
1005 4
        return (new Query)->select('[[user_id]]')
1006 4
            ->from($this->assignmentTable)
1007 4
            ->where(['item_name' => $roleName])->column($this->db);
1008
    }
1009
}
1010