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 ( a81d72...271609 )
by Robert
14:15
created

DbManager::getChildRoles()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2.003

Importance

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