DbManager.php 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  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 three tables used to store the authorization data by setting [[itemTable]],
  28. * [[itemChildTable]] and [[assignmentTable]].
  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 followings:
  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->checkAccessRecursive($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($this->items[$name])) {
  199. return $this->items[$name];
  200. }
  201. $row = (new Query)->from($this->itemTable)
  202. ->where(['name' => $name])
  203. ->one($this->db);
  204. if ($row === false) {
  205. return null;
  206. }
  207. if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
  208. $row['data'] = null;
  209. }
  210. return $this->populateItem($row);
  211. }
  212. /**
  213. * Returns a value indicating whether the database supports cascading update and delete.
  214. * The default implementation will return false for SQLite database and true for all other databases.
  215. * @return boolean whether the database supports cascading update and delete.
  216. */
  217. protected function supportsCascadeUpdate()
  218. {
  219. return strncmp($this->db->getDriverName(), 'sqlite', 6) !== 0;
  220. }
  221. /**
  222. * @inheritdoc
  223. */
  224. protected function addItem($item)
  225. {
  226. $time = time();
  227. if ($item->createdAt === null) {
  228. $item->createdAt = $time;
  229. }
  230. if ($item->updatedAt === null) {
  231. $item->updatedAt = $time;
  232. }
  233. $this->db->createCommand()
  234. ->insert($this->itemTable, [
  235. 'name' => $item->name,
  236. 'type' => $item->type,
  237. 'description' => $item->description,
  238. 'rule_name' => $item->ruleName,
  239. 'data' => $item->data === null ? null : serialize($item->data),
  240. 'created_at' => $item->createdAt,
  241. 'updated_at' => $item->updatedAt,
  242. ])->execute();
  243. $this->invalidateCache();
  244. return true;
  245. }
  246. /**
  247. * @inheritdoc
  248. */
  249. protected function removeItem($item)
  250. {
  251. if (!$this->supportsCascadeUpdate()) {
  252. $this->db->createCommand()
  253. ->delete($this->itemChildTable, ['or', '[[parent]]=:name', '[[child]]=:name'], [':name' => $item->name])
  254. ->execute();
  255. $this->db->createCommand()
  256. ->delete($this->assignmentTable, ['item_name' => $item->name])
  257. ->execute();
  258. }
  259. $this->db->createCommand()
  260. ->delete($this->itemTable, ['name' => $item->name])
  261. ->execute();
  262. $this->invalidateCache();
  263. return true;
  264. }
  265. /**
  266. * @inheritdoc
  267. */
  268. protected function updateItem($name, $item)
  269. {
  270. if ($item->name !== $name && !$this->supportsCascadeUpdate()) {
  271. $this->db->createCommand()
  272. ->update($this->itemChildTable, ['parent' => $item->name], ['parent' => $name])
  273. ->execute();
  274. $this->db->createCommand()
  275. ->update($this->itemChildTable, ['child' => $item->name], ['child' => $name])
  276. ->execute();
  277. $this->db->createCommand()
  278. ->update($this->assignmentTable, ['item_name' => $item->name], ['item_name' => $name])
  279. ->execute();
  280. }
  281. $item->updatedAt = time();
  282. $this->db->createCommand()
  283. ->update($this->itemTable, [
  284. 'name' => $item->name,
  285. 'description' => $item->description,
  286. 'rule_name' => $item->ruleName,
  287. 'data' => $item->data === null ? null : serialize($item->data),
  288. 'updated_at' => $item->updatedAt,
  289. ], [
  290. 'name' => $name,
  291. ])->execute();
  292. $this->invalidateCache();
  293. return true;
  294. }
  295. /**
  296. * @inheritdoc
  297. */
  298. protected function addRule($rule)
  299. {
  300. $time = time();
  301. if ($rule->createdAt === null) {
  302. $rule->createdAt = $time;
  303. }
  304. if ($rule->updatedAt === null) {
  305. $rule->updatedAt = $time;
  306. }
  307. $this->db->createCommand()
  308. ->insert($this->ruleTable, [
  309. 'name' => $rule->name,
  310. 'data' => serialize($rule),
  311. 'created_at' => $rule->createdAt,
  312. 'updated_at' => $rule->updatedAt,
  313. ])->execute();
  314. $this->invalidateCache();
  315. return true;
  316. }
  317. /**
  318. * @inheritdoc
  319. */
  320. protected function updateRule($name, $rule)
  321. {
  322. if ($rule->name !== $name && !$this->supportsCascadeUpdate()) {
  323. $this->db->createCommand()
  324. ->update($this->itemTable, ['rule_name' => $rule->name], ['rule_name' => $name])
  325. ->execute();
  326. }
  327. $rule->updatedAt = time();
  328. $this->db->createCommand()
  329. ->update($this->ruleTable, [
  330. 'name' => $rule->name,
  331. 'data' => serialize($rule),
  332. 'updated_at' => $rule->updatedAt,
  333. ], [
  334. 'name' => $name,
  335. ])->execute();
  336. $this->invalidateCache();
  337. return true;
  338. }
  339. /**
  340. * @inheritdoc
  341. */
  342. protected function removeRule($rule)
  343. {
  344. if (!$this->supportsCascadeUpdate()) {
  345. $this->db->createCommand()
  346. ->update($this->itemTable, ['rule_name' => null], ['rule_name' => $rule->name])
  347. ->execute();
  348. }
  349. $this->db->createCommand()
  350. ->delete($this->ruleTable, ['name' => $rule->name])
  351. ->execute();
  352. $this->invalidateCache();
  353. return true;
  354. }
  355. /**
  356. * @inheritdoc
  357. */
  358. protected function getItems($type)
  359. {
  360. $query = (new Query)
  361. ->from($this->itemTable)
  362. ->where(['type' => $type]);
  363. $items = [];
  364. foreach ($query->all($this->db) as $row) {
  365. $items[$row['name']] = $this->populateItem($row);
  366. }
  367. return $items;
  368. }
  369. /**
  370. * Populates an auth item with the data fetched from database
  371. * @param array $row the data from the auth item table
  372. * @return Item the populated auth item instance (either Role or Permission)
  373. */
  374. protected function populateItem($row)
  375. {
  376. $class = $row['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className();
  377. if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
  378. $data = null;
  379. }
  380. return new $class([
  381. 'name' => $row['name'],
  382. 'type' => $row['type'],
  383. 'description' => $row['description'],
  384. 'ruleName' => $row['rule_name'],
  385. 'data' => $data,
  386. 'createdAt' => $row['created_at'],
  387. 'updatedAt' => $row['updated_at'],
  388. ]);
  389. }
  390. /**
  391. * @inheritdoc
  392. */
  393. public function getRolesByUser($userId)
  394. {
  395. if (empty($userId)) {
  396. return [];
  397. }
  398. $query = (new Query)->select('b.*')
  399. ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
  400. ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
  401. ->andWhere(['a.user_id' => (string) $userId]);
  402. $roles = [];
  403. foreach ($query->all($this->db) as $row) {
  404. $roles[$row['name']] = $this->populateItem($row);
  405. }
  406. return $roles;
  407. }
  408. /**
  409. * @inheritdoc
  410. */
  411. public function getPermissionsByRole($roleName)
  412. {
  413. $childrenList = $this->getChildrenList();
  414. $result = [];
  415. $this->getChildrenRecursive($roleName, $childrenList, $result);
  416. if (empty($result)) {
  417. return [];
  418. }
  419. $query = (new Query)->from($this->itemTable)->where([
  420. 'type' => Item::TYPE_PERMISSION,
  421. 'name' => array_keys($result),
  422. ]);
  423. $permissions = [];
  424. foreach ($query->all($this->db) as $row) {
  425. $permissions[$row['name']] = $this->populateItem($row);
  426. }
  427. return $permissions;
  428. }
  429. /**
  430. * @inheritdoc
  431. */
  432. public function getPermissionsByUser($userId)
  433. {
  434. if (empty($userId)) {
  435. return [];
  436. }
  437. $query = (new Query)->select('item_name')
  438. ->from($this->assignmentTable)
  439. ->where(['user_id' => (string) $userId]);
  440. $childrenList = $this->getChildrenList();
  441. $result = [];
  442. foreach ($query->column($this->db) as $roleName) {
  443. $this->getChildrenRecursive($roleName, $childrenList, $result);
  444. }
  445. if (empty($result)) {
  446. return [];
  447. }
  448. $query = (new Query)->from($this->itemTable)->where([
  449. 'type' => Item::TYPE_PERMISSION,
  450. 'name' => array_keys($result),
  451. ]);
  452. $permissions = [];
  453. foreach ($query->all($this->db) as $row) {
  454. $permissions[$row['name']] = $this->populateItem($row);
  455. }
  456. return $permissions;
  457. }
  458. /**
  459. * Returns the children for every parent.
  460. * @return array the children list. Each array key is a parent item name,
  461. * and the corresponding array value is a list of child item names.
  462. */
  463. protected function getChildrenList()
  464. {
  465. $query = (new Query)->from($this->itemChildTable);
  466. $parents = [];
  467. foreach ($query->all($this->db) as $row) {
  468. $parents[$row['parent']][] = $row['child'];
  469. }
  470. return $parents;
  471. }
  472. /**
  473. * Recursively finds all children and grand children of the specified item.
  474. * @param string $name the name of the item whose children are to be looked for.
  475. * @param array $childrenList the child list built via [[getChildrenList()]]
  476. * @param array $result the children and grand children (in array keys)
  477. */
  478. protected function getChildrenRecursive($name, $childrenList, &$result)
  479. {
  480. if (isset($childrenList[$name])) {
  481. foreach ($childrenList[$name] as $child) {
  482. $result[$child] = true;
  483. $this->getChildrenRecursive($child, $childrenList, $result);
  484. }
  485. }
  486. }
  487. /**
  488. * @inheritdoc
  489. */
  490. public function getRule($name)
  491. {
  492. if ($this->rules !== null) {
  493. return isset($this->rules[$name]) ? $this->rules[$name] : null;
  494. }
  495. $row = (new Query)->select(['data'])
  496. ->from($this->ruleTable)
  497. ->where(['name' => $name])
  498. ->one($this->db);
  499. return $row === false ? null : unserialize($row['data']);
  500. }
  501. /**
  502. * @inheritdoc
  503. */
  504. public function getRules()
  505. {
  506. if ($this->rules !== null) {
  507. return $this->rules;
  508. }
  509. $query = (new Query)->from($this->ruleTable);
  510. $rules = [];
  511. foreach ($query->all($this->db) as $row) {
  512. $rules[$row['name']] = unserialize($row['data']);
  513. }
  514. return $rules;
  515. }
  516. /**
  517. * @inheritdoc
  518. */
  519. public function getAssignment($roleName, $userId)
  520. {
  521. if (empty($userId)) {
  522. return null;
  523. }
  524. $row = (new Query)->from($this->assignmentTable)
  525. ->where(['user_id' => (string) $userId, 'item_name' => $roleName])
  526. ->one($this->db);
  527. if ($row === false) {
  528. return null;
  529. }
  530. return new Assignment([
  531. 'userId' => $row['user_id'],
  532. 'roleName' => $row['item_name'],
  533. 'createdAt' => $row['created_at'],
  534. ]);
  535. }
  536. /**
  537. * @inheritdoc
  538. */
  539. public function getAssignments($userId)
  540. {
  541. if (empty($userId)) {
  542. return [];
  543. }
  544. $query = (new Query)
  545. ->from($this->assignmentTable)
  546. ->where(['user_id' => (string) $userId]);
  547. $assignments = [];
  548. foreach ($query->all($this->db) as $row) {
  549. $assignments[$row['item_name']] = new Assignment([
  550. 'userId' => $row['user_id'],
  551. 'roleName' => $row['item_name'],
  552. 'createdAt' => $row['created_at'],
  553. ]);
  554. }
  555. return $assignments;
  556. }
  557. /**
  558. * @inheritdoc
  559. */
  560. public function addChild($parent, $child)
  561. {
  562. if ($parent->name === $child->name) {
  563. throw new InvalidParamException("Cannot add '{$parent->name}' as a child of itself.");
  564. }
  565. if ($parent instanceof Permission && $child instanceof Role) {
  566. throw new InvalidParamException("Cannot add a role as a child of a permission.");
  567. }
  568. if ($this->detectLoop($parent, $child)) {
  569. throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected.");
  570. }
  571. $this->db->createCommand()
  572. ->insert($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
  573. ->execute();
  574. $this->invalidateCache();
  575. return true;
  576. }
  577. /**
  578. * @inheritdoc
  579. */
  580. public function removeChild($parent, $child)
  581. {
  582. $result = $this->db->createCommand()
  583. ->delete($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
  584. ->execute() > 0;
  585. $this->invalidateCache();
  586. return $result;
  587. }
  588. /**
  589. * @inheritdoc
  590. */
  591. public function removeChildren($parent)
  592. {
  593. $result = $this->db->createCommand()
  594. ->delete($this->itemChildTable, ['parent' => $parent->name])
  595. ->execute() > 0;
  596. $this->invalidateCache();
  597. return $result;
  598. }
  599. /**
  600. * @inheritdoc
  601. */
  602. public function hasChild($parent, $child)
  603. {
  604. return (new Query)
  605. ->from($this->itemChildTable)
  606. ->where(['parent' => $parent->name, 'child' => $child->name])
  607. ->one($this->db) !== false;
  608. }
  609. /**
  610. * @inheritdoc
  611. */
  612. public function getChildren($name)
  613. {
  614. $query = (new Query)
  615. ->select(['name', 'type', 'description', 'rule_name', 'data', 'created_at', 'updated_at'])
  616. ->from([$this->itemTable, $this->itemChildTable])
  617. ->where(['parent' => $name, 'name' => new Expression('[[child]]')]);
  618. $children = [];
  619. foreach ($query->all($this->db) as $row) {
  620. $children[$row['name']] = $this->populateItem($row);
  621. }
  622. return $children;
  623. }
  624. /**
  625. * Checks whether there is a loop in the authorization item hierarchy.
  626. * @param Item $parent the parent item
  627. * @param Item $child the child item to be added to the hierarchy
  628. * @return boolean whether a loop exists
  629. */
  630. protected function detectLoop($parent, $child)
  631. {
  632. if ($child->name === $parent->name) {
  633. return true;
  634. }
  635. foreach ($this->getChildren($child->name) as $grandchild) {
  636. if ($this->detectLoop($parent, $grandchild)) {
  637. return true;
  638. }
  639. }
  640. return false;
  641. }
  642. /**
  643. * @inheritdoc
  644. */
  645. public function assign($role, $userId)
  646. {
  647. $assignment = new Assignment([
  648. 'userId' => $userId,
  649. 'roleName' => $role->name,
  650. 'createdAt' => time(),
  651. ]);
  652. $this->db->createCommand()
  653. ->insert($this->assignmentTable, [
  654. 'user_id' => $assignment->userId,
  655. 'item_name' => $assignment->roleName,
  656. 'created_at' => $assignment->createdAt,
  657. ])->execute();
  658. return $assignment;
  659. }
  660. /**
  661. * @inheritdoc
  662. */
  663. public function revoke($role, $userId)
  664. {
  665. if (empty($userId)) {
  666. return false;
  667. }
  668. return $this->db->createCommand()
  669. ->delete($this->assignmentTable, ['user_id' => (string) $userId, 'item_name' => $role->name])
  670. ->execute() > 0;
  671. }
  672. /**
  673. * @inheritdoc
  674. */
  675. public function revokeAll($userId)
  676. {
  677. if (empty($userId)) {
  678. return false;
  679. }
  680. return $this->db->createCommand()
  681. ->delete($this->assignmentTable, ['user_id' => (string) $userId])
  682. ->execute() > 0;
  683. }
  684. /**
  685. * @inheritdoc
  686. */
  687. public function removeAll()
  688. {
  689. $this->removeAllAssignments();
  690. $this->db->createCommand()->delete($this->itemChildTable)->execute();
  691. $this->db->createCommand()->delete($this->itemTable)->execute();
  692. $this->db->createCommand()->delete($this->ruleTable)->execute();
  693. $this->invalidateCache();
  694. }
  695. /**
  696. * @inheritdoc
  697. */
  698. public function removeAllPermissions()
  699. {
  700. $this->removeAllItems(Item::TYPE_PERMISSION);
  701. }
  702. /**
  703. * @inheritdoc
  704. */
  705. public function removeAllRoles()
  706. {
  707. $this->removeAllItems(Item::TYPE_ROLE);
  708. }
  709. /**
  710. * Removes all auth items of the specified type.
  711. * @param integer $type the auth item type (either Item::TYPE_PERMISSION or Item::TYPE_ROLE)
  712. */
  713. protected function removeAllItems($type)
  714. {
  715. if (!$this->supportsCascadeUpdate()) {
  716. $names = (new Query)
  717. ->select(['name'])
  718. ->from($this->itemTable)
  719. ->where(['type' => $type])
  720. ->column($this->db);
  721. if (empty($names)) {
  722. return;
  723. }
  724. $key = $type == Item::TYPE_PERMISSION ? 'child' : 'parent';
  725. $this->db->createCommand()
  726. ->delete($this->itemChildTable, [$key => $names])
  727. ->execute();
  728. $this->db->createCommand()
  729. ->delete($this->assignmentTable, ['item_name' => $names])
  730. ->execute();
  731. }
  732. $this->db->createCommand()
  733. ->delete($this->itemTable, ['type' => $type])
  734. ->execute();
  735. $this->invalidateCache();
  736. }
  737. /**
  738. * @inheritdoc
  739. */
  740. public function removeAllRules()
  741. {
  742. if (!$this->supportsCascadeUpdate()) {
  743. $this->db->createCommand()
  744. ->update($this->itemTable, ['ruleName' => null])
  745. ->execute();
  746. }
  747. $this->db->createCommand()->delete($this->ruleTable)->execute();
  748. $this->invalidateCache();
  749. }
  750. /**
  751. * @inheritdoc
  752. */
  753. public function removeAllAssignments()
  754. {
  755. $this->db->createCommand()->delete($this->assignmentTable)->execute();
  756. }
  757. public function invalidateCache()
  758. {
  759. if ($this->cache !== null) {
  760. $this->cache->delete($this->cacheKey);
  761. $this->items = null;
  762. $this->rules = null;
  763. $this->parents = null;
  764. }
  765. }
  766. public function loadFromCache()
  767. {
  768. if ($this->items !== null || !$this->cache instanceof Cache) {
  769. return;
  770. }
  771. $data = $this->cache->get($this->cacheKey);
  772. if (is_array($data) && isset($data[0], $data[1], $data[2])) {
  773. list ($this->items, $this->rules, $this->parents) = $data;
  774. return;
  775. }
  776. $query = (new Query)->from($this->itemTable);
  777. $this->items = [];
  778. foreach ($query->all($this->db) as $row) {
  779. $this->items[$row['name']] = $this->populateItem($row);
  780. }
  781. $query = (new Query)->from($this->ruleTable);
  782. $this->rules = [];
  783. foreach ($query->all($this->db) as $row) {
  784. $this->rules[$row['name']] = unserialize($row['data']);
  785. }
  786. $query = (new Query)->from($this->itemChildTable);
  787. $this->parents = [];
  788. foreach ($query->all($this->db) as $row) {
  789. if (isset($this->items[$row['child']])) {
  790. $this->parents[$row['child']][] = $row['parent'];
  791. }
  792. }
  793. $this->cache->set($this->cacheKey, [$this->items, $this->rules, $this->parents]);
  794. }
  795. }