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