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 ( 940f7c...e69234 )
by Robert
09:36
created

DbManager::removeAllAssignments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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