Completed
Push — readme-redesign ( e2fd40...17eb05 )
by Alexander
108:51 queued 68:52
created

DbManager   F

Complexity

Total Complexity 131

Size/Duplication

Total Lines 983
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 0
Metric Value
wmc 131
lcom 1
cbo 14
dl 0
loc 983
rs 1.5999
c 0
b 0
f 0

45 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 8 2
A checkAccess() 0 15 3
D checkAccessFromCache() 0 28 9
C checkAccessRecursive() 0 29 8
A getItem() 0 20 4
A supportsCascadeUpdate() 0 4 1
B addItem() 0 24 4
A removeItem() 0 19 2
B updateItem() 0 31 4
A addRule() 0 21 3
A updateRule() 0 23 3
A removeRule() 0 16 2
A getItems() 0 13 2
A populateItem() 0 18 4
A getRolesByUser() 0 18 4
A getChildRoles() 0 19 2
A getPermissionsByRole() 0 18 3
A getPermissionsByUser() 0 11 2
A getDirectPermissionsByUser() 0 14 2
B getInheritedPermissionsByUser() 0 26 4
A getChildrenList() 0 9 2
A getChildrenRecursive() 0 9 3
B getRule() 0 20 5
A getRules() 0 19 4
A getAssignment() 0 20 3
A getAssignments() 0 21 3
A canAddChild() 0 4 1
B addChild() 0 22 5
A removeChild() 0 10 1
A removeChildren() 0 10 1
A hasChild() 0 7 1
A getChildren() 0 14 2
A detectLoop() 0 12 4
A assign() 0 17 1
A revoke() 0 10 2
A revokeAll() 0 10 2
A removeAll() 0 8 1
A removeAllPermissions() 0 4 1
A removeAllRoles() 0 4 1
B removeAllItems() 0 25 4
A removeAllRules() 0 12 2
A removeAllAssignments() 0 4 1
A invalidateCache() 0 9 2
D loadFromCache() 0 34 9
A getUserIdsByRole() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like DbManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DbManager, and based on these observations, apply Extract Interface, too.

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