DbManager.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. <?php
  2. namespace common\modules\rbac\components;
  3. use Yii;
  4. use yii\caching\Cache;
  5. use yii\caching\TagDependency;
  6. use yii\db\Connection;
  7. use yii\db\Query;
  8. use yii\di\Instance;
  9. use yii\rbac\Assignment;
  10. use yii\rbac\Item;
  11. use yii\rbac\Permission;
  12. use yii\rbac\Role;
  13. use yii\rbac\Rule;
  14. /**
  15. * DbManager represents an authorization manager that stores authorization information in database.
  16. *
  17. * The database connection is specified by [[$db]]. The database schema could be initialized by applying migration:
  18. *
  19. * ~~~
  20. * yii migrate --migrationPath=@yii/rbac/migrations/
  21. * ~~~
  22. *
  23. * If you don't want to use migration and need SQL instead, files for all databases are in migrations directory.
  24. *
  25. * You may change the names of the three tables used to store the authorization data by setting [[\yii\rbac\DbManager::$itemTable]],
  26. * [[\yii\rbac\DbManager::$itemChildTable]] and [[\yii\rbac\DbManager::$assignmentTable]].
  27. *
  28. * @author Misbahul D Munir <misbahuldmunir@gmail.com>
  29. *
  30. * @since 1.0
  31. */
  32. class DbManager extends \yii\rbac\DbManager
  33. {
  34. const PART_ITEMS = 'mdm.admin.items';
  35. const PART_CHILDREN = 'mdm.admin.children';
  36. const PART_RULES = 'mdm.admin.rules';
  37. /**
  38. * @var bool Enable caching
  39. */
  40. public $enableCaching = false;
  41. /**
  42. * @var string|Cache Cache component
  43. */
  44. public $cache = 'cache';
  45. /**
  46. * @var int Cache duration
  47. */
  48. public $cacheDuration = 0;
  49. /**
  50. * @var Item[]
  51. * itemName => item
  52. */
  53. private $_items;
  54. /**
  55. * @var array
  56. * itemName => childName[]
  57. */
  58. private $_children;
  59. /**
  60. * @var array
  61. * userId => itemName[]
  62. */
  63. private $_assignments = [];
  64. /**
  65. * @var Rule[]
  66. * ruleName => rule
  67. */
  68. private $_rules;
  69. /**
  70. * {@inheritdoc}
  71. */
  72. public function init()
  73. {
  74. parent::init();
  75. $this->db = Instance::ensure($this->db, Connection::className());
  76. if ($this->enableCaching) {
  77. $this->cache = Instance::ensure($this->cache, Cache::className());
  78. } else {
  79. $this->cache = null;
  80. }
  81. }
  82. /**
  83. * {@inheritdoc}
  84. */
  85. public function checkAccess($userId, $permissionName, $params = [])
  86. {
  87. $this->loadItems();
  88. $this->loadChildren();
  89. $this->loadRules();
  90. $assignments = $this->getAssignments($userId);
  91. return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments);
  92. }
  93. /**
  94. * {@inheritdoc}
  95. */
  96. public function getAssignments($userId)
  97. {
  98. $this->loadAssignments($userId);
  99. return isset($this->_assignments[$userId]) ? $this->_assignments[$userId] : [];
  100. }
  101. /**
  102. * {@inheritdoc}
  103. */
  104. protected function checkAccessRecursive($user, $itemName, $params, $assignments)
  105. {
  106. if (!isset($this->_items[$itemName])) {
  107. return false;
  108. }
  109. /** @var Item $item */
  110. $item = $this->_items[$itemName];
  111. Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission : $itemName", __METHOD__);
  112. if (!$this->executeRule($user, $item, $params)) {
  113. return false;
  114. }
  115. if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
  116. return true;
  117. }
  118. foreach ($this->_children as $parentName => $children) {
  119. if (in_array($itemName, $children) && $this->checkAccessRecursive($user, $parentName, $params, $assignments)) {
  120. return true;
  121. }
  122. }
  123. return false;
  124. }
  125. /**
  126. * {@inheritdoc}
  127. */
  128. public function addChild($parent, $child)
  129. {
  130. $this->loadItems();
  131. $this->loadChildren();
  132. parent::addChild($parent, $child);
  133. $this->_children[$parent->name][] = $child->name;
  134. $this->invalidate(self::PART_CHILDREN);
  135. return true;
  136. }
  137. /**
  138. * {@inheritdoc}
  139. */
  140. public function removeChild($parent, $child)
  141. {
  142. $result = parent::removeChild($parent, $child);
  143. if ($this->_children !== null) {
  144. $query = (new Query())
  145. ->select('child')
  146. ->from($this->itemChildTable)
  147. ->where(['parent' => $parent->name]);
  148. $this->_children[$parent->name] = $query->column($this->db);
  149. }
  150. $this->invalidate(self::PART_CHILDREN);
  151. return $result;
  152. }
  153. /**
  154. * {@inheritdoc}
  155. */
  156. public function hasChild($parent, $child)
  157. {
  158. $this->loadChildren();
  159. return isset($this->_children[$parent->name]) && in_array($child->name, $this->_children[$parent->name]);
  160. }
  161. /**
  162. * {@inheritdoc}
  163. */
  164. public function assign($role, $userId)
  165. {
  166. $assignment = parent::assign($role, $userId);
  167. if (isset($this->_assignments[$userId])) {
  168. $this->_assignments[$userId][$role->name] = $assignment;
  169. }
  170. return $assignment;
  171. }
  172. /**
  173. * {@inheritdoc}
  174. */
  175. public function revoke($role, $userId)
  176. {
  177. $result = parent::revoke($role, $userId);
  178. unset($this->_assignments[$userId]);
  179. return $result;
  180. }
  181. /**
  182. * {@inheritdoc}
  183. */
  184. public function revokeAll($userId)
  185. {
  186. if (empty($userId)) {
  187. return false;
  188. }
  189. $result = parent::revokeAll($userId);
  190. $this->_assignments[$userId] = [];
  191. return $result;
  192. }
  193. /**
  194. * {@inheritdoc}
  195. */
  196. public function getAssignment($roleName, $userId)
  197. {
  198. $this->loadItems();
  199. $this->loadAssignments($userId);
  200. if (isset($this->_assignments[$userId][$roleName], $this->_items[$roleName])) {
  201. return $this->_items[$roleName];
  202. }
  203. return;
  204. }
  205. /**
  206. * {@inheritdoc}
  207. */
  208. protected function getItems($type)
  209. {
  210. $this->loadItems();
  211. $items = [];
  212. foreach ($this->_items as $name => $item) {
  213. /** @var Item $item */
  214. if ($item->type == $type) {
  215. $items[$name] = $item;
  216. }
  217. }
  218. return $items;
  219. }
  220. /**
  221. * {@inheritdoc}
  222. */
  223. public function removeItem($item)
  224. {
  225. parent::removeItem($item);
  226. $this->_assignments = [];
  227. $this->_children = $this->_items = null;
  228. $this->invalidate([self::PART_ITEMS, self::PART_CHILDREN]);
  229. return true;
  230. }
  231. /**
  232. * {@inheritdoc}
  233. */
  234. public function getItem($name)
  235. {
  236. $this->loadItems();
  237. return isset($this->_items[$name]) ? $this->_items[$name] : null;
  238. }
  239. /**
  240. * {@inheritdoc}
  241. */
  242. public function updateRule($name, $rule)
  243. {
  244. parent::updateRule($name, $rule);
  245. if ($rule->name !== $name) {
  246. $this->_items = null;
  247. $this->invalidate(self::PART_ITEMS);
  248. }
  249. if ($this->_rules !== null) {
  250. unset($this->_rules[$name]);
  251. $this->_rules[$rule->name] = $rule;
  252. }
  253. $this->invalidate(self::PART_RULES);
  254. return true;
  255. }
  256. /**
  257. * {@inheritdoc}
  258. */
  259. public function getRule($name)
  260. {
  261. $this->loadRules();
  262. return isset($this->_rules[$name]) ? $this->_rules[$name] : null;
  263. }
  264. /**
  265. * {@inheritdoc}
  266. */
  267. public function getRules()
  268. {
  269. $this->loadRules();
  270. return $this->_rules;
  271. }
  272. /**
  273. * {@inheritdoc}
  274. */
  275. public function getRolesByUser($userId)
  276. {
  277. $this->loadItems();
  278. $roles = [];
  279. foreach ($this->getAssignments($userId) as $name => $asgn) {
  280. if (isset($this->_items[$name]) && $this->_items[$name]->type == Item::TYPE_ROLE) {
  281. $roles[$name] = $this->_items[$name];
  282. }
  283. }
  284. return $roles;
  285. }
  286. /**
  287. * {@inheritdoc}
  288. */
  289. public function getPermissionsByRole($roleName)
  290. {
  291. $childrenList = $this->getChildrenList();
  292. $result = [];
  293. $this->getChildrenRecursive($roleName, $childrenList, $result);
  294. if (empty($result)) {
  295. return [];
  296. }
  297. $this->loadItems();
  298. $permissions = [];
  299. foreach (array_keys($result) as $itemName) {
  300. if (isset($this->_items[$itemName]) && $this->_items[$itemName] instanceof Permission) {
  301. $permissions[$itemName] = $this->_items[$itemName];
  302. }
  303. }
  304. return $permissions;
  305. }
  306. /**
  307. * {@inheritdoc}
  308. */
  309. protected function getChildrenList()
  310. {
  311. $this->loadChildren();
  312. return $this->_children;
  313. }
  314. /**
  315. * {@inheritdoc}
  316. */
  317. public function getPermissionsByUser($userId)
  318. {
  319. $childrenList = $this->getChildrenList();
  320. $result = [];
  321. foreach ($this->getAssignments($userId) as $roleName => $asgn) {
  322. $this->getChildrenRecursive($roleName, $childrenList, $result);
  323. }
  324. if (empty($result)) {
  325. return [];
  326. }
  327. $this->loadItems();
  328. $permissions = [];
  329. foreach (array_keys($result) as $itemName) {
  330. if (isset($this->_items[$itemName]) && $this->_items[$itemName] instanceof Permission) {
  331. $permissions[$itemName] = $this->_items[$itemName];
  332. }
  333. }
  334. return $permissions;
  335. }
  336. /**
  337. * {@inheritdoc}
  338. */
  339. public function getChildren($name)
  340. {
  341. $this->loadItems();
  342. $this->loadChildren();
  343. $items = [];
  344. if (isset($this->_children[$name])) {
  345. foreach ($this->_children[$name] as $itemName) {
  346. $items[$itemName] = $this->_items[$itemName];
  347. }
  348. }
  349. return $items;
  350. }
  351. /**
  352. * {@inheritdoc}
  353. */
  354. public function removeAll()
  355. {
  356. $this->_children = [];
  357. $this->_items = [];
  358. $this->_assignments = [];
  359. $this->_rules = [];
  360. $this->removeAllAssignments();
  361. $this->db->createCommand()->delete($this->itemChildTable)->execute();
  362. $this->db->createCommand()->delete($this->itemTable)->execute();
  363. $this->db->createCommand()->delete($this->ruleTable)->execute();
  364. $this->invalidate([self::PART_ITEMS, self::PART_CHILDREN, self::PART_RULES]);
  365. }
  366. /**
  367. * {@inheritdoc}
  368. */
  369. protected function removeAllItems($type)
  370. {
  371. parent::removeAllItems($type);
  372. $this->_assignments = [];
  373. $this->_children = $this->_items = null;
  374. $this->invalidate([self::PART_ITEMS, self::PART_CHILDREN]);
  375. }
  376. /**
  377. * {@inheritdoc}
  378. */
  379. public function removeAllRules()
  380. {
  381. parent::removeAllRules();
  382. $this->_rules = [];
  383. $this->_items = null;
  384. $this->invalidate([self::PART_ITEMS, self::PART_RULES]);
  385. }
  386. /**
  387. * {@inheritdoc}
  388. */
  389. public function removeAllAssignments()
  390. {
  391. parent::removeAllAssignments();
  392. $this->_assignments = [];
  393. }
  394. /**
  395. * {@inheritdoc}
  396. */
  397. protected function removeRule($rule)
  398. {
  399. parent::removeRule($rule);
  400. if ($this->_rules !== null) {
  401. unset($this->_rules[$rule->name]);
  402. }
  403. $this->_items = null;
  404. $this->invalidate([self::PART_ITEMS, self::PART_RULES]);
  405. return true;
  406. }
  407. /**
  408. * {@inheritdoc}
  409. */
  410. protected function addRule($rule)
  411. {
  412. parent::addRule($rule);
  413. if ($this->_rules !== null) {
  414. $this->_rules[$rule->name] = $rule;
  415. }
  416. $this->invalidate(self::PART_RULES);
  417. return true;
  418. }
  419. /**
  420. * {@inheritdoc}
  421. */
  422. protected function updateItem($name, $item)
  423. {
  424. parent::updateItem($name, $item);
  425. if ($item->name !== $name) {
  426. $this->_assignments = [];
  427. $this->_children = null;
  428. $this->invalidate(self::PART_CHILDREN);
  429. }
  430. $this->_items = null;
  431. $this->invalidate(self::PART_RULES);
  432. return true;
  433. }
  434. /**
  435. * {@inheritdoc}
  436. */
  437. protected function addItem($item)
  438. {
  439. parent::addItem($item);
  440. if ($this->_items !== null) {
  441. $this->_items[$item->name] = $item;
  442. }
  443. $this->invalidate(self::PART_ITEMS);
  444. return true;
  445. }
  446. /**
  447. * Invalidate cache.
  448. *
  449. * @param string $parts
  450. */
  451. private function invalidate($parts)
  452. {
  453. if ($this->enableCaching) {
  454. TagDependency::invalidate($this->cache, $parts);
  455. }
  456. }
  457. /**
  458. * Build key cache.
  459. *
  460. * @param string $part
  461. *
  462. * @return mixed
  463. */
  464. private function buildKey($part)
  465. {
  466. return [__CLASS__, $part];
  467. }
  468. /**
  469. * Get data from cache.
  470. *
  471. * @param string $part
  472. *
  473. * @return mixed
  474. */
  475. private function getFromCache($part)
  476. {
  477. if ($this->enableCaching) {
  478. return $this->cache->get($this->buildKey($part));
  479. }
  480. return false;
  481. }
  482. /**
  483. * Save data to cache.
  484. *
  485. * @param string $part
  486. * @param mixed $data
  487. */
  488. private function saveToCache($part, $data)
  489. {
  490. if ($this->enableCaching) {
  491. $this->cache->set($this->buildKey($part), $data, $this->cacheDuration, new TagDependency([
  492. 'tags' => $part,
  493. ]));
  494. }
  495. }
  496. /**
  497. * Load data. If avaliable in memory, get from memory
  498. * If no, get from cache. If no avaliable, get from database.
  499. */
  500. private function loadItems()
  501. {
  502. $part = self::PART_ITEMS;
  503. if ($this->_items === null && ($this->_items = $this->getFromCache($part)) === false) {
  504. $query = (new Query())->from($this->itemTable);
  505. $this->_items = [];
  506. foreach ($query->all($this->db) as $row) {
  507. $this->_items[$row['name']] = $this->populateItem($row);
  508. }
  509. $this->saveToCache($part, $this->_items);
  510. }
  511. }
  512. /**
  513. * Load data. If avaliable in memory, get from memory
  514. * If no, get from cache. If no avaliable, get from database.
  515. */
  516. private function loadChildren()
  517. {
  518. $this->loadItems();
  519. $part = self::PART_CHILDREN;
  520. if ($this->_children === null && ($this->_children = $this->getFromCache($part)) === false) {
  521. $query = (new Query())->from($this->itemChildTable);
  522. $this->_children = [];
  523. foreach ($query->all($this->db) as $row) {
  524. if (isset($this->_items[$row['parent']], $this->_items[$row['child']])) {
  525. $this->_children[$row['parent']][] = $row['child'];
  526. }
  527. }
  528. $this->saveToCache($part, $this->_children);
  529. }
  530. }
  531. /**
  532. * Load data. If avaliable in memory, get from memory
  533. * If no, get from cache. If no avaliable, get from database.
  534. */
  535. private function loadRules()
  536. {
  537. $part = self::PART_RULES;
  538. if ($this->_rules === null && ($this->_rules = $this->getFromCache($part)) === false) {
  539. $query = (new Query())->from($this->ruleTable);
  540. $this->_rules = [];
  541. foreach ($query->all($this->db) as $row) {
  542. $rule = @unserialize($row['data']);
  543. if ($rule instanceof Rule) {
  544. $this->_rules[$row['name']] = $rule;
  545. }
  546. }
  547. $this->saveToCache($part, $this->_rules);
  548. }
  549. }
  550. /**
  551. * Load data. If avaliable in memory, get from memory
  552. * If no, get from cache. If no avaliable, get from database.
  553. */
  554. private function loadAssignments($userId)
  555. {
  556. if (!isset($this->_assignments[$userId]) && !empty($userId)) {
  557. $query = (new Query())
  558. ->from($this->assignmentTable)
  559. ->where(['user_id' => (string) $userId]);
  560. $this->_assignments[$userId] = [];
  561. foreach ($query->all($this->db) as $row) {
  562. $this->_assignments[$userId][$row['item_name']] = new Assignment([
  563. 'userId' => $row['user_id'],
  564. 'roleName' => $row['item_name'],
  565. 'createdAt' => $row['created_at'],
  566. ]);
  567. }
  568. }
  569. }
  570. /**
  571. * {@inheritdoc}
  572. */
  573. public function removeChildren($parent)
  574. {
  575. $result = parent::removeChildren($parent);
  576. if ($this->_children !== null) {
  577. unset($this->_children[$parent->name]);
  578. }
  579. $this->invalidate(self::PART_CHILDREN);
  580. return $result;
  581. }
  582. /**
  583. * Returns both roles and permissions assigned to user.
  584. *
  585. * @param integer $userId
  586. * @return array
  587. */
  588. public function getItemsByUser($userId)
  589. {
  590. if (empty($userId)) {
  591. return [];
  592. }
  593. $query = (new Query)->select('b.*')
  594. ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
  595. ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
  596. ->andWhere(['a.user_id' => (string) $userId]);
  597. $roles = [];
  598. foreach ($query->all($this->db) as $row) {
  599. $roles[$row['name']] = $this->populateItem($row);
  600. }
  601. return $roles;
  602. }
  603. }