Completed
Push — prepare-travis-for-js ( a7ee60 )
by Carsten
08:12 queued 01:33
created

DbManager::checkAccessFromCache()   D

Complexity

Conditions 9
Paths 6

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 0
cts 16
cp 0
rs 4.909
c 0
b 0
f 0
cc 9
eloc 14
nc 6
nop 4
crap 90
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 46
    public function init()
110
    {
111 46
        parent::init();
112 46
        $this->db = Instance::ensure($this->db, Connection::className());
113 46
        if ($this->cache !== null) {
114
            $this->cache = Instance::ensure($this->cache, Cache::className());
115
        }
116 46
    }
117
118
    /**
119
     * @inheritdoc
120
     */
121 2
    public function checkAccess($userId, $permissionName, $params = [])
122
    {
123 2
        $assignments = $this->getAssignments($userId);
124 2
        $this->loadFromCache();
125 2
        if ($this->items !== null) {
126
            return $this->checkAccessFromCache($userId, $permissionName, $params, $assignments);
127
        } else {
128 2
            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
    protected function checkAccessFromCache($user, $itemName, $params, $assignments)
146
    {
147
        if (!isset($this->items[$itemName])) {
148
            return false;
149
        }
150
151
        $item = $this->items[$itemName];
152
153
        Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
154
155
        if (!$this->executeRule($user, $item, $params)) {
156
            return false;
157
        }
158
159
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
160
            return true;
161
        }
162
163
        if (!empty($this->parents[$itemName])) {
164
            foreach ($this->parents[$itemName] as $parent) {
165
                if ($this->checkAccessFromCache($user, $parent, $params, $assignments)) {
166
                    return true;
167
                }
168
            }
169
        }
170
171
        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 2
    protected function checkAccessRecursive($user, $itemName, $params, $assignments)
187
    {
188 2
        if (($item = $this->getItem($itemName)) === null) {
189
            return false;
190
        }
191
192 2
        Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
193
194 2
        if (!$this->executeRule($user, $item, $params)) {
195
            return false;
196
        }
197
198 2
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
199 2
            return true;
200
        }
201
202
        $query = new Query;
203
        $parents = $query->select(['parent'])
204
            ->from($this->itemChildTable)
205
            ->where(['child' => $itemName])
206
            ->column($this->db);
207
        foreach ($parents as $parent) {
208
            if ($this->checkAccessRecursive($user, $parent, $params, $assignments)) {
209
                return true;
210
            }
211
        }
212
213
        return false;
214
    }
215
216
    /**
217
     * @inheritdoc
218
     */
219 2
    protected function getItem($name)
220
    {
221 2
        if (empty($name)) {
222
            return null;
223
        }
224
225 2
        if (!empty($this->items[$name])) {
226
            return $this->items[$name];
227
        }
228
229 2
        $row = (new Query)->from($this->itemTable)
230 2
            ->where(['name' => $name])
231 2
            ->one($this->db);
232
233 2
        if ($row === false) {
234
            return null;
235
        }
236
237 2
        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
    protected function supportsCascadeUpdate()
246
    {
247
        return strncmp($this->db->getDriverName(), 'sqlite', 6) !== 0;
248
    }
249
250
    /**
251
     * @inheritdoc
252
     */
253 40
    protected function addItem($item)
254
    {
255 40
        $time = time();
256 40
        if ($item->createdAt === null) {
257 40
            $item->createdAt = $time;
258 40
        }
259 40
        if ($item->updatedAt === null) {
260 40
            $item->updatedAt = $time;
261 40
        }
262 40
        $this->db->createCommand()
263 40
            ->insert($this->itemTable, [
264 40
                'name' => $item->name,
265 40
                'type' => $item->type,
266 40
                'description' => $item->description,
267 40
                'rule_name' => $item->ruleName,
268 40
                'data' => $item->data === null ? null : serialize($item->data),
269 40
                'created_at' => $item->createdAt,
270 40
                'updated_at' => $item->updatedAt,
271 40
            ])->execute();
272
273 40
        $this->invalidateCache();
274
275 40
        return true;
276
    }
277
278
    /**
279
     * @inheritdoc
280
     */
281
    protected function removeItem($item)
282
    {
283
        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
        $this->db->createCommand()
293
            ->delete($this->itemTable, ['name' => $item->name])
294
            ->execute();
295
296
        $this->invalidateCache();
297
298
        return true;
299
    }
300
301
    /**
302
     * @inheritdoc
303
     */
304
    protected function updateItem($name, $item)
305
    {
306
        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
        $item->updatedAt = time();
319
320
        $this->db->createCommand()
321
            ->update($this->itemTable, [
322
                'name' => $item->name,
323
                'description' => $item->description,
324
                'rule_name' => $item->ruleName,
325
                'data' => $item->data === null ? null : serialize($item->data),
326
                'updated_at' => $item->updatedAt,
327
            ], [
328
                'name' => $name,
329
            ])->execute();
330
331
        $this->invalidateCache();
332
333
        return true;
334
    }
335
336
    /**
337
     * @inheritdoc
338
     */
339 40
    protected function addRule($rule)
340
    {
341 40
        $time = time();
342 40
        if ($rule->createdAt === null) {
343 40
            $rule->createdAt = $time;
344 40
        }
345 40
        if ($rule->updatedAt === null) {
346 40
            $rule->updatedAt = $time;
347 40
        }
348 40
        $this->db->createCommand()
349 40
            ->insert($this->ruleTable, [
350 40
                'name' => $rule->name,
351 40
                'data' => serialize($rule),
352 40
                'created_at' => $rule->createdAt,
353 40
                'updated_at' => $rule->updatedAt,
354 40
            ])->execute();
355
356 40
        $this->invalidateCache();
357
358 40
        return true;
359
    }
360
361
    /**
362
     * @inheritdoc
363
     */
364
    protected function updateRule($name, $rule)
365
    {
366
        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
        $rule->updatedAt = time();
373
374
        $this->db->createCommand()
375
            ->update($this->ruleTable, [
376
                'name' => $rule->name,
377
                'data' => serialize($rule),
378
                'updated_at' => $rule->updatedAt,
379
            ], [
380
                'name' => $name,
381
            ])->execute();
382
383
        $this->invalidateCache();
384
385
        return true;
386
    }
387
388
    /**
389
     * @inheritdoc
390
     */
391
    protected function removeRule($rule)
392
    {
393
        if (!$this->supportsCascadeUpdate()) {
394
            $this->db->createCommand()
395
                ->update($this->itemTable, ['rule_name' => null], ['rule_name' => $rule->name])
396
                ->execute();
397
        }
398
399
        $this->db->createCommand()
400
            ->delete($this->ruleTable, ['name' => $rule->name])
401
            ->execute();
402
403
        $this->invalidateCache();
404
405
        return true;
406
    }
407
408
    /**
409
     * @inheritdoc
410
     */
411
    protected function getItems($type)
412
    {
413
        $query = (new Query)
414
            ->from($this->itemTable)
415
            ->where(['type' => $type]);
416
417
        $items = [];
418
        foreach ($query->all($this->db) as $row) {
419
            $items[$row['name']] = $this->populateItem($row);
420
        }
421
422
        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 4
    protected function populateItem($row)
431
    {
432 4
        $class = $row['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className();
433
434 4
        if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
435 4
            $data = null;
436 4
        }
437
438 4
        return new $class([
439 4
            'name' => $row['name'],
440 4
            'type' => $row['type'],
441 4
            'description' => $row['description'],
442 4
            'ruleName' => $row['rule_name'],
443 4
            'data' => $data,
444 4
            'createdAt' => $row['created_at'],
445 4
            'updatedAt' => $row['updated_at'],
446 4
        ]);
447
    }
448
449
    /**
450
     * @inheritdoc
451
     */
452
    public function getRolesByUser($userId)
453
    {
454
        if (!isset($userId) || $userId === '') {
455
            return [];
456
        }
457
458
        $query = (new Query)->select('b.*')
459
            ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
460
            ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
461
            ->andWhere(['a.user_id' => (string) $userId])
462
            ->andWhere(['b.type' => Item::TYPE_ROLE]);
463
464
        $roles = [];
465
        foreach ($query->all($this->db) as $row) {
466
            $roles[$row['name']] = $this->populateItem($row);
467
        }
468
        return $roles;
469
    }
470
471
    /**
472
     * @inheritdoc
473
     */
474
    public function getChildRoles($roleName)
475
    {
476
        $role = $this->getRole($roleName);
477
478
        if (is_null($role)) {
479
            throw new InvalidParamException("Role \"$roleName\" not found.");
480
        }
481
482
        $result = [];
483
        $this->getChildrenRecursive($roleName, $this->getChildrenList(), $result);
484
485
        $roles = [$roleName => $role];
486
487
        $roles += array_filter($this->getRoles(), function (Role $roleItem) use ($result) {
488
            return array_key_exists($roleItem->name, $result);
489
        });
490
491
        return $roles;
492
    }
493
494
    /**
495
     * @inheritdoc
496
     */
497
    public function getPermissionsByRole($roleName)
498
    {
499
        $childrenList = $this->getChildrenList();
500
        $result = [];
501
        $this->getChildrenRecursive($roleName, $childrenList, $result);
502
        if (empty($result)) {
503
            return [];
504
        }
505
        $query = (new Query)->from($this->itemTable)->where([
506
            'type' => Item::TYPE_PERMISSION,
507
            'name' => array_keys($result),
508
        ]);
509
        $permissions = [];
510
        foreach ($query->all($this->db) as $row) {
511
            $permissions[$row['name']] = $this->populateItem($row);
512
        }
513
        return $permissions;
514
    }
515
516
    /**
517
     * @inheritdoc
518
     */
519
    public function getPermissionsByUser($userId)
520
    {
521
        if (empty($userId)) {
522
            return [];
523
        }
524
525
        $directPermission = $this->getDirectPermissionsByUser($userId);
526
        $inheritedPermission = $this->getInheritedPermissionsByUser($userId);
527
528
        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
    protected function getDirectPermissionsByUser($userId)
538
    {
539
        $query = (new Query)->select('b.*')
540
            ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
541
            ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
542
            ->andWhere(['a.user_id' => (string) $userId])
543
            ->andWhere(['b.type' => Item::TYPE_PERMISSION]);
544
545
        $permissions = [];
546
        foreach ($query->all($this->db) as $row) {
547
            $permissions[$row['name']] = $this->populateItem($row);
548
        }
549
        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
    protected function getInheritedPermissionsByUser($userId)
559
    {
560
        $query = (new Query)->select('item_name')
561
            ->from($this->assignmentTable)
562
            ->where(['user_id' => (string) $userId]);
563
564
        $childrenList = $this->getChildrenList();
565
        $result = [];
566
        foreach ($query->column($this->db) as $roleName) {
567
            $this->getChildrenRecursive($roleName, $childrenList, $result);
568
        }
569
570
        if (empty($result)) {
571
            return [];
572
        }
573
574
        $query = (new Query)->from($this->itemTable)->where([
575
            'type' => Item::TYPE_PERMISSION,
576
            'name' => array_keys($result),
577
        ]);
578
        $permissions = [];
579
        foreach ($query->all($this->db) as $row) {
580
            $permissions[$row['name']] = $this->populateItem($row);
581
        }
582
        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
    protected function getChildrenList()
591
    {
592
        $query = (new Query)->from($this->itemChildTable);
593
        $parents = [];
594
        foreach ($query->all($this->db) as $row) {
595
            $parents[$row['parent']][] = $row['child'];
596
        }
597
        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
    protected function getChildrenRecursive($name, $childrenList, &$result)
607
    {
608
        if (isset($childrenList[$name])) {
609
            foreach ($childrenList[$name] as $child) {
610
                $result[$child] = true;
611
                $this->getChildrenRecursive($child, $childrenList, $result);
612
            }
613
        }
614
    }
615
616
    /**
617
     * @inheritdoc
618
     */
619 38
    public function getRule($name)
620
    {
621 38
        if ($this->rules !== null) {
622
            return isset($this->rules[$name]) ? $this->rules[$name] : null;
623
        }
624
625 38
        $row = (new Query)->select(['data'])
626 38
            ->from($this->ruleTable)
627 38
            ->where(['name' => $name])
628 38
            ->one($this->db);
629 38
        return $row === false ? null : unserialize($row['data']);
630
    }
631
632
    /**
633
     * @inheritdoc
634
     */
635
    public function getRules()
636
    {
637
        if ($this->rules !== null) {
638
            return $this->rules;
639
        }
640
641
        $query = (new Query)->from($this->ruleTable);
642
643
        $rules = [];
644
        foreach ($query->all($this->db) as $row) {
645
            $rules[$row['name']] = unserialize($row['data']);
646
        }
647
648
        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 2
    public function getAssignments($userId)
679
    {
680 2
        if (empty($userId)) {
681
            return [];
682
        }
683
684 2
        $query = (new Query)
685 2
            ->from($this->assignmentTable)
686 2
            ->where(['user_id' => (string) $userId]);
687
688 2
        $assignments = [];
689 2
        foreach ($query->all($this->db) as $row) {
690 2
            $assignments[$row['item_name']] = new Assignment([
691 2
                'userId' => $row['user_id'],
692 2
                'roleName' => $row['item_name'],
693 2
                'createdAt' => $row['created_at'],
694 2
            ]);
695 2
        }
696
697 2
        return $assignments;
698
    }
699
700
    /**
701
     * @inheritdoc
702
     * @since 2.0.8
703
     */
704
    public function canAddChild($parent, $child)
705
    {
706
        return !$this->detectLoop($parent, $child);
707
    }
708
709
    /**
710
     * @inheritdoc
711
     */
712 2
    public function addChild($parent, $child)
713
    {
714 2
        if ($parent->name === $child->name) {
715
            throw new InvalidParamException("Cannot add '{$parent->name}' as a child of itself.");
716
        }
717
718 2
        if ($parent instanceof Permission && $child instanceof Role) {
719
            throw new InvalidParamException('Cannot add a role as a child of a permission.');
720
        }
721
722 2
        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 2
        $this->db->createCommand()
727 2
            ->insert($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
728 2
            ->execute();
729
730 2
        $this->invalidateCache();
731
732 2
        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 2
    public function getChildren($name)
778
    {
779 2
        $query = (new Query)
780 2
            ->select(['name', 'type', 'description', 'rule_name', 'data', 'created_at', 'updated_at'])
781 2
            ->from([$this->itemTable, $this->itemChildTable])
782 2
            ->where(['parent' => $name, 'name' => new Expression('[[child]]')]);
783
784 2
        $children = [];
785 2
        foreach ($query->all($this->db) as $row) {
786 2
            $children[$row['name']] = $this->populateItem($row);
787 2
        }
788
789 2
        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 2
    protected function detectLoop($parent, $child)
799
    {
800 2
        if ($child->name === $parent->name) {
801
            return true;
802
        }
803 2
        foreach ($this->getChildren($child->name) as $grandchild) {
804
            if ($this->detectLoop($parent, $grandchild)) {
805
                return true;
806
            }
807 2
        }
808 2
        return false;
809
    }
810
811
    /**
812
     * @inheritdoc
813
     */
814 2
    public function assign($role, $userId)
815
    {
816 2
        $assignment = new Assignment([
817 2
            'userId' => $userId,
818 2
            'roleName' => $role->name,
819 2
            'createdAt' => time(),
820 2
        ]);
821
822 2
        $this->db->createCommand()
823 2
            ->insert($this->assignmentTable, [
824 2
                'user_id' => $assignment->userId,
825 2
                'item_name' => $assignment->roleName,
826 2
                'created_at' => $assignment->createdAt,
827 2
            ])->execute();
828
829 2
        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 46
    public function removeAll()
864
    {
865 46
        $this->removeAllAssignments();
866 46
        $this->db->createCommand()->delete($this->itemChildTable)->execute();
867 46
        $this->db->createCommand()->delete($this->itemTable)->execute();
868 46
        $this->db->createCommand()->delete($this->ruleTable)->execute();
869 46
        $this->invalidateCache();
870 46
    }
871
872
    /**
873
     * @inheritdoc
874
     */
875
    public function removeAllPermissions()
876
    {
877
        $this->removeAllItems(Item::TYPE_PERMISSION);
878
    }
879
880
    /**
881
     * @inheritdoc
882
     */
883
    public function removeAllRoles()
884
    {
885
        $this->removeAllItems(Item::TYPE_ROLE);
886
    }
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
    protected function removeAllItems($type)
893
    {
894
        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
        $this->db->createCommand()
912
            ->delete($this->itemTable, ['type' => $type])
913
            ->execute();
914
915
        $this->invalidateCache();
916
    }
917
918
    /**
919
     * @inheritdoc
920
     */
921
    public function removeAllRules()
922
    {
923
        if (!$this->supportsCascadeUpdate()) {
924
            $this->db->createCommand()
925
                ->update($this->itemTable, ['rule_name' => null])
926
                ->execute();
927
        }
928
929
        $this->db->createCommand()->delete($this->ruleTable)->execute();
930
931
        $this->invalidateCache();
932
    }
933
934
    /**
935
     * @inheritdoc
936
     */
937 46
    public function removeAllAssignments()
938
    {
939 46
        $this->db->createCommand()->delete($this->assignmentTable)->execute();
940 46
    }
941
942 46
    public function invalidateCache()
943
    {
944 46
        if ($this->cache !== null) {
945
            $this->cache->delete($this->cacheKey);
946
            $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
            $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
            $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
        }
950 46
    }
951
952 2
    public function loadFromCache()
953
    {
954 2
        if ($this->items !== null || !$this->cache instanceof Cache) {
955 2
            return;
956
        }
957
958
        $data = $this->cache->get($this->cacheKey);
959
        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
        $query = (new Query)->from($this->itemTable);
965
        $this->items = [];
966
        foreach ($query->all($this->db) as $row) {
967
            $this->items[$row['name']] = $this->populateItem($row);
968
        }
969
970
        $query = (new Query)->from($this->ruleTable);
971
        $this->rules = [];
972
        foreach ($query->all($this->db) as $row) {
973
            $this->rules[$row['name']] = unserialize($row['data']);
974
        }
975
976
        $query = (new Query)->from($this->itemChildTable);
977
        $this->parents = [];
978
        foreach ($query->all($this->db) as $row) {
979
            if (isset($this->items[$row['child']])) {
980
                $this->parents[$row['child']][] = $row['parent'];
981
            }
982
        }
983
984
        $this->cache->set($this->cacheKey, [$this->items, $this->rules, $this->parents]);
985
    }
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
    public function getUserIdsByRole($roleName)
995
    {
996
        if (empty($roleName)) {
997
            return [];
998
        }
999
1000
        return (new Query)->select('[[user_id]]')
1001
            ->from($this->assignmentTable)
1002
            ->where(['item_name' => $roleName])->column($this->db);
1003
    }
1004
}
1005