Completed
Push — 2.1 ( c952e8...98ed49 )
by Carsten
10:00
created

DbManager   F

Complexity

Total Complexity 128

Size/Duplication

Total Lines 947
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 79.62%

Importance

Changes 4
Bugs 1 Features 2
Metric Value
wmc 128
c 4
b 1
f 2
lcom 1
cbo 12
dl 0
loc 947
ccs 379
cts 476
cp 0.7962
rs 3.4285

44 Methods

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