Passed
Push — master ( 28cbf6...ac1a5a )
by
unknown
05:50 queued 19s
created

DbManager::getPermissionsByRole()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3.0032

Importance

Changes 0
Metric Value
cc 3
eloc 12
nc 3
nop 1
dl 0
loc 18
ccs 13
cts 14
cp 0.9286
crap 3.0032
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @link https://www.yiiframework.com/
5
 * @copyright Copyright (c) 2008 Yii Software LLC
6
 * @license https://www.yiiframework.com/license/
7
 */
8
9
namespace yii\rbac;
10
11
use Yii;
12
use yii\base\InvalidArgumentException;
13
use yii\base\InvalidCallException;
14
use yii\caching\CacheInterface;
15
use yii\db\Connection;
16
use yii\db\Expression;
17
use yii\db\Query;
18
use yii\di\Instance;
19
20
/**
21
 * DbManager represents an authorization manager that stores authorization information in database.
22
 *
23
 * The database connection is specified by [[db]]. The database schema could be initialized by applying migration:
24
 *
25
 * ```
26
 * yii migrate --migrationPath=@yii/rbac/migrations/
27
 * ```
28
 *
29
 * If you don't want to use migration and need SQL instead, files for all databases are in migrations directory.
30
 *
31
 * You may change the names of the tables used to store the authorization and rule data by setting [[itemTable]],
32
 * [[itemChildTable]], [[assignmentTable]] and [[ruleTable]].
33
 *
34
 * For more details and usage information on DbManager, see the [guide article on security authorization](guide:security-authorization).
35
 *
36
 * @author Qiang Xue <[email protected]>
37
 * @author Alexander Kochetov <[email protected]>
38
 * @since 2.0
39
 */
40
class DbManager extends BaseManager
41
{
42
    /**
43
     * @var Connection|array|string the DB connection object or the application component ID of the DB connection.
44
     * After the DbManager object is created, if you want to change this property, you should only assign it
45
     * with a DB connection object.
46
     * Starting from version 2.0.2, this can also be a configuration array for creating the object.
47
     */
48
    public $db = 'db';
49
    /**
50
     * @var string the name of the table storing authorization items. Defaults to "auth_item".
51
     */
52
    public $itemTable = '{{%auth_item}}';
53
    /**
54
     * @var string the name of the table storing authorization item hierarchy. Defaults to "auth_item_child".
55
     */
56
    public $itemChildTable = '{{%auth_item_child}}';
57
    /**
58
     * @var string the name of the table storing authorization item assignments. Defaults to "auth_assignment".
59
     */
60
    public $assignmentTable = '{{%auth_assignment}}';
61
    /**
62
     * @var string the name of the table storing rules. Defaults to "auth_rule".
63
     */
64
    public $ruleTable = '{{%auth_rule}}';
65
    /**
66
     * @var CacheInterface|array|string|null the cache used to improve RBAC performance. This can be one of the following:
67
     *
68
     * - an application component ID (e.g. `cache`)
69
     * - a configuration array
70
     * - a [[\yii\caching\Cache]] object
71
     *
72
     * When this is not set, it means caching is not enabled.
73
     *
74
     * Note that by enabling RBAC cache, all auth items, rules and auth item parent-child relationships will
75
     * be cached and loaded into memory. This will improve the performance of RBAC permission check. However,
76
     * it does require extra memory and as a result may not be appropriate if your RBAC system contains too many
77
     * auth items. You should seek other RBAC implementations (e.g. RBAC based on Redis storage) in this case.
78
     *
79
     * Also note that if you modify RBAC items, rules or parent-child relationships from outside of this component,
80
     * you have to manually call [[invalidateCache()]] to ensure data consistency.
81
     *
82
     * @since 2.0.3
83
     */
84
    public $cache;
85
    /**
86
     * @var string the key used to store RBAC data in cache
87
     * @see cache
88
     * @since 2.0.3
89
     */
90
    public $cacheKey = 'rbac';
91
    /**
92
     * @var string the key used to store user RBAC roles in cache
93
     * @since 2.0.48
94
     */
95
    public $rolesCacheSuffix = 'roles';
96
97
    /**
98
     * @var Item[] all auth items (name => Item)
99
     */
100
    protected $items;
101
    /**
102
     * @var Rule[] all auth rules (name => Rule)
103
     */
104
    protected $rules;
105
    /**
106
     * @var array auth item parent-child relationships (childName => list of parents)
107
     */
108
    protected $parents;
109
    /**
110
     * @var array user assignments (user id => Assignment[])
111
     * @since `protected` since 2.0.38
112
     */
113
    protected $checkAccessAssignments = [];
114
115
116
    /**
117
     * Initializes the application component.
118
     * This method overrides the parent implementation by establishing the database connection.
119
     */
120 250
    public function init()
121
    {
122 250
        parent::init();
123 250
        $this->db = Instance::ensure($this->db, Connection::className());
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

123
        $this->db = Instance::ensure($this->db, /** @scrutinizer ignore-deprecated */ Connection::className());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
124 250
        if ($this->cache !== null) {
125 100
            $this->cache = Instance::ensure($this->cache, 'yii\caching\CacheInterface');
126
        }
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132 30
    public function checkAccess($userId, $permissionName, $params = [])
133
    {
134 30
        if (isset($this->checkAccessAssignments[(string) $userId])) {
135 30
            $assignments = $this->checkAccessAssignments[(string) $userId];
136
        } else {
137 30
            $assignments = $this->getAssignments($userId);
138 30
            $this->checkAccessAssignments[(string) $userId] = $assignments;
139
        }
140
141 30
        if ($this->hasNoAssignments($assignments)) {
142
            return false;
143
        }
144
145 30
        $this->loadFromCache();
146 30
        if ($this->items !== null) {
147 15
            return $this->checkAccessFromCache($userId, $permissionName, $params, $assignments);
148
        }
149
150 15
        return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments);
151
    }
152
153
    /**
154
     * Performs access check for the specified user based on the data loaded from cache.
155
     * This method is internally called by [[checkAccess()]] when [[cache]] is enabled.
156
     * @param string|int $user the user ID. This should can be either an integer or a string representing
157
     * the unique identifier of a user. See [[\yii\web\User::id]].
158
     * @param string $itemName the name of the operation that need access check
159
     * @param array $params name-value pairs that would be passed to rules associated
160
     * with the tasks and roles assigned to the user. A param with name 'user' is added to this array,
161
     * which holds the value of `$userId`.
162
     * @param Assignment[] $assignments the assignments to the specified user
163
     * @return bool whether the operations can be performed by the user.
164
     * @since 2.0.3
165
     */
166 15
    protected function checkAccessFromCache($user, $itemName, $params, $assignments)
167
    {
168 15
        if (!isset($this->items[$itemName])) {
169 2
            return false;
170
        }
171
172 15
        $item = $this->items[$itemName];
173
174 15
        Yii::debug($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
175
176 15
        if (!$this->executeRule($user, $item, $params)) {
177 10
            return false;
178
        }
179
180 15
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
181 11
            return true;
182
        }
183
184 11
        if (!empty($this->parents[$itemName])) {
185 7
            foreach ($this->parents[$itemName] as $parent) {
186 7
                if ($this->checkAccessFromCache($user, $parent, $params, $assignments)) {
187 7
                    return true;
188
                }
189
            }
190
        }
191
192 11
        return false;
193
    }
194
195
    /**
196
     * Performs access check for the specified user.
197
     * This method is internally called by [[checkAccess()]].
198
     * @param string|int $user the user ID. This should can be either an integer or a string representing
199
     * the unique identifier of a user. See [[\yii\web\User::id]].
200
     * @param string $itemName the name of the operation that need access check
201
     * @param array $params name-value pairs that would be passed to rules associated
202
     * with the tasks and roles assigned to the user. A param with name 'user' is added to this array,
203
     * which holds the value of `$userId`.
204
     * @param Assignment[] $assignments the assignments to the specified user
205
     * @return bool whether the operations can be performed by the user.
206
     */
207 15
    protected function checkAccessRecursive($user, $itemName, $params, $assignments)
208
    {
209 15
        if (($item = $this->getItem($itemName)) === null) {
210 3
            return false;
211
        }
212
213 15
        Yii::debug($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
214
215 15
        if (!$this->executeRule($user, $item, $params)) {
216 15
            return false;
217
        }
218
219 15
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
220 9
            return true;
221
        }
222
223 9
        $query = new Query();
224 9
        $parents = $query->select(['parent'])
225 9
            ->from($this->itemChildTable)
226 9
            ->where(['child' => $itemName])
227 9
            ->column($this->db);
228 9
        foreach ($parents as $parent) {
229 3
            if ($this->checkAccessRecursive($user, $parent, $params, $assignments)) {
230 3
                return true;
231
            }
232
        }
233
234 9
        return false;
235
    }
236
237
    /**
238
     * {@inheritdoc}
239
     */
240 84
    protected function getItem($name)
241
    {
242 84
        if (empty($name)) {
243 3
            return null;
244
        }
245
246 84
        if (!empty($this->items[$name])) {
247 9
            return $this->items[$name];
248
        }
249
250 75
        $row = (new Query())->from($this->itemTable)
251 75
            ->where(['name' => $name])
252 75
            ->one($this->db);
253
254 75
        if ($row === false) {
255 13
            return null;
256
        }
257
258 75
        return $this->populateItem($row);
259
    }
260
261
    /**
262
     * Returns a value indicating whether the database supports cascading update and delete.
263
     * The default implementation will return false for SQLite database and true for all other databases.
264
     * @return bool whether the database supports cascading update and delete.
265
     */
266 35
    protected function supportsCascadeUpdate()
267
    {
268 35
        return strncmp($this->db->getDriverName(), 'sqlite', 6) !== 0;
0 ignored issues
show
Bug introduced by
It seems like $this->db->getDriverName() can also be of type null; however, parameter $string1 of strncmp() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

268
        return strncmp(/** @scrutinizer ignore-type */ $this->db->getDriverName(), 'sqlite', 6) !== 0;
Loading history...
269
    }
270
271
    /**
272
     * {@inheritdoc}
273
     */
274 225
    protected function addItem($item)
275
    {
276 225
        $time = time();
277 225
        if ($item->createdAt === null) {
278 225
            $item->createdAt = $time;
279
        }
280 225
        if ($item->updatedAt === null) {
281 225
            $item->updatedAt = $time;
282
        }
283 225
        $this->db->createCommand()
284 225
            ->insert($this->itemTable, [
285 225
                'name' => $item->name,
286 225
                'type' => $item->type,
287 225
                'description' => $item->description,
288 225
                'rule_name' => $item->ruleName,
289 225
                'data' => $item->data === null ? null : serialize($item->data),
290 225
                'created_at' => $item->createdAt,
291 225
                'updated_at' => $item->updatedAt,
292 225
            ])->execute();
293
294 225
        $this->invalidateCache();
295
296 225
        return true;
297
    }
298
299
    /**
300
     * {@inheritdoc}
301
     */
302 5
    protected function removeItem($item)
303
    {
304 5
        if (!$this->supportsCascadeUpdate()) {
305 1
            $this->db->createCommand()
306 1
                ->delete($this->itemChildTable, ['or', '[[parent]]=:parent', '[[child]]=:child'], [':parent' => $item->name, ':child' => $item->name])
307 1
                ->execute();
308 1
            $this->db->createCommand()
309 1
                ->delete($this->assignmentTable, ['item_name' => $item->name])
310 1
                ->execute();
311
        }
312
313 5
        $this->db->createCommand()
314 5
            ->delete($this->itemTable, ['name' => $item->name])
315 5
            ->execute();
316
317 5
        $this->invalidateCache();
318
319 5
        return true;
320
    }
321
322
    /**
323
     * {@inheritdoc}
324
     */
325 15
    protected function updateItem($name, $item)
326
    {
327 15
        if ($item->name !== $name && !$this->supportsCascadeUpdate()) {
328 3
            $this->db->createCommand()
329 3
                ->update($this->itemChildTable, ['parent' => $item->name], ['parent' => $name])
330 3
                ->execute();
331 3
            $this->db->createCommand()
332 3
                ->update($this->itemChildTable, ['child' => $item->name], ['child' => $name])
333 3
                ->execute();
334 3
            $this->db->createCommand()
335 3
                ->update($this->assignmentTable, ['item_name' => $item->name], ['item_name' => $name])
336 3
                ->execute();
337
        }
338
339 15
        $item->updatedAt = time();
340
341 15
        $this->db->createCommand()
342 15
            ->update($this->itemTable, [
343 15
                'name' => $item->name,
344 15
                'description' => $item->description,
345 15
                'rule_name' => $item->ruleName,
346 15
                'data' => $item->data === null ? null : serialize($item->data),
347 15
                'updated_at' => $item->updatedAt,
348 15
            ], [
349 15
                'name' => $name,
350 15
            ])->execute();
351
352 15
        $this->invalidateCache();
353
354 15
        return true;
355
    }
356
357
    /**
358
     * {@inheritdoc}
359
     */
360 130
    protected function addRule($rule)
361
    {
362 130
        $time = time();
363 130
        if ($rule->createdAt === null) {
364 130
            $rule->createdAt = $time;
365
        }
366 130
        if ($rule->updatedAt === null) {
367 130
            $rule->updatedAt = $time;
368
        }
369 130
        $this->db->createCommand()
370 130
            ->insert($this->ruleTable, [
371 130
                'name' => $rule->name,
372 130
                'data' => serialize($rule),
373 130
                'created_at' => $rule->createdAt,
374 130
                'updated_at' => $rule->updatedAt,
375 130
            ])->execute();
376
377 130
        $this->invalidateCache();
378
379 130
        return true;
380
    }
381
382
    /**
383
     * {@inheritdoc}
384
     */
385 5
    protected function updateRule($name, $rule)
386
    {
387 5
        if ($rule->name !== $name && !$this->supportsCascadeUpdate()) {
388 1
            $this->db->createCommand()
389 1
                ->update($this->itemTable, ['rule_name' => $rule->name], ['rule_name' => $name])
390 1
                ->execute();
391
        }
392
393 5
        $rule->updatedAt = time();
394
395 5
        $this->db->createCommand()
396 5
            ->update($this->ruleTable, [
397 5
                'name' => $rule->name,
398 5
                'data' => serialize($rule),
399 5
                'updated_at' => $rule->updatedAt,
400 5
            ], [
401 5
                'name' => $name,
402 5
            ])->execute();
403
404 5
        $this->invalidateCache();
405
406 5
        return true;
407
    }
408
409
    /**
410
     * {@inheritdoc}
411
     */
412 5
    protected function removeRule($rule)
413
    {
414 5
        if (!$this->supportsCascadeUpdate()) {
415 1
            $this->db->createCommand()
416 1
                ->update($this->itemTable, ['rule_name' => null], ['rule_name' => $rule->name])
417 1
                ->execute();
418
        }
419
420 5
        $this->db->createCommand()
421 5
            ->delete($this->ruleTable, ['name' => $rule->name])
422 5
            ->execute();
423
424 5
        $this->invalidateCache();
425
426 5
        return true;
427
    }
428
429
    /**
430
     * {@inheritdoc}
431
     */
432 20
    protected function getItems($type)
433
    {
434 20
        $query = (new Query())
435 20
            ->from($this->itemTable)
436 20
            ->where(['type' => $type]);
437
438 20
        $items = [];
439 20
        foreach ($query->all($this->db) as $row) {
440 20
            $items[$row['name']] = $this->populateItem($row);
441
        }
442
443 20
        return $items;
444
    }
445
446
    /**
447
     * Populates an auth item with the data fetched from database.
448
     * @param array $row the data from the auth item table
449
     * @return Item the populated auth item instance (either Role or Permission)
450
     */
451 165
    protected function populateItem($row)
452
    {
453 165
        $class = $row['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className();
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

453
        $class = $row['type'] == Item::TYPE_PERMISSION ? Permission::className() : /** @scrutinizer ignore-deprecated */ Role::className();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
454
455 165
        if (!isset($row['data']) || ($data = @unserialize(is_resource($row['data']) ? stream_get_contents($row['data']) : $row['data'])) === false) {
456 165
            $data = null;
457
        }
458
459 165
        return new $class([
460 165
            'name' => $row['name'],
461 165
            'type' => $row['type'],
462 165
            'description' => $row['description'],
463 165
            'ruleName' => $row['rule_name'] ?: null,
464 165
            'data' => $data,
465 165
            'createdAt' => $row['created_at'],
466 165
            'updatedAt' => $row['updated_at'],
467 165
        ]);
468
    }
469
470
    /**
471
     * {@inheritdoc}
472
     * The roles returned by this method include the roles assigned via [[$defaultRoles]].
473
     */
474 30
    public function getRolesByUser($userId)
475
    {
476 30
        if ($this->isEmptyUserId($userId)) {
477 5
            return [];
478
        }
479
480 25
        if ($this->cache !== null) {
481 13
            $data = $this->cache->get($this->getUserRolesCacheKey($userId));
482
483 13
            if ($data !== false) {
484
                return $data;
485
            }
486
        }
487
488 25
        $query = (new Query())->select('b.*')
489 25
            ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
490 25
            ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
491 25
            ->andWhere(['a.user_id' => (string) $userId])
492 25
            ->andWhere(['b.type' => Item::TYPE_ROLE]);
493
494 25
        $roles = $this->getDefaultRoleInstances();
495 25
        foreach ($query->all($this->db) as $row) {
496 25
            $roles[$row['name']] = $this->populateItem($row);
497
        }
498
499 25
        if ($this->cache !== null) {
500 13
            $this->cacheUserRolesData($userId, $roles);
501
        }
502
503 25
        return $roles;
504
    }
505
506
    /**
507
     * {@inheritdoc}
508
     */
509 5
    public function getChildRoles($roleName)
510
    {
511 5
        $role = $this->getRole($roleName);
512
513 5
        if ($role === null) {
514
            throw new InvalidArgumentException("Role \"$roleName\" not found.");
515
        }
516
517 5
        $result = [];
518 5
        $this->getChildrenRecursive($roleName, $this->getChildrenList(), $result);
519
520 5
        $roles = [$roleName => $role];
521
522 5
        $roles += array_filter($this->getRoles(), function (Role $roleItem) use ($result) {
523 5
            return array_key_exists($roleItem->name, $result);
524 5
        });
525
526 5
        return $roles;
527
    }
528
529
    /**
530
     * {@inheritdoc}
531
     */
532 5
    public function getPermissionsByRole($roleName)
533
    {
534 5
        $childrenList = $this->getChildrenList();
535 5
        $result = [];
536 5
        $this->getChildrenRecursive($roleName, $childrenList, $result);
537 5
        if (empty($result)) {
538
            return [];
539
        }
540 5
        $query = (new Query())->from($this->itemTable)->where([
541 5
            'type' => Item::TYPE_PERMISSION,
542 5
            'name' => array_keys($result),
543 5
        ]);
544 5
        $permissions = [];
545 5
        foreach ($query->all($this->db) as $row) {
546 5
            $permissions[$row['name']] = $this->populateItem($row);
547
        }
548
549 5
        return $permissions;
550
    }
551
552
    /**
553
     * {@inheritdoc}
554
     */
555 20
    public function getPermissionsByUser($userId)
556
    {
557 20
        if ($this->isEmptyUserId($userId)) {
558 5
            return [];
559
        }
560
561 15
        $directPermission = $this->getDirectPermissionsByUser($userId);
562 15
        $inheritedPermission = $this->getInheritedPermissionsByUser($userId);
563
564 15
        return array_merge($directPermission, $inheritedPermission);
565
    }
566
567
    /**
568
     * Returns all permissions that are directly assigned to user.
569
     * @param string|int $userId the user ID (see [[\yii\web\User::id]])
570
     * @return Permission[] all direct permissions that the user has. The array is indexed by the permission names.
571
     * @since 2.0.7
572
     */
573 15
    protected function getDirectPermissionsByUser($userId)
574
    {
575 15
        $query = (new Query())->select('b.*')
576 15
            ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
577 15
            ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
578 15
            ->andWhere(['a.user_id' => (string) $userId])
579 15
            ->andWhere(['b.type' => Item::TYPE_PERMISSION]);
580
581 15
        $permissions = [];
582 15
        foreach ($query->all($this->db) as $row) {
583 15
            $permissions[$row['name']] = $this->populateItem($row);
584
        }
585
586 15
        return $permissions;
587
    }
588
589
    /**
590
     * Returns all permissions that the user inherits from the roles assigned to him.
591
     * @param string|int $userId the user ID (see [[\yii\web\User::id]])
592
     * @return Permission[] all inherited permissions that the user has. The array is indexed by the permission names.
593
     * @since 2.0.7
594
     */
595 15
    protected function getInheritedPermissionsByUser($userId)
596
    {
597 15
        $query = (new Query())->select('item_name')
598 15
            ->from($this->assignmentTable)
599 15
            ->where(['user_id' => (string) $userId]);
600
601 15
        $childrenList = $this->getChildrenList();
602 15
        $result = [];
603 15
        foreach ($query->column($this->db) as $roleName) {
604 15
            $this->getChildrenRecursive($roleName, $childrenList, $result);
605
        }
606
607 15
        if (empty($result)) {
608 10
            return [];
609
        }
610
611 5
        $query = (new Query())->from($this->itemTable)->where([
612 5
            'type' => Item::TYPE_PERMISSION,
613 5
            'name' => array_keys($result),
614 5
        ]);
615 5
        $permissions = [];
616 5
        foreach ($query->all($this->db) as $row) {
617 5
            $permissions[$row['name']] = $this->populateItem($row);
618
        }
619
620 5
        return $permissions;
621
    }
622
623
    /**
624
     * Returns the children for every parent.
625
     * @return array the children list. Each array key is a parent item name,
626
     * and the corresponding array value is a list of child item names.
627
     */
628 25
    protected function getChildrenList()
629
    {
630 25
        $query = (new Query())->from($this->itemChildTable);
631 25
        $parents = [];
632 25
        foreach ($query->all($this->db) as $row) {
633 15
            $parents[$row['parent']][] = $row['child'];
634
        }
635
636 25
        return $parents;
637
    }
638
639
    /**
640
     * Recursively finds all children and grand children of the specified item.
641
     * @param string $name the name of the item whose children are to be looked for.
642
     * @param array $childrenList the child list built via [[getChildrenList()]]
643
     * @param array $result the children and grand children (in array keys)
644
     */
645 25
    protected function getChildrenRecursive($name, $childrenList, &$result)
646
    {
647 25
        if (isset($childrenList[$name])) {
648 15
            foreach ($childrenList[$name] as $child) {
649 15
                $result[$child] = true;
650 15
                $this->getChildrenRecursive($child, $childrenList, $result);
651
            }
652
        }
653
    }
654
655
    /**
656
     * {@inheritdoc}
657
     */
658 125
    public function getRule($name)
659
    {
660 125
        if ($this->rules !== null) {
661 10
            return isset($this->rules[$name]) ? $this->rules[$name] : null;
662
        }
663
664 125
        $row = (new Query())->select(['data'])
665 125
            ->from($this->ruleTable)
666 125
            ->where(['name' => $name])
667 125
            ->one($this->db);
668 125
        if ($row === false) {
669 20
            return null;
670
        }
671 125
        $data = $row['data'];
672 125
        if (is_resource($data)) {
673 50
            $data = stream_get_contents($data);
674
        }
675 125
        if (!$data) {
676
            return null;
677
        }
678 125
        return unserialize($data);
679
    }
680
681
    /**
682
     * {@inheritdoc}
683
     */
684 25
    public function getRules()
685
    {
686 25
        if ($this->rules !== null) {
687
            return $this->rules;
688
        }
689
690 25
        $query = (new Query())->from($this->ruleTable);
691
692 25
        $rules = [];
693 25
        foreach ($query->all($this->db) as $row) {
694 15
            $data = $row['data'];
695 15
            if (is_resource($data)) {
696 6
                $data = stream_get_contents($data);
697
            }
698 15
            if ($data) {
699 15
                $rules[$row['name']] = unserialize($data);
700
            }
701
        }
702
703 25
        return $rules;
704
    }
705
706
    /**
707
     * {@inheritdoc}
708
     */
709 15
    public function getAssignment($roleName, $userId)
710
    {
711 15
        if ($this->isEmptyUserId($userId)) {
712 5
            return null;
713
        }
714
715 10
        $row = (new Query())->from($this->assignmentTable)
716 10
            ->where(['user_id' => (string) $userId, 'item_name' => $roleName])
717 10
            ->one($this->db);
718
719 10
        if ($row === false) {
720
            return null;
721
        }
722
723 10
        return new Assignment([
724 10
            'userId' => $row['user_id'],
725 10
            'roleName' => $row['item_name'],
726 10
            'createdAt' => $row['created_at'],
727 10
        ]);
728
    }
729
730
    /**
731
     * {@inheritdoc}
732
     */
733 50
    public function getAssignments($userId)
734
    {
735 50
        if ($this->isEmptyUserId($userId)) {
736 5
            return [];
737
        }
738
739 45
        $query = (new Query())
740 45
            ->from($this->assignmentTable)
741 45
            ->where(['user_id' => (string) $userId]);
742
743 45
        $assignments = [];
744 45
        foreach ($query->all($this->db) as $row) {
745 35
            $assignments[$row['item_name']] = new Assignment([
746 35
                'userId' => $row['user_id'],
747 35
                'roleName' => $row['item_name'],
748 35
                'createdAt' => $row['created_at'],
749 35
            ]);
750
        }
751
752 45
        return $assignments;
753
    }
754
755
    /**
756
     * {@inheritdoc}
757
     * @since 2.0.8
758
     */
759 5
    public function canAddChild($parent, $child)
760
    {
761 5
        return !$this->detectLoop($parent, $child);
762
    }
763
764
    /**
765
     * {@inheritdoc}
766
     */
767 105
    public function addChild($parent, $child)
768
    {
769 105
        if ($parent->name === $child->name) {
770
            throw new InvalidArgumentException("Cannot add '{$parent->name}' as a child of itself.");
771
        }
772
773 105
        if ($parent instanceof Permission && $child instanceof Role) {
774
            throw new InvalidArgumentException('Cannot add a role as a child of a permission.');
775
        }
776
777 105
        if ($this->detectLoop($parent, $child)) {
778
            throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected.");
779
        }
780
781 105
        $this->db->createCommand()
782 105
            ->insert($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
783 105
            ->execute();
784
785 105
        $this->invalidateCache();
786
787 105
        return true;
788
    }
789
790
    /**
791
     * {@inheritdoc}
792
     */
793
    public function removeChild($parent, $child)
794
    {
795
        $result = $this->db->createCommand()
796
            ->delete($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
797
            ->execute() > 0;
798
799
        $this->invalidateCache();
800
801
        return $result;
802
    }
803
804
    /**
805
     * {@inheritdoc}
806
     */
807
    public function removeChildren($parent)
808
    {
809
        $result = $this->db->createCommand()
810
            ->delete($this->itemChildTable, ['parent' => $parent->name])
811
            ->execute() > 0;
812
813
        $this->invalidateCache();
814
815
        return $result;
816
    }
817
818
    /**
819
     * {@inheritdoc}
820
     */
821
    public function hasChild($parent, $child)
822
    {
823
        return (new Query())
824
            ->from($this->itemChildTable)
825
            ->where(['parent' => $parent->name, 'child' => $child->name])
826
            ->one($this->db) !== false;
827
    }
828
829
    /**
830
     * {@inheritdoc}
831
     */
832 105
    public function getChildren($name)
833
    {
834 105
        $query = (new Query())
835 105
            ->select(['name', 'type', 'description', 'rule_name', 'data', 'created_at', 'updated_at'])
836 105
            ->from([$this->itemTable, $this->itemChildTable])
837 105
            ->where(['parent' => $name, 'name' => new Expression('[[child]]')]);
838
839 105
        $children = [];
840 105
        foreach ($query->all($this->db) as $row) {
841 105
            $children[$row['name']] = $this->populateItem($row);
842
        }
843
844 105
        return $children;
845
    }
846
847
    /**
848
     * Checks whether there is a loop in the authorization item hierarchy.
849
     * @param Item $parent the parent item
850
     * @param Item $child the child item to be added to the hierarchy
851
     * @return bool whether a loop exists
852
     */
853 105
    protected function detectLoop($parent, $child)
854
    {
855 105
        if ($child->name === $parent->name) {
856 5
            return true;
857
        }
858 105
        foreach ($this->getChildren($child->name) as $grandchild) {
859 100
            if ($this->detectLoop($parent, $grandchild)) {
860 5
                return true;
861
            }
862
        }
863
864 105
        return false;
865
    }
866
867
    /**
868
     * {@inheritdoc}
869
     */
870 215
    public function assign($role, $userId)
871
    {
872 215
        $assignment = new Assignment([
873 215
            'userId' => $userId,
874 215
            'roleName' => $role->name,
875 215
            'createdAt' => time(),
876 215
        ]);
877
878 215
        $this->db->createCommand()
879 215
            ->insert($this->assignmentTable, [
880 215
                'user_id' => $assignment->userId,
881 215
                'item_name' => $assignment->roleName,
882 215
                'created_at' => $assignment->createdAt,
883 215
            ])->execute();
884
885 215
        unset($this->checkAccessAssignments[(string) $userId]);
886
887 215
        $this->invalidateCache();
888
889 215
        return $assignment;
890
    }
891
892
    /**
893
     * {@inheritdoc}
894
     */
895 30
    public function revoke($role, $userId)
896
    {
897 30
        if ($this->isEmptyUserId($userId)) {
898 5
            return false;
899
        }
900
901 25
        unset($this->checkAccessAssignments[(string) $userId]);
902 25
        $result = $this->db->createCommand()
903 25
            ->delete($this->assignmentTable, ['user_id' => (string) $userId, 'item_name' => $role->name])
904 25
            ->execute() > 0;
905
906 25
        $this->invalidateCache();
907
908 25
        return $result;
909
    }
910
911
    /**
912
     * {@inheritdoc}
913
     */
914 20
    public function revokeAll($userId)
915
    {
916 20
        if ($this->isEmptyUserId($userId)) {
917 5
            return false;
918
        }
919
920 15
        unset($this->checkAccessAssignments[(string) $userId]);
921 15
        $result = $this->db->createCommand()
922 15
            ->delete($this->assignmentTable, ['user_id' => (string) $userId])
923 15
            ->execute() > 0;
924
925 15
        $this->invalidateCache();
926
927 15
        return $result;
928
    }
929
930
    /**
931
     * {@inheritdoc}
932
     */
933 250
    public function removeAll()
934
    {
935 250
        $this->removeAllAssignments();
936 250
        $this->db->createCommand()->delete($this->itemChildTable)->execute();
937 250
        $this->db->createCommand()->delete($this->itemTable)->execute();
938 250
        $this->db->createCommand()->delete($this->ruleTable)->execute();
939 250
        $this->invalidateCache();
940
    }
941
942
    /**
943
     * {@inheritdoc}
944
     */
945 5
    public function removeAllPermissions()
946
    {
947 5
        $this->removeAllItems(Item::TYPE_PERMISSION);
948
    }
949
950
    /**
951
     * {@inheritdoc}
952
     */
953 5
    public function removeAllRoles()
954
    {
955 5
        $this->removeAllItems(Item::TYPE_ROLE);
956
    }
957
958
    /**
959
     * Removes all auth items of the specified type.
960
     * @param int $type the auth item type (either Item::TYPE_PERMISSION or Item::TYPE_ROLE)
961
     */
962 10
    protected function removeAllItems($type)
963
    {
964 10
        if (!$this->supportsCascadeUpdate()) {
965 2
            $names = (new Query())
966 2
                ->select(['name'])
967 2
                ->from($this->itemTable)
968 2
                ->where(['type' => $type])
969 2
                ->column($this->db);
970 2
            if (empty($names)) {
971
                return;
972
            }
973 2
            $key = $type == Item::TYPE_PERMISSION ? 'child' : 'parent';
974 2
            $this->db->createCommand()
975 2
                ->delete($this->itemChildTable, [$key => $names])
976 2
                ->execute();
977 2
            $this->db->createCommand()
978 2
                ->delete($this->assignmentTable, ['item_name' => $names])
979 2
                ->execute();
980
        }
981 10
        $this->db->createCommand()
982 10
            ->delete($this->itemTable, ['type' => $type])
983 10
            ->execute();
984
985 10
        $this->invalidateCache();
986
    }
987
988
    /**
989
     * {@inheritdoc}
990
     */
991 5
    public function removeAllRules()
992
    {
993 5
        if (!$this->supportsCascadeUpdate()) {
994 1
            $this->db->createCommand()
995 1
                ->update($this->itemTable, ['rule_name' => null])
996 1
                ->execute();
997
        }
998
999 5
        $this->db->createCommand()->delete($this->ruleTable)->execute();
1000
1001 5
        $this->invalidateCache();
1002
    }
1003
1004
    /**
1005
     * {@inheritdoc}
1006
     */
1007 250
    public function removeAllAssignments()
1008
    {
1009 250
        $this->checkAccessAssignments = [];
1010 250
        $this->db->createCommand()->delete($this->assignmentTable)->execute();
1011
    }
1012
1013 250
    public function invalidateCache()
1014
    {
1015 250
        if ($this->cache !== null) {
1016 106
            $this->cache->delete($this->cacheKey);
1017 106
            $this->items = null;
1018 106
            $this->rules = null;
1019 106
            $this->parents = null;
1020
1021 106
            $cachedUserIds = $this->cache->get($this->getUserRolesCachedSetKey());
1022
1023 106
            if ($cachedUserIds !== false) {
1024 13
                foreach ($cachedUserIds as $userId) {
1025 13
                    $this->cache->delete($this->getUserRolesCacheKey($userId));
1026
                }
1027
1028 13
                $this->cache->delete($this->getUserRolesCachedSetKey());
1029
            }
1030
        }
1031 250
        $this->checkAccessAssignments = [];
1032
    }
1033
1034 30
    public function loadFromCache()
1035
    {
1036 30
        if ($this->items !== null || !$this->cache instanceof CacheInterface) {
1037 30
            return;
1038
        }
1039
1040 15
        $data = $this->cache->get($this->cacheKey);
1041 15
        if (is_array($data) && isset($data[0], $data[1], $data[2])) {
1042
            list($this->items, $this->rules, $this->parents) = $data;
1043
            return;
1044
        }
1045
1046 15
        $query = (new Query())->from($this->itemTable);
1047 15
        $this->items = [];
1048 15
        foreach ($query->all($this->db) as $row) {
1049 15
            $this->items[$row['name']] = $this->populateItem($row);
1050
        }
1051
1052 15
        $query = (new Query())->from($this->ruleTable);
1053 15
        $this->rules = [];
1054 15
        foreach ($query->all($this->db) as $row) {
1055 15
            $data = $row['data'];
1056 15
            if (is_resource($data)) {
1057 7
                $data = stream_get_contents($data);
1058
            }
1059 15
            if ($data) {
1060 15
                $this->rules[$row['name']] = unserialize($data);
1061
            }
1062
        }
1063
1064 15
        $query = (new Query())->from($this->itemChildTable);
1065 15
        $this->parents = [];
1066 15
        foreach ($query->all($this->db) as $row) {
1067 7
            if (isset($this->items[$row['child']])) {
1068 7
                $this->parents[$row['child']][] = $row['parent'];
1069
            }
1070
        }
1071
1072 15
        $this->cache->set($this->cacheKey, [$this->items, $this->rules, $this->parents]);
1073
    }
1074
1075
    /**
1076
     * Returns all role assignment information for the specified role.
1077
     * @param string $roleName
1078
     * @return string[] the ids. An empty array will be
1079
     * returned if role is not assigned to any user.
1080
     * @since 2.0.7
1081
     */
1082 5
    public function getUserIdsByRole($roleName)
1083
    {
1084 5
        if (empty($roleName)) {
1085
            return [];
1086
        }
1087
1088 5
        return (new Query())->select('[[user_id]]')
1089 5
            ->from($this->assignmentTable)
1090 5
            ->where(['item_name' => $roleName])->column($this->db);
1091
    }
1092
1093
    /**
1094
     * Check whether $userId is empty.
1095
     * @param mixed $userId
1096
     * @return bool
1097
     * @since 2.0.26
1098
     */
1099 145
    protected function isEmptyUserId($userId)
1100
    {
1101 145
        return !isset($userId) || $userId === '';
1102
    }
1103
1104 13
    private function getUserRolesCacheKey($userId)
1105
    {
1106 13
        return $this->cacheKey . $this->rolesCacheSuffix . $userId;
1107
    }
1108
1109 106
    private function getUserRolesCachedSetKey()
1110
    {
1111 106
        return $this->cacheKey . $this->rolesCacheSuffix;
1112
    }
1113
1114 13
    private function cacheUserRolesData($userId, $roles)
1115
    {
1116 13
        $cachedUserIds = $this->cache->get($this->getUserRolesCachedSetKey());
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1116
        /** @scrutinizer ignore-call */ 
1117
        $cachedUserIds = $this->cache->get($this->getUserRolesCachedSetKey());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1117
1118 13
        if ($cachedUserIds === false) {
1119 13
            $cachedUserIds = [];
1120
        }
1121
1122 13
        $cachedUserIds[] = $userId;
1123
1124 13
        $this->cache->set($this->getUserRolesCacheKey($userId), $roles);
1125 13
        $this->cache->set($this->getUserRolesCachedSetKey(), $cachedUserIds);
1126
    }
1127
}
1128