GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 97c43c...6b8cf1 )
by Robert
18:45
created

DbManager   F

Complexity

Total Complexity 131

Size/Duplication

Total Lines 983
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 79.23%

Importance

Changes 0
Metric Value
wmc 131
lcom 1
cbo 14
dl 0
loc 983
rs 1.5999
c 0
b 0
f 0
ccs 393
cts 496
cp 0.7923

45 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 8 2
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
A checkAccess() 0 15 3

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 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
125 8
        if ($this->hasNoAssignments($assignments)) {
126
            return false;
127
        }
128
129 8
        $this->loadFromCache();
130 8
        if ($this->items !== null) {
131 2
            return $this->checkAccessFromCache($userId, $permissionName, $params, $assignments);
132
        } else {
133 6
            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 2
    protected function checkAccessFromCache($user, $itemName, $params, $assignments)
151
    {
152 2
        if (!isset($this->items[$itemName])) {
153 1
            return false;
154
        }
155
156 2
        $item = $this->items[$itemName];
157
158 2
        Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
159
160 2
        if (!$this->executeRule($user, $item, $params)) {
161 2
            return false;
162
        }
163
164 2
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
165 2
            return true;
166
        }
167
168 1
        if (!empty($this->parents[$itemName])) {
169 1
            foreach ($this->parents[$itemName] as $parent) {
170 1
                if ($this->checkAccessFromCache($user, $parent, $params, $assignments)) {
171 1
                    return true;
172
                }
173 1
            }
174 1
        }
175
176 1
        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 6
    protected function checkAccessRecursive($user, $itemName, $params, $assignments)
192
    {
193 6
        if (($item = $this->getItem($itemName)) === null) {
194 3
            return false;
195
        }
196
197 6
        Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
198
199 6
        if (!$this->executeRule($user, $item, $params)) {
200 6
            return false;
201
        }
202
203 6
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
204 6
            return true;
205
        }
206
207 3
        $query = new Query;
208 3
        $parents = $query->select(['parent'])
209 3
            ->from($this->itemChildTable)
210 3
            ->where(['child' => $itemName])
211 3
            ->column($this->db);
212 3
        foreach ($parents as $parent) {
213 3
            if ($this->checkAccessRecursive($user, $parent, $params, $assignments)) {
214 3
                return true;
215
            }
216 3
        }
217
218 3
        return false;
219
    }
220
221
    /**
222
     * @inheritdoc
223
     */
224 35
    protected function getItem($name)
225
    {
226 35
        if (empty($name)) {
227 3
            return null;
228
        }
229
230 35
        if (!empty($this->items[$name])) {
231 1
            return $this->items[$name];
232
        }
233
234 34
        $row = (new Query)->from($this->itemTable)
235 34
            ->where(['name' => $name])
236 34
            ->one($this->db);
237
238 34
        if ($row === false) {
239 11
            return null;
240
        }
241
242 34
        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 24
    protected function supportsCascadeUpdate()
251
    {
252 24
        return strncmp($this->db->getDriverName(), 'sqlite', 6) !== 0;
253
    }
254
255
    /**
256
     * @inheritdoc
257
     */
258 80
    protected function addItem($item)
259
    {
260 80
        $time = time();
261 80
        if ($item->createdAt === null) {
262 80
            $item->createdAt = $time;
263 80
        }
264 80
        if ($item->updatedAt === null) {
265 80
            $item->updatedAt = $time;
266 80
        }
267 80
        $this->db->createCommand()
268 80
            ->insert($this->itemTable, [
269 80
                'name' => $item->name,
270 80
                'type' => $item->type,
271 80
                'description' => $item->description,
272 80
                'rule_name' => $item->ruleName,
273 80
                'data' => $item->data === null ? null : serialize($item->data),
274 80
                'created_at' => $item->createdAt,
275 80
                'updated_at' => $item->updatedAt,
276 80
            ])->execute();
277
278 80
        $this->invalidateCache();
279
280 80
        return true;
281
    }
282
283
    /**
284
     * @inheritdoc
285
     */
286 4
    protected function removeItem($item)
287
    {
288 4
        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 4
        $this->db->createCommand()
298 4
            ->delete($this->itemTable, ['name' => $item->name])
299 4
            ->execute();
300
301 4
        $this->invalidateCache();
302
303 4
        return true;
304
    }
305
306
    /**
307
     * @inheritdoc
308
     */
309 8
    protected function updateItem($name, $item)
310
    {
311 8
        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 8
        $item->updatedAt = time();
324
325 8
        $this->db->createCommand()
326 8
            ->update($this->itemTable, [
327 8
                'name' => $item->name,
328 8
                'description' => $item->description,
329 8
                'rule_name' => $item->ruleName,
330 8
                'data' => $item->data === null ? null : serialize($item->data),
331 8
                'updated_at' => $item->updatedAt,
332 8
            ], [
333 8
                'name' => $name,
334 8
            ])->execute();
335
336 8
        $this->invalidateCache();
337
338 8
        return true;
339
    }
340
341
    /**
342
     * @inheritdoc
343
     */
344 80
    protected function addRule($rule)
345
    {
346 80
        $time = time();
347 80
        if ($rule->createdAt === null) {
348 80
            $rule->createdAt = $time;
349 80
        }
350 80
        if ($rule->updatedAt === null) {
351 80
            $rule->updatedAt = $time;
352 80
        }
353 80
        $this->db->createCommand()
354 80
            ->insert($this->ruleTable, [
355 80
                'name' => $rule->name,
356 80
                'data' => serialize($rule),
357 80
                'created_at' => $rule->createdAt,
358 80
                'updated_at' => $rule->updatedAt,
359 80
            ])->execute();
360
361 80
        $this->invalidateCache();
362
363 80
        return true;
364
    }
365
366
    /**
367
     * @inheritdoc
368
     */
369 4
    protected function updateRule($name, $rule)
370
    {
371 4
        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 4
        $rule->updatedAt = time();
378
379 4
        $this->db->createCommand()
380 4
            ->update($this->ruleTable, [
381 4
                'name' => $rule->name,
382 4
                'data' => serialize($rule),
383 4
                'updated_at' => $rule->updatedAt,
384 4
            ], [
385 4
                'name' => $name,
386 4
            ])->execute();
387
388 4
        $this->invalidateCache();
389
390 4
        return true;
391
    }
392
393
    /**
394
     * @inheritdoc
395
     */
396 4
    protected function removeRule($rule)
397
    {
398 4
        if (!$this->supportsCascadeUpdate()) {
399
            $this->db->createCommand()
400
                ->update($this->itemTable, ['rule_name' => null], ['rule_name' => $rule->name])
401
                ->execute();
402
        }
403
404 4
        $this->db->createCommand()
405 4
            ->delete($this->ruleTable, ['name' => $rule->name])
406 4
            ->execute();
407
408 4
        $this->invalidateCache();
409
410 4
        return true;
411
    }
412
413
    /**
414
     * @inheritdoc
415
     */
416 16
    protected function getItems($type)
417
    {
418 16
        $query = (new Query)
419 16
            ->from($this->itemTable)
420 16
            ->where(['type' => $type]);
421
422 16
        $items = [];
423 16
        foreach ($query->all($this->db) as $row) {
424 16
            $items[$row['name']] = $this->populateItem($row);
425 16
        }
426
427 16
        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 76
    protected function populateItem($row)
436
    {
437 76
        $class = $row['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className();
438
439 76
        if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
440 76
            $data = null;
441 76
        }
442
443 76
        return new $class([
444 76
            'name' => $row['name'],
445 76
            'type' => $row['type'],
446 76
            'description' => $row['description'],
447 76
            'ruleName' => $row['rule_name'],
448 76
            'data' => $data,
449 76
            'createdAt' => $row['created_at'],
450 76
            'updatedAt' => $row['updated_at'],
451 76
        ]);
452
    }
453
454
    /**
455
     * @inheritdoc
456
     */
457 8
    public function getRolesByUser($userId)
458
    {
459 8
        if (!isset($userId) || $userId === '') {
460
            return [];
461
        }
462
463 8
        $query = (new Query)->select('b.*')
464 8
            ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
465 8
            ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
466 8
            ->andWhere(['a.user_id' => (string) $userId])
467 8
            ->andWhere(['b.type' => Item::TYPE_ROLE]);
468
469 8
        $roles = $this->getDefaultRoles();
470 8
        foreach ($query->all($this->db) as $row) {
471 8
            $roles[$row['name']] = $this->populateItem($row);
472 8
        }
473 8
        return $roles;
474
    }
475
476
    /**
477
     * @inheritdoc
478
     */
479 4
    public function getChildRoles($roleName)
480
    {
481 4
        $role = $this->getRole($roleName);
482
483 4
        if (is_null($role)) {
484
            throw new InvalidParamException("Role \"$roleName\" not found.");
485
        }
486
487 4
        $result = [];
488 4
        $this->getChildrenRecursive($roleName, $this->getChildrenList(), $result);
489
490 4
        $roles = [$roleName => $role];
491
492 4
        $roles += array_filter($this->getRoles(), function (Role $roleItem) use ($result) {
493 4
            return array_key_exists($roleItem->name, $result);
494 4
        });
495
496 4
        return $roles;
497
    }
498
499
    /**
500
     * @inheritdoc
501
     */
502 4
    public function getPermissionsByRole($roleName)
503
    {
504 4
        $childrenList = $this->getChildrenList();
505 4
        $result = [];
506 4
        $this->getChildrenRecursive($roleName, $childrenList, $result);
507 4
        if (empty($result)) {
508
            return [];
509
        }
510 4
        $query = (new Query)->from($this->itemTable)->where([
511 4
            'type' => Item::TYPE_PERMISSION,
512 4
            'name' => array_keys($result),
513 4
        ]);
514 4
        $permissions = [];
515 4
        foreach ($query->all($this->db) as $row) {
516 4
            $permissions[$row['name']] = $this->populateItem($row);
517 4
        }
518 4
        return $permissions;
519
    }
520
521
    /**
522
     * @inheritdoc
523
     */
524 4
    public function getPermissionsByUser($userId)
525
    {
526 4
        if (empty($userId)) {
527
            return [];
528
        }
529
530 4
        $directPermission = $this->getDirectPermissionsByUser($userId);
531 4
        $inheritedPermission = $this->getInheritedPermissionsByUser($userId);
532
533 4
        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 4
    protected function getDirectPermissionsByUser($userId)
543
    {
544 4
        $query = (new Query)->select('b.*')
545 4
            ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
546 4
            ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
547 4
            ->andWhere(['a.user_id' => (string) $userId])
548 4
            ->andWhere(['b.type' => Item::TYPE_PERMISSION]);
549
550 4
        $permissions = [];
551 4
        foreach ($query->all($this->db) as $row) {
552 4
            $permissions[$row['name']] = $this->populateItem($row);
553 4
        }
554 4
        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 4
    protected function getInheritedPermissionsByUser($userId)
564
    {
565 4
        $query = (new Query)->select('item_name')
566 4
            ->from($this->assignmentTable)
567 4
            ->where(['user_id' => (string) $userId]);
568
569 4
        $childrenList = $this->getChildrenList();
570 4
        $result = [];
571 4
        foreach ($query->column($this->db) as $roleName) {
572 4
            $this->getChildrenRecursive($roleName, $childrenList, $result);
573 4
        }
574
575 4
        if (empty($result)) {
576
            return [];
577
        }
578
579 4
        $query = (new Query)->from($this->itemTable)->where([
580 4
            'type' => Item::TYPE_PERMISSION,
581 4
            'name' => array_keys($result),
582 4
        ]);
583 4
        $permissions = [];
584 4
        foreach ($query->all($this->db) as $row) {
585 4
            $permissions[$row['name']] = $this->populateItem($row);
586 4
        }
587 4
        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 12
    protected function getChildrenList()
596
    {
597 12
        $query = (new Query)->from($this->itemChildTable);
598 12
        $parents = [];
599 12
        foreach ($query->all($this->db) as $row) {
600 12
            $parents[$row['parent']][] = $row['child'];
601 12
        }
602 12
        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 12
    protected function getChildrenRecursive($name, $childrenList, &$result)
612
    {
613 12
        if (isset($childrenList[$name])) {
614 12
            foreach ($childrenList[$name] as $child) {
615 12
                $result[$child] = true;
616 12
                $this->getChildrenRecursive($child, $childrenList, $result);
617 12
            }
618 12
        }
619 12
    }
620
621
    /**
622
     * @inheritdoc
623
     */
624 76
    public function getRule($name)
625
    {
626 76
        if ($this->rules !== null) {
627 2
            return isset($this->rules[$name]) ? $this->rules[$name] : null;
628
        }
629
630 76
        $row = (new Query)->select(['data'])
631 76
            ->from($this->ruleTable)
632 76
            ->where(['name' => $name])
633 76
            ->one($this->db);
634 76
        if ($row === false) {
635 12
            return null;
636
        }
637 76
        $data = $row['data'];
638 76
        if (is_resource($data)) {
639
            $data = stream_get_contents($data);
640
        }
641 76
        return unserialize($data);
642
643
    }
644
645
    /**
646
     * @inheritdoc
647
     */
648 20
    public function getRules()
649
    {
650 20
        if ($this->rules !== null) {
651
            return $this->rules;
652
        }
653
654 20
        $query = (new Query)->from($this->ruleTable);
655
656 20
        $rules = [];
657 20
        foreach ($query->all($this->db) as $row) {
658 12
            $data = $row['data'];
659 12
            if (is_resource($data)) {
660
               $data = stream_get_contents($data);
661
            }
662 12
            $rules[$row['name']] = unserialize($data);
663 20
        }
664
665 20
        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 12
    public function getAssignments($userId)
696
    {
697 12
        if (empty($userId)) {
698 4
            return [];
699
        }
700
701 12
        $query = (new Query)
702 12
            ->from($this->assignmentTable)
703 12
            ->where(['user_id' => (string) $userId]);
704
705 12
        $assignments = [];
706 12
        foreach ($query->all($this->db) as $row) {
707 12
            $assignments[$row['item_name']] = new Assignment([
708 12
                'userId' => $row['user_id'],
709 12
                'roleName' => $row['item_name'],
710 12
                'createdAt' => $row['created_at'],
711 12
            ]);
712 12
        }
713
714 12
        return $assignments;
715
    }
716
717
    /**
718
     * @inheritdoc
719
     * @since 2.0.8
720
     */
721 4
    public function canAddChild($parent, $child)
722
    {
723 4
        return !$this->detectLoop($parent, $child);
724
    }
725
726
    /**
727
     * @inheritdoc
728
     */
729 72
    public function addChild($parent, $child)
730
    {
731 72
        if ($parent->name === $child->name) {
732
            throw new InvalidParamException("Cannot add '{$parent->name}' as a child of itself.");
733
        }
734
735 72
        if ($parent instanceof Permission && $child instanceof Role) {
736
            throw new InvalidParamException('Cannot add a role as a child of a permission.');
737
        }
738
739 72
        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 72
        $this->db->createCommand()
744 72
            ->insert($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
745 72
            ->execute();
746
747 72
        $this->invalidateCache();
748
749 72
        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 72
    public function getChildren($name)
795
    {
796 72
        $query = (new Query)
797 72
            ->select(['name', 'type', 'description', 'rule_name', 'data', 'created_at', 'updated_at'])
798 72
            ->from([$this->itemTable, $this->itemChildTable])
799 72
            ->where(['parent' => $name, 'name' => new Expression('[[child]]')]);
800
801 72
        $children = [];
802 72
        foreach ($query->all($this->db) as $row) {
803 72
            $children[$row['name']] = $this->populateItem($row);
804 72
        }
805
806 72
        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 72
    protected function detectLoop($parent, $child)
816
    {
817 72
        if ($child->name === $parent->name) {
818 4
            return true;
819
        }
820 72
        foreach ($this->getChildren($child->name) as $grandchild) {
821 68
            if ($this->detectLoop($parent, $grandchild)) {
822 4
                return true;
823
            }
824 72
        }
825 72
        return false;
826
    }
827
828
    /**
829
     * @inheritdoc
830
     */
831 72
    public function assign($role, $userId)
832
    {
833 72
        $assignment = new Assignment([
834 72
            'userId' => $userId,
835 72
            'roleName' => $role->name,
836 72
            'createdAt' => time(),
837 72
        ]);
838
839 72
        $this->db->createCommand()
840 72
            ->insert($this->assignmentTable, [
841 72
                'user_id' => $assignment->userId,
842 72
                'item_name' => $assignment->roleName,
843 72
                'created_at' => $assignment->createdAt,
844 72
            ])->execute();
845
846 72
        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 92
    public function removeAll()
881
    {
882 92
        $this->removeAllAssignments();
883 92
        $this->db->createCommand()->delete($this->itemChildTable)->execute();
884 92
        $this->db->createCommand()->delete($this->itemTable)->execute();
885 92
        $this->db->createCommand()->delete($this->ruleTable)->execute();
886 92
        $this->invalidateCache();
887 92
    }
888
889
    /**
890
     * @inheritdoc
891
     */
892 4
    public function removeAllPermissions()
893
    {
894 4
        $this->removeAllItems(Item::TYPE_PERMISSION);
895 4
    }
896
897
    /**
898
     * @inheritdoc
899
     */
900 4
    public function removeAllRoles()
901
    {
902 4
        $this->removeAllItems(Item::TYPE_ROLE);
903 4
    }
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 8
    protected function removeAllItems($type)
910
    {
911 8
        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 8
        $this->db->createCommand()
929 8
            ->delete($this->itemTable, ['type' => $type])
930 8
            ->execute();
931
932 8
        $this->invalidateCache();
933 8
    }
934
935
    /**
936
     * @inheritdoc
937
     */
938 4
    public function removeAllRules()
939
    {
940 4
        if (!$this->supportsCascadeUpdate()) {
941
            $this->db->createCommand()
942
                ->update($this->itemTable, ['rule_name' => null])
943
                ->execute();
944
        }
945
946 4
        $this->db->createCommand()->delete($this->ruleTable)->execute();
947
948 4
        $this->invalidateCache();
949 4
    }
950
951
    /**
952
     * @inheritdoc
953
     */
954 92
    public function removeAllAssignments()
955
    {
956 92
        $this->db->createCommand()->delete($this->assignmentTable)->execute();
957 92
    }
958
959 92
    public function invalidateCache()
960
    {
961 92
        if ($this->cache !== null) {
962 23
            $this->cache->delete($this->cacheKey);
963 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...
964 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...
965 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...
966 23
        }
967 92
    }
968
969 8
    public function loadFromCache()
970
    {
971 8
        if ($this->items !== null || !$this->cache instanceof Cache) {
972 8
            return;
973
        }
974
975 2
        $data = $this->cache->get($this->cacheKey);
976 2
        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 2
        $query = (new Query)->from($this->itemTable);
982 2
        $this->items = [];
983 2
        foreach ($query->all($this->db) as $row) {
984 2
            $this->items[$row['name']] = $this->populateItem($row);
985 2
        }
986
987 2
        $query = (new Query)->from($this->ruleTable);
988 2
        $this->rules = [];
989 2
        foreach ($query->all($this->db) as $row) {
990 2
            $this->rules[$row['name']] = unserialize($row['data']);
991 2
        }
992
993 2
        $query = (new Query)->from($this->itemChildTable);
994 2
        $this->parents = [];
995 2
        foreach ($query->all($this->db) as $row) {
996 1
            if (isset($this->items[$row['child']])) {
997 1
                $this->parents[$row['child']][] = $row['parent'];
998 1
            }
999 2
        }
1000
1001 2
        $this->cache->set($this->cacheKey, [$this->items, $this->rules, $this->parents]);
1002 2
    }
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 4
    public function getUserIdsByRole($roleName)
1012
    {
1013 4
        if (empty($roleName)) {
1014
            return [];
1015
        }
1016
1017 4
        return (new Query)->select('[[user_id]]')
1018 4
            ->from($this->assignmentTable)
1019 4
            ->where(['item_name' => $roleName])->column($this->db);
1020
    }
1021
}
1022