Completed
Push — prepare-travis-for-js ( a7ee60...8c0a43 )
by Carsten
17:00 queued 09:50
created

DbManager::getRule()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.2
c 0
b 0
f 0
ccs 8
cts 8
cp 1
cc 4
eloc 8
nc 4
nop 1
crap 4
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\rbac;
9
10
use Yii;
11
use yii\caching\Cache;
12
use yii\db\Connection;
13
use yii\db\Query;
14
use yii\db\Expression;
15
use yii\base\InvalidCallException;
16
use yii\base\InvalidParamException;
17
use yii\di\Instance;
18
19
/**
20
 * DbManager represents an authorization manager that stores authorization information in database.
21
 *
22
 * The database connection is specified by [[db]]. The database schema could be initialized by applying migration:
23
 *
24
 * ```
25
 * yii migrate --migrationPath=@yii/rbac/migrations/
26
 * ```
27
 *
28
 * If you don't want to use migration and need SQL instead, files for all databases are in migrations directory.
29
 *
30
 * You may change the names of the tables used to store the authorization and rule data by setting [[itemTable]],
31
 * [[itemChildTable]], [[assignmentTable]] and [[ruleTable]].
32
 *
33
 * For more details and usage information on DbManager, see the [guide article on security authorization](guide:security-authorization).
34
 *
35
 * @author Qiang Xue <[email protected]>
36
 * @author Alexander Kochetov <[email protected]>
37
 * @since 2.0
38
 */
39
class DbManager extends BaseManager
40
{
41
    /**
42
     * @var Connection|array|string the DB connection object or the application component ID of the DB connection.
43
     * After the DbManager object is created, if you want to change this property, you should only assign it
44
     * with a DB connection object.
45
     * Starting from version 2.0.2, this can also be a configuration array for creating the object.
46
     */
47
    public $db = 'db';
48
    /**
49
     * @var string the name of the table storing authorization items. Defaults to "auth_item".
50
     */
51
    public $itemTable = '{{%auth_item}}';
52
    /**
53
     * @var string the name of the table storing authorization item hierarchy. Defaults to "auth_item_child".
54
     */
55
    public $itemChildTable = '{{%auth_item_child}}';
56
    /**
57
     * @var string the name of the table storing authorization item assignments. Defaults to "auth_assignment".
58
     */
59
    public $assignmentTable = '{{%auth_assignment}}';
60
    /**
61
     * @var string the name of the table storing rules. Defaults to "auth_rule".
62
     */
63
    public $ruleTable = '{{%auth_rule}}';
64
    /**
65
     * @var Cache|array|string the cache used to improve RBAC performance. This can be one of the following:
66
     *
67
     * - an application component ID (e.g. `cache`)
68
     * - a configuration array
69
     * - a [[\yii\caching\Cache]] object
70
     *
71
     * When this is not set, it means caching is not enabled.
72
     *
73
     * Note that by enabling RBAC cache, all auth items, rules and auth item parent-child relationships will
74
     * be cached and loaded into memory. This will improve the performance of RBAC permission check. However,
75
     * it does require extra memory and as a result may not be appropriate if your RBAC system contains too many
76
     * auth items. You should seek other RBAC implementations (e.g. RBAC based on Redis storage) in this case.
77
     *
78
     * Also note that if you modify RBAC items, rules or parent-child relationships from outside of this component,
79
     * you have to manually call [[invalidateCache()]] to ensure data consistency.
80
     *
81
     * @since 2.0.3
82
     */
83
    public $cache;
84
    /**
85
     * @var string the key used to store RBAC data in cache
86
     * @see cache
87
     * @since 2.0.3
88
     */
89
    public $cacheKey = 'rbac';
90
91
    /**
92
     * @var Item[] all auth items (name => Item)
93
     */
94
    protected $items;
95
    /**
96
     * @var Rule[] all auth rules (name => Rule)
97
     */
98
    protected $rules;
99
    /**
100
     * @var array auth item parent-child relationships (childName => list of parents)
101
     */
102
    protected $parents;
103
104
105
    /**
106
     * Initializes the application component.
107
     * This method overrides the parent implementation by establishing the database connection.
108
     */
109 92
    public function init()
110
    {
111 92
        parent::init();
112 92
        $this->db = Instance::ensure($this->db, Connection::className());
113 92
        if ($this->cache !== null) {
114 23
            $this->cache = Instance::ensure($this->cache, Cache::className());
115 23
        }
116 92
    }
117
118
    /**
119
     * @inheritdoc
120
     */
121 8
    public function checkAccess($userId, $permissionName, $params = [])
122
    {
123 8
        $assignments = $this->getAssignments($userId);
124 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 80
    protected function addRule($rule)
340
    {
341 80
        $time = time();
342 80
        if ($rule->createdAt === null) {
343 80
            $rule->createdAt = $time;
344 80
        }
345 80
        if ($rule->updatedAt === null) {
346 80
            $rule->updatedAt = $time;
347 80
        }
348 80
        $this->db->createCommand()
349 80
            ->insert($this->ruleTable, [
350 80
                'name' => $rule->name,
351 80
                'data' => serialize($rule),
352 80
                'created_at' => $rule->createdAt,
353 80
                'updated_at' => $rule->updatedAt,
354 80
            ])->execute();
355
356 80
        $this->invalidateCache();
357
358 80
        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 76
    public function getRule($name)
620
    {
621 76
        if ($this->rules !== null) {
622 2
            return isset($this->rules[$name]) ? $this->rules[$name] : null;
623
        }
624
625 76
        $row = (new Query)->select(['data'])
626 76
            ->from($this->ruleTable)
627 76
            ->where(['name' => $name])
628 76
            ->one($this->db);
629 76
        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 92
    public function removeAll()
864
    {
865 92
        $this->removeAllAssignments();
866 92
        $this->db->createCommand()->delete($this->itemChildTable)->execute();
867 92
        $this->db->createCommand()->delete($this->itemTable)->execute();
868 92
        $this->db->createCommand()->delete($this->ruleTable)->execute();
869 92
        $this->invalidateCache();
870 92
    }
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 92
    public function removeAllAssignments()
938
    {
939 92
        $this->db->createCommand()->delete($this->assignmentTable)->execute();
940 92
    }
941
942 92
    public function invalidateCache()
943
    {
944 92
        if ($this->cache !== null) {
945 23
            $this->cache->delete($this->cacheKey);
946 23
            $this->items = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array<integer,object<yii\rbac\Item>> of property $items.

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

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

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

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

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

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

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

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

Loading history...
949 23
        }
950 92
    }
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