Completed
Push — rbac-get-user-ids-by-role ( 0e0079 )
by Alexander
07:35
created

DbManager::checkAccess()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2
Metric Value
dl 0
loc 10
rs 9.4286
ccs 6
cts 6
cp 1
cc 2
eloc 7
nc 2
nop 3
crap 2
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 64
    public function init()
108
    {
109 64
        parent::init();
110 64
        $this->db = Instance::ensure($this->db, Connection::className());
111 64
        if ($this->cache !== null) {
112 16
            $this->cache = Instance::ensure($this->cache, Cache::className());
113 16
        }
114 64
    }
115
116
    /**
117
     * @inheritdoc
118
     */
119 4
    public function checkAccess($userId, $permissionName, $params = [])
120
    {
121 4
        $assignments = $this->getAssignments($userId);
122 4
        $this->loadFromCache();
123 4
        if ($this->items !== null) {
124 1
            return $this->checkAccessFromCache($userId, $permissionName, $params, $assignments);
125
        } else {
126 3
            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 1
    protected function checkAccessFromCache($user, $itemName, $params, $assignments)
144
    {
145 1
        if (!isset($this->items[$itemName])) {
146
            return false;
147
        }
148
149 1
        $item = $this->items[$itemName];
150
151 1
        Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
152
153 1
        if (!$this->executeRule($user, $item, $params)) {
154 1
            return false;
155
        }
156
157 1
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
158 1
            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 3
    protected function checkAccessRecursive($user, $itemName, $params, $assignments)
185
    {
186 3
        if (($item = $this->getItem($itemName)) === null) {
187
            return false;
188
        }
189
190 3
        Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
191
192 3
        if (!$this->executeRule($user, $item, $params)) {
193 3
            return false;
194
        }
195
196 3
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
197 3
            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 15
    protected function getItem($name)
218
    {
219 15
        if (empty($name)) {
220
            return null;
221
        }
222
223 15
        if (!empty($this->items[$name])) {
224
            return $this->items[$name];
225
        }
226
227 15
        $row = (new Query)->from($this->itemTable)
228 15
            ->where(['name' => $name])
229 15
            ->one($this->db);
230
231 15
        if ($row === false) {
232
            return null;
233
        }
234
235 15
        if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
236 15
            $row['data'] = null;
237 15
        }
238
239 15
        return $this->populateItem($row);
0 ignored issues
show
Bug introduced by
It seems like $row defined by (new \yii\db\Query())->f...$name))->one($this->db) on line 227 can also be of type boolean; however, yii\rbac\DbManager::populateItem() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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 8
    protected function supportsCascadeUpdate()
248
    {
249 8
        return strncmp($this->db->getDriverName(), 'sqlite', 6) !== 0;
250
    }
251
252
    /**
253
     * @inheritdoc
254
     */
255 56
    protected function addItem($item)
256
    {
257 56
        $time = time();
258 56
        if ($item->createdAt === null) {
259 56
            $item->createdAt = $time;
260 56
        }
261 56
        if ($item->updatedAt === null) {
262 56
            $item->updatedAt = $time;
263 56
        }
264 56
        $this->db->createCommand()
265 56
            ->insert($this->itemTable, [
266 56
                'name' => $item->name,
267 56
                'type' => $item->type,
268 56
                'description' => $item->description,
269 56
                'rule_name' => $item->ruleName,
270 56
                'data' => $item->data === null ? null : serialize($item->data),
271 56
                'created_at' => $item->createdAt,
272 56
                'updated_at' => $item->updatedAt,
273 56
            ])->execute();
274
275 56
        $this->invalidateCache();
276
277 56
        return true;
278
    }
279
280
    /**
281
     * @inheritdoc
282
     */
283
    protected function removeItem($item)
284
    {
285
        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
        $this->db->createCommand()
295
            ->delete($this->itemTable, ['name' => $item->name])
296
            ->execute();
297
298
        $this->invalidateCache();
299
300
        return true;
301
    }
302
303
    /**
304
     * @inheritdoc
305
     */
306
    protected function updateItem($name, $item)
307
    {
308
        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
        $item->updatedAt = time();
321
322
        $this->db->createCommand()
323
            ->update($this->itemTable, [
324
                'name' => $item->name,
325
                'description' => $item->description,
326
                'rule_name' => $item->ruleName,
327
                'data' => $item->data === null ? null : serialize($item->data),
328
                'updated_at' => $item->updatedAt,
329
            ], [
330
                'name' => $name,
331
            ])->execute();
332
333
        $this->invalidateCache();
334
335
        return true;
336
    }
337
338
    /**
339
     * @inheritdoc
340
     */
341 52
    protected function addRule($rule)
342
    {
343 52
        $time = time();
344 52
        if ($rule->createdAt === null) {
345 52
            $rule->createdAt = $time;
346 52
        }
347 52
        if ($rule->updatedAt === null) {
348 52
            $rule->updatedAt = $time;
349 52
        }
350 52
        $this->db->createCommand()
351 52
            ->insert($this->ruleTable, [
352 52
                'name' => $rule->name,
353 52
                'data' => serialize($rule),
354 52
                'created_at' => $rule->createdAt,
355 52
                'updated_at' => $rule->updatedAt,
356 52
            ])->execute();
357
358 52
        $this->invalidateCache();
359
360 52
        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
    protected function getItems($type)
414
    {
415
        $query = (new Query)
416
            ->from($this->itemTable)
417
            ->where(['type' => $type]);
418
419
        $items = [];
420
        foreach ($query->all($this->db) as $row) {
421
            $items[$row['name']] = $this->populateItem($row);
422
        }
423
424
        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 52
    protected function populateItem($row)
433
    {
434 52
        $class = $row['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className();
435
436 52
        if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
437 52
            $data = null;
438 52
        }
439
440 52
        return new $class([
441 52
            'name' => $row['name'],
442 52
            'type' => $row['type'],
443 52
            'description' => $row['description'],
444 52
            'ruleName' => $row['rule_name'],
445 52
            'data' => $data,
446 52
            'createdAt' => $row['created_at'],
447 52
            'updatedAt' => $row['updated_at'],
448 52
        ]);
449
    }
450
451
    /**
452
     * @inheritdoc
453
     */
454 8
    public function getRolesByUser($userId)
455
    {
456 8
        if (empty($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 getPermissionsByRole($roleName)
477
    {
478 4
        $childrenList = $this->getChildrenList();
479 4
        $result = [];
480 4
        $this->getChildrenRecursive($roleName, $childrenList, $result);
481 4
        if (empty($result)) {
482
            return [];
483
        }
484 4
        $query = (new Query)->from($this->itemTable)->where([
485 4
            'type' => Item::TYPE_PERMISSION,
486 4
            'name' => array_keys($result),
487 4
        ]);
488 4
        $permissions = [];
489 4
        foreach ($query->all($this->db) as $row) {
490 4
            $permissions[$row['name']] = $this->populateItem($row);
491 4
        }
492 4
        return $permissions;
493
    }
494
495
    /**
496
     * @inheritdoc
497
     */
498 4
    public function getPermissionsByUser($userId)
499
    {
500 4
        if (empty($userId)) {
501
            return [];
502
        }
503
504 4
        $directPermission = $this->getDirectPermissionsByUser($userId);
505 4
        $inheritedPermission = $this->getInheritedPermissionsByUser($userId);
506
507 4
        return array_merge($directPermission, $inheritedPermission);
508
    }
509
510
    /**
511
     * Returns all permissions that are directly assigned to user.
512
     * @param string|integer $userId the user ID (see [[\yii\web\User::id]])
513
     * @return Permission[] all direct permissions that the user has. The array is indexed by the permission names.
514
     *
515
     * @since 2.0.7
516
     */
517 4
    protected function getDirectPermissionsByUser($userId)
518
    {
519 4
        $query = (new Query)->select('b.*')
520 4
            ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
521 4
            ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
522 4
            ->andWhere(['a.user_id' => (string) $userId])
523 4
            ->andWhere(['b.type' => Item::TYPE_PERMISSION]);
524
525 4
        $permissions = [];
526 4
        foreach ($query->all($this->db) as $row) {
527 4
            $permissions[$row['name']] = $this->populateItem($row);
528 4
        }
529 4
        return $permissions;
530
    }
531
532
    /**
533
     * Returns all permissions that the user inherits from the roles assigned to him.
534
     * @param string|integer $userId the user ID (see [[\yii\web\User::id]])
535
     * @return Permission[] all inherited permissions that the user has. The array is indexed by the permission names.
536
     *
537
     * @since 2.0.7
538
     */
539 4
    protected function getInheritedPermissionsByUser($userId)
540
    {
541 4
        $query = (new Query)->select('item_name')
542 4
            ->from($this->assignmentTable)
543 4
            ->where(['user_id' => (string) $userId]);
544
545 4
        $childrenList = $this->getChildrenList();
546 4
        $result = [];
547 4
        foreach ($query->column($this->db) as $roleName) {
548 4
            $this->getChildrenRecursive($roleName, $childrenList, $result);
549 4
        }
550
551 4
        if (empty($result)) {
552
            return [];
553
        }
554
555 4
        $query = (new Query)->from($this->itemTable)->where([
556 4
            'type' => Item::TYPE_PERMISSION,
557 4
            'name' => array_keys($result),
558 4
        ]);
559 4
        $permissions = [];
560 4
        foreach ($query->all($this->db) as $row) {
561 4
            $permissions[$row['name']] = $this->populateItem($row);
562 4
        }
563 4
        return $permissions;
564
    }
565
566
    /**
567
     * Returns the children for every parent.
568
     * @return array the children list. Each array key is a parent item name,
569
     * and the corresponding array value is a list of child item names.
570
     */
571 8
    protected function getChildrenList()
572
    {
573 8
        $query = (new Query)->from($this->itemChildTable);
574 8
        $parents = [];
575 8
        foreach ($query->all($this->db) as $row) {
576 8
            $parents[$row['parent']][] = $row['child'];
577 8
        }
578 8
        return $parents;
579
    }
580
581
    /**
582
     * Recursively finds all children and grand children of the specified item.
583
     * @param string $name the name of the item whose children are to be looked for.
584
     * @param array $childrenList the child list built via [[getChildrenList()]]
585
     * @param array $result the children and grand children (in array keys)
586
     */
587 8
    protected function getChildrenRecursive($name, $childrenList, &$result)
588
    {
589 8
        if (isset($childrenList[$name])) {
590 8
            foreach ($childrenList[$name] as $child) {
591 8
                $result[$child] = true;
592 8
                $this->getChildrenRecursive($child, $childrenList, $result);
593 8
            }
594 8
        }
595 8
    }
596
597
    /**
598
     * @inheritdoc
599
     */
600 20
    public function getRule($name)
601
    {
602 20
        if ($this->rules !== null) {
603 1
            return isset($this->rules[$name]) ? $this->rules[$name] : null;
604
        }
605
606 19
        $row = (new Query)->select(['data'])
607 19
            ->from($this->ruleTable)
608 19
            ->where(['name' => $name])
609 19
            ->one($this->db);
610 19
        return $row === false ? null : unserialize($row['data']);
611
    }
612
613
    /**
614
     * @inheritdoc
615
     */
616 8
    public function getRules()
617
    {
618 8
        if ($this->rules !== null) {
619
            return $this->rules;
620
        }
621
622 8
        $query = (new Query)->from($this->ruleTable);
623
624 8
        $rules = [];
625 8
        foreach ($query->all($this->db) as $row) {
626 4
            $rules[$row['name']] = unserialize($row['data']);
627 8
        }
628
629 8
        return $rules;
630
    }
631
632
    /**
633
     * @inheritdoc
634
     */
635
    public function getAssignment($roleName, $userId)
636
    {
637
        if (empty($userId)) {
638
            return null;
639
        }
640
641
        $row = (new Query)->from($this->assignmentTable)
642
            ->where(['user_id' => (string) $userId, 'item_name' => $roleName])
643
            ->one($this->db);
644
645
        if ($row === false) {
646
            return null;
647
        }
648
649
        return new Assignment([
650
            'userId' => $row['user_id'],
651
            'roleName' => $row['item_name'],
652
            'createdAt' => $row['created_at'],
653
        ]);
654
    }
655
656
    /**
657
     * @inheritdoc
658
     */
659 8
    public function getAssignments($userId)
660
    {
661 8
        if (empty($userId)) {
662 4
            return [];
663
        }
664
665 8
        $query = (new Query)
666 8
            ->from($this->assignmentTable)
667 8
            ->where(['user_id' => (string) $userId]);
668
669 8
        $assignments = [];
670 8
        foreach ($query->all($this->db) as $row) {
671 8
            $assignments[$row['item_name']] = new Assignment([
672 8
                'userId' => $row['user_id'],
673 8
                'roleName' => $row['item_name'],
674 8
                'createdAt' => $row['created_at'],
675 8
            ]);
676 8
        }
677
678 8
        return $assignments;
679
    }
680
681
    /**
682
     * @inheritdoc
683
     */
684 52
    public function addChild($parent, $child)
685
    {
686 52
        if ($parent->name === $child->name) {
687
            throw new InvalidParamException("Cannot add '{$parent->name}' as a child of itself.");
688
        }
689
690 52
        if ($parent instanceof Permission && $child instanceof Role) {
691
            throw new InvalidParamException('Cannot add a role as a child of a permission.');
692
        }
693
694 52
        if ($this->detectLoop($parent, $child)) {
695
            throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected.");
696
        }
697
698 52
        $this->db->createCommand()
699 52
            ->insert($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
700 52
            ->execute();
701
702 52
        $this->invalidateCache();
703
704 52
        return true;
705
    }
706
707
    /**
708
     * @inheritdoc
709
     */
710
    public function removeChild($parent, $child)
711
    {
712
        $result = $this->db->createCommand()
713
            ->delete($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
714
            ->execute() > 0;
715
716
        $this->invalidateCache();
717
718
        return $result;
719
    }
720
721
    /**
722
     * @inheritdoc
723
     */
724
    public function removeChildren($parent)
725
    {
726
        $result = $this->db->createCommand()
727
            ->delete($this->itemChildTable, ['parent' => $parent->name])
728
            ->execute() > 0;
729
730
        $this->invalidateCache();
731
732
        return $result;
733
    }
734
735
    /**
736
     * @inheritdoc
737
     */
738
    public function hasChild($parent, $child)
739
    {
740
        return (new Query)
741
            ->from($this->itemChildTable)
742
            ->where(['parent' => $parent->name, 'child' => $child->name])
743
            ->one($this->db) !== false;
744
    }
745
746
    /**
747
     * @inheritdoc
748
     */
749 52
    public function getChildren($name)
750
    {
751 52
        $query = (new Query)
752 52
            ->select(['name', 'type', 'description', 'rule_name', 'data', 'created_at', 'updated_at'])
753 52
            ->from([$this->itemTable, $this->itemChildTable])
754 52
            ->where(['parent' => $name, 'name' => new Expression('[[child]]')]);
755
756 52
        $children = [];
757 52
        foreach ($query->all($this->db) as $row) {
758 52
            $children[$row['name']] = $this->populateItem($row);
759 52
        }
760
761 52
        return $children;
762
    }
763
764
    /**
765
     * Checks whether there is a loop in the authorization item hierarchy.
766
     * @param Item $parent the parent item
767
     * @param Item $child the child item to be added to the hierarchy
768
     * @return boolean whether a loop exists
769
     */
770 52
    protected function detectLoop($parent, $child)
771
    {
772 52
        if ($child->name === $parent->name) {
773
            return true;
774
        }
775 52
        foreach ($this->getChildren($child->name) as $grandchild) {
776 48
            if ($this->detectLoop($parent, $grandchild)) {
777
                return true;
778
            }
779 52
        }
780 52
        return false;
781
    }
782
783
    /**
784
     * @inheritdoc
785
     */
786 48
    public function assign($role, $userId)
787
    {
788 48
        $assignment = new Assignment([
789 48
            'userId' => $userId,
790 48
            'roleName' => $role->name,
791 48
            'createdAt' => time(),
792 48
        ]);
793
794 48
        $this->db->createCommand()
795 48
            ->insert($this->assignmentTable, [
796 48
                'user_id' => $assignment->userId,
797 48
                'item_name' => $assignment->roleName,
798 48
                'created_at' => $assignment->createdAt,
799 48
            ])->execute();
800
801 48
        return $assignment;
802
    }
803
804
    /**
805
     * @inheritdoc
806
     */
807
    public function revoke($role, $userId)
808
    {
809
        if (empty($userId)) {
810
            return false;
811
        }
812
813
        return $this->db->createCommand()
814
            ->delete($this->assignmentTable, ['user_id' => (string) $userId, 'item_name' => $role->name])
815
            ->execute() > 0;
816
    }
817
818
    /**
819
     * @inheritdoc
820
     */
821
    public function revokeAll($userId)
822
    {
823
        if (empty($userId)) {
824
            return false;
825
        }
826
827
        return $this->db->createCommand()
828
            ->delete($this->assignmentTable, ['user_id' => (string) $userId])
829
            ->execute() > 0;
830
    }
831
832
    /**
833
     * @inheritdoc
834
     */
835 64
    public function removeAll()
836
    {
837 64
        $this->removeAllAssignments();
838 64
        $this->db->createCommand()->delete($this->itemChildTable)->execute();
839 64
        $this->db->createCommand()->delete($this->itemTable)->execute();
840 64
        $this->db->createCommand()->delete($this->ruleTable)->execute();
841 64
        $this->invalidateCache();
842 64
    }
843
844
    /**
845
     * @inheritdoc
846
     */
847
    public function removeAllPermissions()
848
    {
849
        $this->removeAllItems(Item::TYPE_PERMISSION);
850
    }
851
852
    /**
853
     * @inheritdoc
854
     */
855
    public function removeAllRoles()
856
    {
857
        $this->removeAllItems(Item::TYPE_ROLE);
858
    }
859
860
    /**
861
     * Removes all auth items of the specified type.
862
     * @param integer $type the auth item type (either Item::TYPE_PERMISSION or Item::TYPE_ROLE)
863
     */
864
    protected function removeAllItems($type)
865
    {
866
        if (!$this->supportsCascadeUpdate()) {
867
            $names = (new Query)
868
                ->select(['name'])
869
                ->from($this->itemTable)
870
                ->where(['type' => $type])
871
                ->column($this->db);
872
            if (empty($names)) {
873
                return;
874
            }
875
            $key = $type == Item::TYPE_PERMISSION ? 'child' : 'parent';
876
            $this->db->createCommand()
877
                ->delete($this->itemChildTable, [$key => $names])
878
                ->execute();
879
            $this->db->createCommand()
880
                ->delete($this->assignmentTable, ['item_name' => $names])
881
                ->execute();
882
        }
883
        $this->db->createCommand()
884
            ->delete($this->itemTable, ['type' => $type])
885
            ->execute();
886
887
        $this->invalidateCache();
888
    }
889
890
    /**
891
     * @inheritdoc
892
     */
893
    public function removeAllRules()
894
    {
895
        if (!$this->supportsCascadeUpdate()) {
896
            $this->db->createCommand()
897
                ->update($this->itemTable, ['ruleName' => null])
898
                ->execute();
899
        }
900
901
        $this->db->createCommand()->delete($this->ruleTable)->execute();
902
903
        $this->invalidateCache();
904
    }
905
906
    /**
907
     * @inheritdoc
908
     */
909 64
    public function removeAllAssignments()
910
    {
911 64
        $this->db->createCommand()->delete($this->assignmentTable)->execute();
912 64
    }
913
914 64
    public function invalidateCache()
915
    {
916 64
        if ($this->cache !== null) {
917 16
            $this->cache->delete($this->cacheKey);
918 16
            $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...
919 16
            $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...
920 16
            $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...
921 16
        }
922 64
    }
923
924 4
    public function loadFromCache()
925
    {
926 4
        if ($this->items !== null || !$this->cache instanceof Cache) {
927 4
            return;
928
        }
929
930 1
        $data = $this->cache->get($this->cacheKey);
931 1
        if (is_array($data) && isset($data[0], $data[1], $data[2])) {
932
            list ($this->items, $this->rules, $this->parents) = $data;
933
            return;
934
        }
935
936 1
        $query = (new Query)->from($this->itemTable);
937 1
        $this->items = [];
938 1
        foreach ($query->all($this->db) as $row) {
939 1
            $this->items[$row['name']] = $this->populateItem($row);
940 1
        }
941
942 1
        $query = (new Query)->from($this->ruleTable);
943 1
        $this->rules = [];
944 1
        foreach ($query->all($this->db) as $row) {
945 1
            $this->rules[$row['name']] = unserialize($row['data']);
946 1
        }
947
948 1
        $query = (new Query)->from($this->itemChildTable);
949 1
        $this->parents = [];
950 1
        foreach ($query->all($this->db) as $row) {
951 1
            if (isset($this->items[$row['child']])) {
952 1
                $this->parents[$row['child']][] = $row['parent'];
953 1
            }
954 1
        }
955
956 1
        $this->cache->set($this->cacheKey, [$this->items, $this->rules, $this->parents]);
957 1
    }
958
959
    /**
960
     * Returns all role assignment information for the specified role.
961
     * @param string $roleName
962
     * @return Assignment[] the assignments. An empty array will be
963
     * returned if role is not assigned to any user.
964
     */
965 4
    public function getUserIDsByRole($roleName)
966
    {
967 4
        if (empty($roleName)) {
968
            return [];
969
        }
970
971 4
        return (new Query)->select('[[user_id]]')
972 4
            ->from($this->assignmentTable)
973 4
            ->where(['item_name' => $roleName])->column($this->db);
974
    }
975
}
976