You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

984 satır
29KB

  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. namespace yii\rbac;
  8. use Yii;
  9. use yii\caching\Cache;
  10. use yii\db\Connection;
  11. use yii\db\Query;
  12. use yii\db\Expression;
  13. use yii\base\InvalidCallException;
  14. use yii\base\InvalidParamException;
  15. use yii\di\Instance;
  16. /**
  17. * DbManager represents an authorization manager that stores authorization information in database.
  18. *
  19. * The database connection is specified by [[db]]. The database schema could be initialized by applying migration:
  20. *
  21. * ```
  22. * yii migrate --migrationPath=@yii/rbac/migrations/
  23. * ```
  24. *
  25. * If you don't want to use migration and need SQL instead, files for all databases are in migrations directory.
  26. *
  27. * You may change the names of the tables used to store the authorization and rule data by setting [[itemTable]],
  28. * [[itemChildTable]], [[assignmentTable]] and [[ruleTable]].
  29. *
  30. * @author Qiang Xue <qiang.xue@gmail.com>
  31. * @author Alexander Kochetov <creocoder@gmail.com>
  32. * @since 2.0
  33. */
  34. class DbManager extends BaseManager
  35. {
  36. /**
  37. * @var Connection|array|string the DB connection object or the application component ID of the DB connection.
  38. * After the DbManager object is created, if you want to change this property, you should only assign it
  39. * with a DB connection object.
  40. * Starting from version 2.0.2, this can also be a configuration array for creating the object.
  41. */
  42. public $db = 'db';
  43. /**
  44. * @var string the name of the table storing authorization items. Defaults to "auth_item".
  45. */
  46. public $itemTable = '{{%auth_item}}';
  47. /**
  48. * @var string the name of the table storing authorization item hierarchy. Defaults to "auth_item_child".
  49. */
  50. public $itemChildTable = '{{%auth_item_child}}';
  51. /**
  52. * @var string the name of the table storing authorization item assignments. Defaults to "auth_assignment".
  53. */
  54. public $assignmentTable = '{{%auth_assignment}}';
  55. /**
  56. * @var string the name of the table storing rules. Defaults to "auth_rule".
  57. */
  58. public $ruleTable = '{{%auth_rule}}';
  59. /**
  60. * @var Cache|array|string the cache used to improve RBAC performance. This can be one of the following:
  61. *
  62. * - an application component ID (e.g. `cache`)
  63. * - a configuration array
  64. * - a [[\yii\caching\Cache]] object
  65. *
  66. * When this is not set, it means caching is not enabled.
  67. *
  68. * Note that by enabling RBAC cache, all auth items, rules and auth item parent-child relationships will
  69. * be cached and loaded into memory. This will improve the performance of RBAC permission check. However,
  70. * it does require extra memory and as a result may not be appropriate if your RBAC system contains too many
  71. * auth items. You should seek other RBAC implementations (e.g. RBAC based on Redis storage) in this case.
  72. *
  73. * Also note that if you modify RBAC items, rules or parent-child relationships from outside of this component,
  74. * you have to manually call [[invalidateCache()]] to ensure data consistency.
  75. *
  76. * @since 2.0.3
  77. */
  78. public $cache;
  79. /**
  80. * @var string the key used to store RBAC data in cache
  81. * @see cache
  82. * @since 2.0.3
  83. */
  84. public $cacheKey = 'rbac';
  85. /**
  86. * @var Item[] all auth items (name => Item)
  87. */
  88. protected $items;
  89. /**
  90. * @var Rule[] all auth rules (name => Rule)
  91. */
  92. protected $rules;
  93. /**
  94. * @var array auth item parent-child relationships (childName => list of parents)
  95. */
  96. protected $parents;
  97. /**
  98. * Initializes the application component.
  99. * This method overrides the parent implementation by establishing the database connection.
  100. */
  101. public function init()
  102. {
  103. parent::init();
  104. $this->db = Instance::ensure($this->db, Connection::className());
  105. if ($this->cache !== null) {
  106. $this->cache = Instance::ensure($this->cache, Cache::className());
  107. }
  108. }
  109. /**
  110. * @inheritdoc
  111. */
  112. public function checkAccess($userId, $permissionName, $params = [])
  113. {
  114. $assignments = $this->getAssignments($userId);
  115. $this->loadFromCache();
  116. if ($this->items !== null) {
  117. return $this->checkAccessFromCache($userId, $permissionName, $params, $assignments);
  118. } else {
  119. return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments);
  120. }
  121. }
  122. /**
  123. * Performs access check for the specified user based on the data loaded from cache.
  124. * This method is internally called by [[checkAccess()]] when [[cache]] is enabled.
  125. * @param string|integer $user the user ID. This should can be either an integer or a string representing
  126. * the unique identifier of a user. See [[\yii\web\User::id]].
  127. * @param string $itemName the name of the operation that need access check
  128. * @param array $params name-value pairs that would be passed to rules associated
  129. * with the tasks and roles assigned to the user. A param with name 'user' is added to this array,
  130. * which holds the value of `$userId`.
  131. * @param Assignment[] $assignments the assignments to the specified user
  132. * @return boolean whether the operations can be performed by the user.
  133. * @since 2.0.3
  134. */
  135. protected function checkAccessFromCache($user, $itemName, $params, $assignments)
  136. {
  137. if (!isset($this->items[$itemName])) {
  138. return false;
  139. }
  140. $item = $this->items[$itemName];
  141. Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
  142. if (!$this->executeRule($user, $item, $params)) {
  143. return false;
  144. }
  145. if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
  146. return true;
  147. }
  148. if (!empty($this->parents[$itemName])) {
  149. foreach ($this->parents[$itemName] as $parent) {
  150. if ($this->checkAccessFromCache($user, $parent, $params, $assignments)) {
  151. return true;
  152. }
  153. }
  154. }
  155. return false;
  156. }
  157. /**
  158. * Performs access check for the specified user.
  159. * This method is internally called by [[checkAccess()]].
  160. * @param string|integer $user the user ID. This should can be either an integer or a string representing
  161. * the unique identifier of a user. See [[\yii\web\User::id]].
  162. * @param string $itemName the name of the operation that need access check
  163. * @param array $params name-value pairs that would be passed to rules associated
  164. * with the tasks and roles assigned to the user. A param with name 'user' is added to this array,
  165. * which holds the value of `$userId`.
  166. * @param Assignment[] $assignments the assignments to the specified user
  167. * @return boolean whether the operations can be performed by the user.
  168. */
  169. protected function checkAccessRecursive($user, $itemName, $params, $assignments)
  170. {
  171. if (($item = $this->getItem($itemName)) === null) {
  172. return false;
  173. }
  174. Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
  175. if (!$this->executeRule($user, $item, $params)) {
  176. return false;
  177. }
  178. if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
  179. return true;
  180. }
  181. $query = new Query;
  182. $parents = $query->select(['parent'])
  183. ->from($this->itemChildTable)
  184. ->where(['child' => $itemName])
  185. ->column($this->db);
  186. foreach ($parents as $parent) {
  187. if ($this->checkAccessRecursive($user, $parent, $params, $assignments)) {
  188. return true;
  189. }
  190. }
  191. return false;
  192. }
  193. /**
  194. * @inheritdoc
  195. */
  196. protected function getItem($name)
  197. {
  198. if (empty($name)) {
  199. return null;
  200. }
  201. if (!empty($this->items[$name])) {
  202. return $this->items[$name];
  203. }
  204. $row = (new Query)->from($this->itemTable)
  205. ->where(['name' => $name])
  206. ->one($this->db);
  207. if ($row === false) {
  208. return null;
  209. }
  210. if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
  211. $row['data'] = null;
  212. }
  213. return $this->populateItem($row);
  214. }
  215. /**
  216. * Returns a value indicating whether the database supports cascading update and delete.
  217. * The default implementation will return false for SQLite database and true for all other databases.
  218. * @return boolean whether the database supports cascading update and delete.
  219. */
  220. protected function supportsCascadeUpdate()
  221. {
  222. return strncmp($this->db->getDriverName(), 'sqlite', 6) !== 0;
  223. }
  224. /**
  225. * @inheritdoc
  226. */
  227. protected function addItem($item)
  228. {
  229. $time = time();
  230. if ($item->createdAt === null) {
  231. $item->createdAt = $time;
  232. }
  233. if ($item->updatedAt === null) {
  234. $item->updatedAt = $time;
  235. }
  236. $this->db->createCommand()
  237. ->insert($this->itemTable, [
  238. 'name' => $item->name,
  239. 'type' => $item->type,
  240. 'description' => $item->description,
  241. 'rule_name' => $item->ruleName,
  242. 'data' => $item->data === null ? null : serialize($item->data),
  243. 'created_at' => $item->createdAt,
  244. 'updated_at' => $item->updatedAt,
  245. ])->execute();
  246. $this->invalidateCache();
  247. return true;
  248. }
  249. /**
  250. * @inheritdoc
  251. */
  252. protected function removeItem($item)
  253. {
  254. if (!$this->supportsCascadeUpdate()) {
  255. $this->db->createCommand()
  256. ->delete($this->itemChildTable, ['or', '[[parent]]=:name', '[[child]]=:name'], [':name' => $item->name])
  257. ->execute();
  258. $this->db->createCommand()
  259. ->delete($this->assignmentTable, ['item_name' => $item->name])
  260. ->execute();
  261. }
  262. $this->db->createCommand()
  263. ->delete($this->itemTable, ['name' => $item->name])
  264. ->execute();
  265. $this->invalidateCache();
  266. return true;
  267. }
  268. /**
  269. * @inheritdoc
  270. */
  271. protected function updateItem($name, $item)
  272. {
  273. if ($item->name !== $name && !$this->supportsCascadeUpdate()) {
  274. $this->db->createCommand()
  275. ->update($this->itemChildTable, ['parent' => $item->name], ['parent' => $name])
  276. ->execute();
  277. $this->db->createCommand()
  278. ->update($this->itemChildTable, ['child' => $item->name], ['child' => $name])
  279. ->execute();
  280. $this->db->createCommand()
  281. ->update($this->assignmentTable, ['item_name' => $item->name], ['item_name' => $name])
  282. ->execute();
  283. }
  284. $item->updatedAt = time();
  285. $this->db->createCommand()
  286. ->update($this->itemTable, [
  287. 'name' => $item->name,
  288. 'description' => $item->description,
  289. 'rule_name' => $item->ruleName,
  290. 'data' => $item->data === null ? null : serialize($item->data),
  291. 'updated_at' => $item->updatedAt,
  292. ], [
  293. 'name' => $name,
  294. ])->execute();
  295. $this->invalidateCache();
  296. return true;
  297. }
  298. /**
  299. * @inheritdoc
  300. */
  301. protected function addRule($rule)
  302. {
  303. $time = time();
  304. if ($rule->createdAt === null) {
  305. $rule->createdAt = $time;
  306. }
  307. if ($rule->updatedAt === null) {
  308. $rule->updatedAt = $time;
  309. }
  310. $this->db->createCommand()
  311. ->insert($this->ruleTable, [
  312. 'name' => $rule->name,
  313. 'data' => serialize($rule),
  314. 'created_at' => $rule->createdAt,
  315. 'updated_at' => $rule->updatedAt,
  316. ])->execute();
  317. $this->invalidateCache();
  318. return true;
  319. }
  320. /**
  321. * @inheritdoc
  322. */
  323. protected function updateRule($name, $rule)
  324. {
  325. if ($rule->name !== $name && !$this->supportsCascadeUpdate()) {
  326. $this->db->createCommand()
  327. ->update($this->itemTable, ['rule_name' => $rule->name], ['rule_name' => $name])
  328. ->execute();
  329. }
  330. $rule->updatedAt = time();
  331. $this->db->createCommand()
  332. ->update($this->ruleTable, [
  333. 'name' => $rule->name,
  334. 'data' => serialize($rule),
  335. 'updated_at' => $rule->updatedAt,
  336. ], [
  337. 'name' => $name,
  338. ])->execute();
  339. $this->invalidateCache();
  340. return true;
  341. }
  342. /**
  343. * @inheritdoc
  344. */
  345. protected function removeRule($rule)
  346. {
  347. if (!$this->supportsCascadeUpdate()) {
  348. $this->db->createCommand()
  349. ->update($this->itemTable, ['rule_name' => null], ['rule_name' => $rule->name])
  350. ->execute();
  351. }
  352. $this->db->createCommand()
  353. ->delete($this->ruleTable, ['name' => $rule->name])
  354. ->execute();
  355. $this->invalidateCache();
  356. return true;
  357. }
  358. /**
  359. * @inheritdoc
  360. */
  361. protected function getItems($type)
  362. {
  363. $query = (new Query)
  364. ->from($this->itemTable)
  365. ->where(['type' => $type]);
  366. $items = [];
  367. foreach ($query->all($this->db) as $row) {
  368. $items[$row['name']] = $this->populateItem($row);
  369. }
  370. return $items;
  371. }
  372. /**
  373. * Populates an auth item with the data fetched from database
  374. * @param array $row the data from the auth item table
  375. * @return Item the populated auth item instance (either Role or Permission)
  376. */
  377. protected function populateItem($row)
  378. {
  379. $class = $row['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className();
  380. if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
  381. $data = null;
  382. }
  383. return new $class([
  384. 'name' => $row['name'],
  385. 'type' => $row['type'],
  386. 'description' => $row['description'],
  387. 'ruleName' => $row['rule_name'],
  388. 'data' => $data,
  389. 'createdAt' => $row['created_at'],
  390. 'updatedAt' => $row['updated_at'],
  391. ]);
  392. }
  393. /**
  394. * @inheritdoc
  395. */
  396. public function getRolesByUser($userId)
  397. {
  398. if (!isset($userId) || $userId === '') {
  399. return [];
  400. }
  401. $query = (new Query)->select('b.*')
  402. ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
  403. ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
  404. ->andWhere(['a.user_id' => (string) $userId])
  405. ->andWhere(['b.type' => Item::TYPE_ROLE]);
  406. $roles = [];
  407. foreach ($query->all($this->db) as $row) {
  408. $roles[$row['name']] = $this->populateItem($row);
  409. }
  410. return $roles;
  411. }
  412. /**
  413. * @inheritdoc
  414. */
  415. public function getPermissionsByRole($roleName)
  416. {
  417. $childrenList = $this->getChildrenList();
  418. $result = [];
  419. $this->getChildrenRecursive($roleName, $childrenList, $result);
  420. if (empty($result)) {
  421. return [];
  422. }
  423. $query = (new Query)->from($this->itemTable)->where([
  424. 'type' => Item::TYPE_PERMISSION,
  425. 'name' => array_keys($result),
  426. ]);
  427. $permissions = [];
  428. foreach ($query->all($this->db) as $row) {
  429. $permissions[$row['name']] = $this->populateItem($row);
  430. }
  431. return $permissions;
  432. }
  433. /**
  434. * @inheritdoc
  435. */
  436. public function getPermissionsByUser($userId)
  437. {
  438. if (empty($userId)) {
  439. return [];
  440. }
  441. $directPermission = $this->getDirectPermissionsByUser($userId);
  442. $inheritedPermission = $this->getInheritedPermissionsByUser($userId);
  443. return array_merge($directPermission, $inheritedPermission);
  444. }
  445. /**
  446. * Returns all permissions that are directly assigned to user.
  447. * @param string|integer $userId the user ID (see [[\yii\web\User::id]])
  448. * @return Permission[] all direct permissions that the user has. The array is indexed by the permission names.
  449. * @since 2.0.7
  450. */
  451. protected function getDirectPermissionsByUser($userId)
  452. {
  453. $query = (new Query)->select('b.*')
  454. ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
  455. ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
  456. ->andWhere(['a.user_id' => (string) $userId])
  457. ->andWhere(['b.type' => Item::TYPE_PERMISSION]);
  458. $permissions = [];
  459. foreach ($query->all($this->db) as $row) {
  460. $permissions[$row['name']] = $this->populateItem($row);
  461. }
  462. return $permissions;
  463. }
  464. /**
  465. * Returns all permissions that the user inherits from the roles assigned to him.
  466. * @param string|integer $userId the user ID (see [[\yii\web\User::id]])
  467. * @return Permission[] all inherited permissions that the user has. The array is indexed by the permission names.
  468. * @since 2.0.7
  469. */
  470. protected function getInheritedPermissionsByUser($userId)
  471. {
  472. $query = (new Query)->select('item_name')
  473. ->from($this->assignmentTable)
  474. ->where(['user_id' => (string) $userId]);
  475. $childrenList = $this->getChildrenList();
  476. $result = [];
  477. foreach ($query->column($this->db) as $roleName) {
  478. $this->getChildrenRecursive($roleName, $childrenList, $result);
  479. }
  480. if (empty($result)) {
  481. return [];
  482. }
  483. $query = (new Query)->from($this->itemTable)->where([
  484. 'type' => Item::TYPE_PERMISSION,
  485. 'name' => array_keys($result),
  486. ]);
  487. $permissions = [];
  488. foreach ($query->all($this->db) as $row) {
  489. $permissions[$row['name']] = $this->populateItem($row);
  490. }
  491. return $permissions;
  492. }
  493. /**
  494. * Returns the children for every parent.
  495. * @return array the children list. Each array key is a parent item name,
  496. * and the corresponding array value is a list of child item names.
  497. */
  498. protected function getChildrenList()
  499. {
  500. $query = (new Query)->from($this->itemChildTable);
  501. $parents = [];
  502. foreach ($query->all($this->db) as $row) {
  503. $parents[$row['parent']][] = $row['child'];
  504. }
  505. return $parents;
  506. }
  507. /**
  508. * Recursively finds all children and grand children of the specified item.
  509. * @param string $name the name of the item whose children are to be looked for.
  510. * @param array $childrenList the child list built via [[getChildrenList()]]
  511. * @param array $result the children and grand children (in array keys)
  512. */
  513. protected function getChildrenRecursive($name, $childrenList, &$result)
  514. {
  515. if (isset($childrenList[$name])) {
  516. foreach ($childrenList[$name] as $child) {
  517. $result[$child] = true;
  518. $this->getChildrenRecursive($child, $childrenList, $result);
  519. }
  520. }
  521. }
  522. /**
  523. * @inheritdoc
  524. */
  525. public function getRule($name)
  526. {
  527. if ($this->rules !== null) {
  528. return isset($this->rules[$name]) ? $this->rules[$name] : null;
  529. }
  530. $row = (new Query)->select(['data'])
  531. ->from($this->ruleTable)
  532. ->where(['name' => $name])
  533. ->one($this->db);
  534. return $row === false ? null : unserialize($row['data']);
  535. }
  536. /**
  537. * @inheritdoc
  538. */
  539. public function getRules()
  540. {
  541. if ($this->rules !== null) {
  542. return $this->rules;
  543. }
  544. $query = (new Query)->from($this->ruleTable);
  545. $rules = [];
  546. foreach ($query->all($this->db) as $row) {
  547. $rules[$row['name']] = unserialize($row['data']);
  548. }
  549. return $rules;
  550. }
  551. /**
  552. * @inheritdoc
  553. */
  554. public function getAssignment($roleName, $userId)
  555. {
  556. if (empty($userId)) {
  557. return null;
  558. }
  559. $row = (new Query)->from($this->assignmentTable)
  560. ->where(['user_id' => (string) $userId, 'item_name' => $roleName])
  561. ->one($this->db);
  562. if ($row === false) {
  563. return null;
  564. }
  565. return new Assignment([
  566. 'userId' => $row['user_id'],
  567. 'roleName' => $row['item_name'],
  568. 'createdAt' => $row['created_at'],
  569. ]);
  570. }
  571. /**
  572. * @inheritdoc
  573. */
  574. public function getAssignments($userId)
  575. {
  576. if (empty($userId)) {
  577. return [];
  578. }
  579. $query = (new Query)
  580. ->from($this->assignmentTable)
  581. ->where(['user_id' => (string) $userId]);
  582. $assignments = [];
  583. foreach ($query->all($this->db) as $row) {
  584. $assignments[$row['item_name']] = new Assignment([
  585. 'userId' => $row['user_id'],
  586. 'roleName' => $row['item_name'],
  587. 'createdAt' => $row['created_at'],
  588. ]);
  589. }
  590. return $assignments;
  591. }
  592. /**
  593. * @inheritdoc
  594. * @since 2.0.8
  595. */
  596. public function canAddChild($parent, $child)
  597. {
  598. return !$this->detectLoop($parent, $child);
  599. }
  600. /**
  601. * @inheritdoc
  602. */
  603. public function addChild($parent, $child)
  604. {
  605. if ($parent->name === $child->name) {
  606. throw new InvalidParamException("Cannot add '{$parent->name}' as a child of itself.");
  607. }
  608. if ($parent instanceof Permission && $child instanceof Role) {
  609. throw new InvalidParamException('Cannot add a role as a child of a permission.');
  610. }
  611. if ($this->detectLoop($parent, $child)) {
  612. throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected.");
  613. }
  614. $this->db->createCommand()
  615. ->insert($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
  616. ->execute();
  617. $this->invalidateCache();
  618. return true;
  619. }
  620. /**
  621. * @inheritdoc
  622. */
  623. public function removeChild($parent, $child)
  624. {
  625. $result = $this->db->createCommand()
  626. ->delete($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
  627. ->execute() > 0;
  628. $this->invalidateCache();
  629. return $result;
  630. }
  631. /**
  632. * @inheritdoc
  633. */
  634. public function removeChildren($parent)
  635. {
  636. $result = $this->db->createCommand()
  637. ->delete($this->itemChildTable, ['parent' => $parent->name])
  638. ->execute() > 0;
  639. $this->invalidateCache();
  640. return $result;
  641. }
  642. /**
  643. * @inheritdoc
  644. */
  645. public function hasChild($parent, $child)
  646. {
  647. return (new Query)
  648. ->from($this->itemChildTable)
  649. ->where(['parent' => $parent->name, 'child' => $child->name])
  650. ->one($this->db) !== false;
  651. }
  652. /**
  653. * @inheritdoc
  654. */
  655. public function getChildren($name)
  656. {
  657. $query = (new Query)
  658. ->select(['name', 'type', 'description', 'rule_name', 'data', 'created_at', 'updated_at'])
  659. ->from([$this->itemTable, $this->itemChildTable])
  660. ->where(['parent' => $name, 'name' => new Expression('[[child]]')]);
  661. $children = [];
  662. foreach ($query->all($this->db) as $row) {
  663. $children[$row['name']] = $this->populateItem($row);
  664. }
  665. return $children;
  666. }
  667. /**
  668. * Checks whether there is a loop in the authorization item hierarchy.
  669. * @param Item $parent the parent item
  670. * @param Item $child the child item to be added to the hierarchy
  671. * @return boolean whether a loop exists
  672. */
  673. protected function detectLoop($parent, $child)
  674. {
  675. if ($child->name === $parent->name) {
  676. return true;
  677. }
  678. foreach ($this->getChildren($child->name) as $grandchild) {
  679. if ($this->detectLoop($parent, $grandchild)) {
  680. return true;
  681. }
  682. }
  683. return false;
  684. }
  685. /**
  686. * @inheritdoc
  687. */
  688. public function assign($role, $userId)
  689. {
  690. $assignment = new Assignment([
  691. 'userId' => $userId,
  692. 'roleName' => $role->name,
  693. 'createdAt' => time(),
  694. ]);
  695. $this->db->createCommand()
  696. ->insert($this->assignmentTable, [
  697. 'user_id' => $assignment->userId,
  698. 'item_name' => $assignment->roleName,
  699. 'created_at' => $assignment->createdAt,
  700. ])->execute();
  701. return $assignment;
  702. }
  703. /**
  704. * @inheritdoc
  705. */
  706. public function revoke($role, $userId)
  707. {
  708. if (empty($userId)) {
  709. return false;
  710. }
  711. return $this->db->createCommand()
  712. ->delete($this->assignmentTable, ['user_id' => (string) $userId, 'item_name' => $role->name])
  713. ->execute() > 0;
  714. }
  715. /**
  716. * @inheritdoc
  717. */
  718. public function revokeAll($userId)
  719. {
  720. if (empty($userId)) {
  721. return false;
  722. }
  723. return $this->db->createCommand()
  724. ->delete($this->assignmentTable, ['user_id' => (string) $userId])
  725. ->execute() > 0;
  726. }
  727. /**
  728. * @inheritdoc
  729. */
  730. public function removeAll()
  731. {
  732. $this->removeAllAssignments();
  733. $this->db->createCommand()->delete($this->itemChildTable)->execute();
  734. $this->db->createCommand()->delete($this->itemTable)->execute();
  735. $this->db->createCommand()->delete($this->ruleTable)->execute();
  736. $this->invalidateCache();
  737. }
  738. /**
  739. * @inheritdoc
  740. */
  741. public function removeAllPermissions()
  742. {
  743. $this->removeAllItems(Item::TYPE_PERMISSION);
  744. }
  745. /**
  746. * @inheritdoc
  747. */
  748. public function removeAllRoles()
  749. {
  750. $this->removeAllItems(Item::TYPE_ROLE);
  751. }
  752. /**
  753. * Removes all auth items of the specified type.
  754. * @param integer $type the auth item type (either Item::TYPE_PERMISSION or Item::TYPE_ROLE)
  755. */
  756. protected function removeAllItems($type)
  757. {
  758. if (!$this->supportsCascadeUpdate()) {
  759. $names = (new Query)
  760. ->select(['name'])
  761. ->from($this->itemTable)
  762. ->where(['type' => $type])
  763. ->column($this->db);
  764. if (empty($names)) {
  765. return;
  766. }
  767. $key = $type == Item::TYPE_PERMISSION ? 'child' : 'parent';
  768. $this->db->createCommand()
  769. ->delete($this->itemChildTable, [$key => $names])
  770. ->execute();
  771. $this->db->createCommand()
  772. ->delete($this->assignmentTable, ['item_name' => $names])
  773. ->execute();
  774. }
  775. $this->db->createCommand()
  776. ->delete($this->itemTable, ['type' => $type])
  777. ->execute();
  778. $this->invalidateCache();
  779. }
  780. /**
  781. * @inheritdoc
  782. */
  783. public function removeAllRules()
  784. {
  785. if (!$this->supportsCascadeUpdate()) {
  786. $this->db->createCommand()
  787. ->update($this->itemTable, ['rule_name' => null])
  788. ->execute();
  789. }
  790. $this->db->createCommand()->delete($this->ruleTable)->execute();
  791. $this->invalidateCache();
  792. }
  793. /**
  794. * @inheritdoc
  795. */
  796. public function removeAllAssignments()
  797. {
  798. $this->db->createCommand()->delete($this->assignmentTable)->execute();
  799. }
  800. public function invalidateCache()
  801. {
  802. if ($this->cache !== null) {
  803. $this->cache->delete($this->cacheKey);
  804. $this->items = null;
  805. $this->rules = null;
  806. $this->parents = null;
  807. }
  808. }
  809. public function loadFromCache()
  810. {
  811. if ($this->items !== null || !$this->cache instanceof Cache) {
  812. return;
  813. }
  814. $data = $this->cache->get($this->cacheKey);
  815. if (is_array($data) && isset($data[0], $data[1], $data[2])) {
  816. list ($this->items, $this->rules, $this->parents) = $data;
  817. return;
  818. }
  819. $query = (new Query)->from($this->itemTable);
  820. $this->items = [];
  821. foreach ($query->all($this->db) as $row) {
  822. $this->items[$row['name']] = $this->populateItem($row);
  823. }
  824. $query = (new Query)->from($this->ruleTable);
  825. $this->rules = [];
  826. foreach ($query->all($this->db) as $row) {
  827. $this->rules[$row['name']] = unserialize($row['data']);
  828. }
  829. $query = (new Query)->from($this->itemChildTable);
  830. $this->parents = [];
  831. foreach ($query->all($this->db) as $row) {
  832. if (isset($this->items[$row['child']])) {
  833. $this->parents[$row['child']][] = $row['parent'];
  834. }
  835. }
  836. $this->cache->set($this->cacheKey, [$this->items, $this->rules, $this->parents]);
  837. }
  838. /**
  839. * Returns all role assignment information for the specified role.
  840. * @param string $roleName
  841. * @return Assignment[] the assignments. An empty array will be
  842. * returned if role is not assigned to any user.
  843. * @since 2.0.7
  844. */
  845. public function getUserIdsByRole($roleName)
  846. {
  847. if (empty($roleName)) {
  848. return [];
  849. }
  850. return (new Query)->select('[[user_id]]')
  851. ->from($this->assignmentTable)
  852. ->where(['item_name' => $roleName])->column($this->db);
  853. }
  854. }