PageRenderTime 59ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/app/protected/modules/zurmo/utils/ReadPermissionsOptimizationUtil.php

https://bitbucket.org/jim_fulford/fulford_zurmo
PHP | 994 lines | 837 code | 72 blank | 85 comment | 75 complexity | 7a51257385f5fade876b1c17f717f9ae MD5 | raw file
Possible License(s): GPL-3.0, BSD-3-Clause, LGPL-3.0
  1. <?php
  2. /*********************************************************************************
  3. * Zurmo is a customer relationship management program developed by
  4. * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
  5. *
  6. * Zurmo is free software; you can redistribute it and/or modify it under
  7. * the terms of the GNU General Public License version 3 as published by the
  8. * Free Software Foundation with the addition of the following permission added
  9. * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
  10. * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
  11. * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
  12. *
  13. * Zurmo is distributed in the hope that it will be useful, but WITHOUT
  14. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  15. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  16. * details.
  17. *
  18. * You should have received a copy of the GNU General Public License along with
  19. * this program; if not, see http://www.gnu.org/licenses or write to the Free
  20. * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  21. * 02110-1301 USA.
  22. *
  23. * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
  24. * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
  25. ********************************************************************************/
  26. abstract class ReadPermissionsOptimizationUtil
  27. {
  28. // $forcePhp is for use in tests only. So that the php version and
  29. // the optimized version can be run in succession to compare them.
  30. public static function rebuild($forcePhp = false)
  31. {
  32. assert('is_bool($forcePhp)');
  33. foreach (self::getMungableModelClassNames() as $modelClassName)
  34. {
  35. if (!SECURITY_OPTIMIZED || $forcePhp)
  36. {
  37. // The slow way will remain here as documentation
  38. // for what the optimized way is doing.
  39. $mungeTableName = self::getMungeTableName($modelClassName);
  40. self::recreateTable($mungeTableName);
  41. //Specifically call RedBeanModel to avoid the use of the security in OwnedSecurableItem since for
  42. //rebuild it needs to look at all models regardless of permissions of the current user.
  43. $modelCount = RedBeanModel::getCount(null, null, $modelClassName);
  44. $subset = intval($modelCount / 20);
  45. if ($subset < 100)
  46. {
  47. $subset = 100;
  48. }
  49. elseif ($subset > 1000)
  50. {
  51. $subset = 1000;
  52. }
  53. for ($i = 0; $i < $modelCount; $i += $subset)
  54. {
  55. //Specifically call RedBeanModel to avoid the use of the security in OwnedSecurableItem since for
  56. //rebuild it needs to look at all models regardless of permissions of the current user.
  57. $models = RedBeanModel::getSubset(null, $i, $subset, null, null, $modelClassName);
  58. foreach ($models as $model)
  59. {
  60. assert('$model instanceof SecurableItem');
  61. $securableItemId = $model->getClassId('SecurableItem');
  62. $users = User::getAll();
  63. foreach ($users as $user)
  64. {
  65. list($allowPermissions, $denyPermissions) = $model->getExplicitActualPermissions($user);
  66. $effectiveExplicitPermissions = $allowPermissions & ~$denyPermissions;
  67. if (($effectiveExplicitPermissions & Permission::READ) == Permission::READ)
  68. {
  69. self::incrementCount($mungeTableName, $securableItemId, $user);
  70. }
  71. }
  72. $groups = Group::getAll();
  73. foreach ($groups as $group)
  74. {
  75. list($allowPermissions, $denyPermissions) = $model->getExplicitActualPermissions($group);
  76. $effectiveExplicitPermissions = $allowPermissions & ~$denyPermissions;
  77. if (($effectiveExplicitPermissions & Permission::READ) == Permission::READ)
  78. {
  79. self::incrementCount($mungeTableName, $securableItemId, $group);
  80. foreach ($group->users as $user)
  81. {
  82. if ($user->role->id > 0)
  83. {
  84. self::incrementParentRolesCounts($mungeTableName, $securableItemId, $user->role);
  85. }
  86. }
  87. foreach ($group->groups as $subGroup)
  88. {
  89. self::processNestedGroupWhereParentHasReadPermissionOnSecurableItem(
  90. $mungeTableName, $securableItemId, $subGroup);
  91. }
  92. }
  93. }
  94. $roles = Role::getAll();
  95. foreach ($roles as $role)
  96. {
  97. $count = self::getRoleMungeCount($model, $role);
  98. assert('$count >= 0');
  99. if ($count > 0)
  100. {
  101. self::setCount($mungeTableName, $securableItemId, $role, $count);
  102. }
  103. }
  104. }
  105. }
  106. }
  107. else
  108. {
  109. $modelTableName = RedBeanModel::getTableName($modelClassName);
  110. $mungeTableName = self::getMungeTableName($modelClassName);
  111. ZurmoDatabaseCompatibilityUtil::
  112. callProcedureWithoutOuts("rebuild('$modelTableName', '$mungeTableName')");
  113. }
  114. }
  115. }
  116. protected static function processNestedGroupWhereParentHasReadPermissionOnSecurableItem(
  117. $mungeTableName, $securableItemId, Group $group)
  118. {
  119. assert('is_string($mungeTableName) && $mungeTableName != ""');
  120. assert('is_int($securableItemId) && $securableItemId > 0');
  121. self::incrementCount($mungeTableName, $securableItemId, $group);
  122. foreach ($group->users as $user)
  123. {
  124. if ($user->role->id > 0)
  125. {
  126. self::incrementParentRolesCounts($mungeTableName, $securableItemId, $user->role);
  127. }
  128. }
  129. foreach ($group->groups as $subGroup)
  130. {
  131. self::processNestedGroupWhereParentHasReadPermissionOnSecurableItem(
  132. $mungeTableName, $securableItemId, $subGroup);
  133. }
  134. }
  135. protected static function getRoleMungeCount(SecurableItem $securableItem, Role $role)
  136. {
  137. $count = 0;
  138. foreach ($role->roles as $subRole)
  139. {
  140. $count += self::getSubRoleMungeCount($securableItem, $subRole);
  141. }
  142. return $count;
  143. }
  144. protected static function getSubRoleMungeCount(SecurableItem $securableItem, Role $role)
  145. {
  146. $count = self::getImmediateRoleMungeCount($securableItem, $role);
  147. foreach ($role->roles as $subRole)
  148. {
  149. $count += self::getSubRoleMungeCount($securableItem, $subRole);
  150. }
  151. return $count;
  152. }
  153. protected static function getImmediateRoleMungeCount(SecurableItem $securableItem, Role $role)
  154. {
  155. $count = 0;
  156. foreach ($role->users as $user)
  157. {
  158. if ($securableItem->owner->isSame($user))
  159. {
  160. $count++;
  161. }
  162. list($allowPermissions, $denyPermissions) = $securableItem->getExplicitActualPermissions($user);
  163. $effectiveExplicitPermissions = $allowPermissions & ~$denyPermissions;
  164. if (($effectiveExplicitPermissions & Permission::READ) == Permission::READ)
  165. {
  166. $count++;
  167. }
  168. foreach ($user->groups as $group)
  169. {
  170. $count += self::getGroupMungeCount($securableItem, $group);
  171. }
  172. }
  173. return $count;
  174. }
  175. protected static function getGroupMungeCount(SecurableItem $securableItem, Group $group)
  176. {
  177. $count = 0;
  178. list($allowPermissions, $denyPermissions) = $securableItem->getExplicitActualPermissions($group);
  179. $effectiveExplicitPermissions = $allowPermissions & ~$denyPermissions;
  180. if (($effectiveExplicitPermissions & Permission::READ) == Permission::READ)
  181. {
  182. $count++;
  183. }
  184. if ($group->group->id > 0 && !(!RedBeanDatabase::isFrozen() && $group->group->isSame($group))) // Prevent cycles in database auto build.
  185. {
  186. $count += self::getGroupMungeCount($securableItem, $group->group);
  187. }
  188. return $count;
  189. }
  190. // SecurableItem create, assigned, or deleted.
  191. // Past tense implies the method must be called immediately after the associated operation.
  192. public static function ownedSecurableItemCreated(OwnedSecurableItem $ownedSecurableItem)
  193. {
  194. self::ownedSecurableItemOwnerChanged($ownedSecurableItem);
  195. }
  196. public static function ownedSecurableItemOwnerChanged(OwnedSecurableItem $ownedSecurableItem, User $oldUser = null)
  197. {
  198. $modelClassName = get_class($ownedSecurableItem);
  199. assert('$modelClassName != "OwnedSecurableItem"');
  200. $mungeTableName = self::getMungeTableName($modelClassName);
  201. if ($oldUser !== null && $oldUser->role->id > 0)
  202. {
  203. self::decrementParentRolesCounts($mungeTableName, $ownedSecurableItem->getClassId('SecurableItem'), $oldUser->role);
  204. self::garbageCollect($mungeTableName);
  205. }
  206. if ($ownedSecurableItem->owner->role->id > 0)
  207. {
  208. self::incrementParentRolesCounts($mungeTableName, $ownedSecurableItem->getClassId('SecurableItem'), $ownedSecurableItem->owner->role);
  209. }
  210. }
  211. // Being implies the the method must be called just before the associated operation.
  212. // The object is needed before the delete occurs and the delete cannot fail.
  213. public static function securableItemBeingDeleted(SecurableItem $securableItem) // Call being methods before the destructive operation.
  214. {
  215. $modelClassName = get_class($securableItem);
  216. assert('$modelClassName != "OwnedSecurableItem"');
  217. $mungeTableName = self::getMungeTableName($modelClassName);
  218. $securableItemId = $securableItem->id;
  219. R::exec("delete from $mungeTableName
  220. where securableitem_id = $securableItemId");
  221. }
  222. // Permissions added or removed.
  223. public static function securableItemGivenPermissionsForUser(SecurableItem $securableItem, User $user)
  224. {
  225. $modelClassName = get_class($securableItem);
  226. assert('$modelClassName != "OwnedSecurableItem"');
  227. $mungeTableName = self::getMungeTableName($modelClassName);
  228. $securableItemId = $securableItem->getClassId('SecurableItem');
  229. self::incrementCount($mungeTableName, $securableItemId, $user);
  230. if ($user->role->id > 0)
  231. {
  232. self::incrementParentRolesCounts($mungeTableName, $securableItemId, $user->role);
  233. }
  234. }
  235. public static function securableItemGivenPermissionsForGroup(SecurableItem $securableItem, Group $group)
  236. {
  237. $modelClassName = get_class($securableItem);
  238. assert('$modelClassName != "OwnedSecurableItem"');
  239. $mungeTableName = self::getMungeTableName($modelClassName);
  240. $securableItemId = $securableItem->getClassId('SecurableItem');
  241. self::incrementCount($mungeTableName, $securableItemId, $group);
  242. foreach ($group->users as $user)
  243. {
  244. if ($user->role->id > 0)
  245. {
  246. self::incrementParentRolesCounts($mungeTableName, $securableItemId, $user->role);
  247. }
  248. }
  249. foreach ($group->groups as $subGroup)
  250. {
  251. self::securableItemGivenPermissionsForGroup($securableItem, $subGroup);
  252. }
  253. }
  254. public static function securableItemLostPermissionsForUser(SecurableItem $securableItem, User $user)
  255. {
  256. $modelClassName = get_class($securableItem);
  257. assert('$modelClassName != "OwnedSecurableItem"');
  258. $mungeTableName = self::getMungeTableName($modelClassName);
  259. $securableItemId = $securableItem->getClassId('SecurableItem');
  260. self::decrementCount($mungeTableName, $securableItemId, $user);
  261. if ($user->role->id > 0)
  262. {
  263. self::decrementParentRolesCounts($mungeTableName, $securableItemId, $user->role);
  264. }
  265. self::garbageCollect($mungeTableName);
  266. }
  267. public static function securableItemLostPermissionsForGroup(SecurableItem $securableItem, Group $group)
  268. {
  269. $modelClassName = get_class($securableItem);
  270. assert('$modelClassName != "OwnedSecurableItem"');
  271. $mungeTableName = self::getMungeTableName($modelClassName);
  272. $securableItemId = $securableItem->getClassId('SecurableItem');
  273. self::decrementCount($mungeTableName, $securableItemId, $group);
  274. foreach ($group->users as $user)
  275. {
  276. self::securableItemLostPermissionsForUser($securableItem, $user);
  277. }
  278. foreach ($group->groups as $subGroup)
  279. {
  280. self::securableItemLostPermissionsForGroup($securableItem, $subGroup);
  281. }
  282. self::garbageCollect($mungeTableName);
  283. }
  284. // User operations.
  285. public static function userBeingDeleted($user) // Call being methods before the destructive operation.
  286. {
  287. foreach (self::getMungableModelClassNames() as $modelClassName)
  288. {
  289. $mungeTableName = self::getMungeTableName($modelClassName);
  290. if ($user->role->id > 0)
  291. {
  292. self::decrementParentRolesCountsForAllSecurableItems($mungeTableName, $user->role);
  293. self::garbageCollect($mungeTableName);
  294. }
  295. $userId = $user->id;
  296. R::exec("delete from $mungeTableName
  297. where munge_id = 'U$userId'");
  298. }
  299. }
  300. // Group operations.
  301. public static function userAddedToGroup(Group $group, User $user)
  302. {
  303. foreach (self::getMungableModelClassNames() as $modelClassName)
  304. {
  305. $mungeTableName = self::getMungeTableName($modelClassName);
  306. $groupId = $group->id;
  307. $sql = "select securableitem_id
  308. from $mungeTableName
  309. where munge_id = concat('G', $groupId)";
  310. $securableItemIds = R::getCol($sql);
  311. self::bulkIncrementParentRolesCounts($mungeTableName, $securableItemIds, $user->role);
  312. /*
  313. * This extra step is not needed. See slide 21. This is similar to userBeingRemovedFromRole in that
  314. * the above query already is trapping the information needed.
  315. Follow the same process for any upstream groups that the group is a member of.
  316. */
  317. }
  318. }
  319. public static function userRemovedFromGroup(Group $group, User $user)
  320. {
  321. foreach (self::getMungableModelClassNames() as $modelClassName)
  322. {
  323. $mungeTableName = self::getMungeTableName($modelClassName);
  324. $groupId = $group->id;
  325. $sql = "select securableitem_id
  326. from $mungeTableName
  327. where munge_id = concat('G', $groupId)";
  328. $securableItemIds = R::getCol($sql);
  329. self::bulkDecrementParentRolesCounts($mungeTableName, $securableItemIds, $user->role);
  330. /*
  331. * This extra step is not needed. See slide 22. This is similar to userBeingRemovedFromRole or
  332. * userAddedToGroup in that the above query is already trapping the information needed.
  333. Follow the same process for any upstream groups that the group is a member of.
  334. */
  335. self::garbageCollect($mungeTableName);
  336. }
  337. }
  338. public static function groupAddedToGroup(Group $group)
  339. {
  340. self::groupAddedOrRemovedFromGroup(true, $group);
  341. }
  342. public static function groupBeingRemovedFromGroup(Group $group) // Call being methods before the destructive operation.
  343. {
  344. self::groupAddedOrRemovedFromGroup(false, $group);
  345. }
  346. public static function groupBeingDeleted($group) // Call being methods before the destructive operation.
  347. {
  348. if ($group->group->id > 0 && !(!RedBeanDatabase::isFrozen() && $group->group->isSame($group))) // Prevent cycles in database auto build.
  349. {
  350. self::groupBeingRemovedFromGroup($group);
  351. }
  352. foreach ($group->groups as $childGroup)
  353. {
  354. if (!RedBeanDatabase::isFrozen() && $group->isSame($childGroup)) // Prevent cycles in database auto build.
  355. {
  356. continue;
  357. }
  358. self::groupBeingRemovedFromGroup($childGroup);
  359. }
  360. foreach ($group->users as $user)
  361. {
  362. self::userRemovedFromGroup($group, $user);
  363. }
  364. foreach (self::getMungableModelClassNames() as $modelClassName)
  365. {
  366. $groupId = $group->id;
  367. $mungeTableName = self::getMungeTableName($modelClassName);
  368. R::exec("delete from $mungeTableName
  369. where munge_id = 'G$groupId'");
  370. }
  371. }
  372. protected static function groupAddedOrRemovedFromGroup($isAdd, Group $group)
  373. {
  374. assert('is_bool($isAdd)');
  375. if (!RedBeanDatabase::isFrozen() && $group->group->isSame($group)) // Prevent cycles in database auto build.
  376. {
  377. return;
  378. }
  379. $countMethod1 = $isAdd ? 'bulkIncrementCount' : 'bulkDecrementCount';
  380. $countMethod2 = $isAdd ? 'bulkIncrementParentRolesCounts' : 'bulkDecrementParentRolesCounts';
  381. $parentGroups = self::getAllParentGroups($group);
  382. $users = self::getAllUsersInGroupAndChildGroupsRecursively($group);
  383. // Handle groups that $parentGroup is in. In/decrement for the containing groups' containing
  384. // groups the models they have explicit permissions on.
  385. // And handle user's role's parents. In/decrement for all users that have permission because
  386. // they are now in the containing group.
  387. if (count($parentGroups) > 0)
  388. {
  389. $parentGroupPermitableIds = array();
  390. foreach ($parentGroups as $parentGroup)
  391. {
  392. $parentGroupPermitableIds[] = $parentGroup->getClassId('Permitable');
  393. }
  394. $sql = 'select securableitem_id
  395. from permission
  396. where permitable_id in (' . join(', ', $parentGroupPermitableIds) . ')';
  397. $securableItemIds = R::getCol($sql);
  398. foreach (self::getMungableModelClassNames() as $modelClassName)
  399. {
  400. $mungeTableName = self::getMungeTableName($modelClassName);
  401. self::$countMethod1($mungeTableName, $securableItemIds, $group);
  402. foreach ($users as $user)
  403. {
  404. if ($user->role->id > 0)
  405. {
  406. self::$countMethod2($mungeTableName, $securableItemIds, $user->role);
  407. }
  408. }
  409. }
  410. }
  411. if (!$isAdd)
  412. {
  413. foreach (self::getMungableModelClassNames() as $modelClassName)
  414. {
  415. $mungeTableName = self::getMungeTableName($modelClassName);
  416. self::garbageCollect($mungeTableName);
  417. }
  418. }
  419. }
  420. protected static function getAllUsersInGroupAndChildGroupsRecursively(Group $group)
  421. {
  422. $users = array();
  423. foreach ($group->users as $user)
  424. {
  425. $users[] = $user;
  426. }
  427. foreach ($group->groups as $childGroup)
  428. {
  429. if (!RedBeanDatabase::isFrozen() && $group->isSame($childGroup)) // Prevent cycles in database auto build.
  430. {
  431. continue;
  432. }
  433. $users = array_merge($users, self::getAllUsersInGroupAndChildGroupsRecursively($childGroup));
  434. }
  435. return $users;
  436. }
  437. protected static function getAllParentGroups(Group $group)
  438. {
  439. $parentGroups = array();
  440. $parentGroup = $group->group;
  441. while ($parentGroup->id > 0 && !(!RedBeanDatabase::isFrozen() && $parentGroup->isSame($parentGroup->group))) // Prevent cycles in database auto build.
  442. {
  443. $parentGroups[] = $parentGroup;
  444. $parentGroup = $parentGroup->group;
  445. }
  446. return $parentGroups;
  447. }
  448. // Role operations.
  449. public static function roleParentSet(Role $role)
  450. {
  451. assert('$role->role->id > 0');
  452. self::roleParentSetOrRemoved(true, $role);
  453. }
  454. public static function roleParentBeingRemoved(Role $role) // Call being methods before the destructive operation.
  455. {
  456. assert('$role->role->id > 0');
  457. self::roleParentSetOrRemoved(false, $role);
  458. }
  459. public static function roleBeingDeleted(Role $role) // Call being methods before the destructive operation.
  460. {
  461. foreach (self::getMungableModelClassNames() as $modelClassName)
  462. {
  463. if ($role->role->id > 0)
  464. {
  465. self::roleParentBeingRemoved($role);
  466. }
  467. foreach ($role->roles as $childRole)
  468. {
  469. if ($childRole->role->id > 0)
  470. {
  471. self::roleParentBeingRemoved($childRole);
  472. }
  473. }
  474. $mungeTableName = self::getMungeTableName($modelClassName);
  475. $roleId = $role->id;
  476. $sql = "delete from $mungeTableName
  477. where munge_id = 'R$roleId'";
  478. R::exec($sql);
  479. }
  480. }
  481. protected static function roleParentSetOrRemoved($isSet, Role $role)
  482. {
  483. assert('is_bool($isSet)');
  484. if (!RedBeanDatabase::isFrozen() && $role->role->isSame($role)) // Prevent cycles in database auto build.
  485. {
  486. return;
  487. }
  488. $countMethod = $isSet ? 'bulkIncrementParentRolesCounts' : 'bulkDecrementParentRolesCounts';
  489. foreach (self::getMungableModelClassNames() as $modelClassName)
  490. {
  491. $mungeTableName = self::getMungeTableName($modelClassName);
  492. $usersInRolesChildren = self::getAllUsersInRolesChildRolesRecursively($role);
  493. // Handle users in $role. In/decrement for the parent's parent
  494. // roles the models they either own or have explicit permissions on.
  495. if (count($role->users) > 0)
  496. {
  497. $userIds = array();
  498. $permitableIds = array();
  499. foreach ($role->users as $user)
  500. {
  501. $userIds[] = $user->id;
  502. $permitableIds[] = $user->getClassId('Permitable');
  503. }
  504. $sql = 'select securableitem_id
  505. from ownedsecurableitem
  506. where owner__user_id in (' . join(', ', $userIds) . ')
  507. union all
  508. select securableitem_id
  509. from permission
  510. where permitable_id in (' . join(', ', $permitableIds) . ')';
  511. $securableItemIds = R::getCol($sql);
  512. self::$countMethod($mungeTableName, $securableItemIds, $role->role);
  513. }
  514. // Handle users in the child roles of $role. Increment for the parent's parent
  515. // roles the models they either own or have explicit permissions on.
  516. if (count($usersInRolesChildren))
  517. {
  518. $userIds = array();
  519. $permitableIds = array();
  520. foreach ($usersInRolesChildren as $user)
  521. {
  522. $userIds[] = $user->id;
  523. $permitableIds[] = $user->getClassId('Permitable');
  524. }
  525. $sql = 'select securableitem_id
  526. from ownedsecurableitem
  527. where owner__user_id in (' . join(', ', $userIds) . ')
  528. union all
  529. select securableitem_id
  530. from permission
  531. where permitable_id in (' . join(', ', $permitableIds) . ')';
  532. $securableItemIds = R::getCol($sql);
  533. self::$countMethod($mungeTableName, $securableItemIds, $role);
  534. }
  535. // Handle groups for the users in $role. Increment for the parent's parent
  536. // roles the models they have explicit permissions on.
  537. if (count($role->users) > 0)
  538. {
  539. $permitableIds = array();
  540. foreach ($role->users as $user)
  541. {
  542. foreach ($user->groups as $group)
  543. {
  544. $permitableIds[] = $group->getClassId('Permitable');
  545. }
  546. }
  547. $permitableIds = array_unique($permitableIds);
  548. $sql = 'select securableitem_id
  549. from permission
  550. where permitable_id in (' . join(', ', $permitableIds) . ')';
  551. $securableItemIds = R::getCol($sql);
  552. self::$countMethod($mungeTableName, $securableItemIds, $role->role);
  553. }
  554. // Handle groups for the users $role's child roles. Increment for the role's parent
  555. // roles the models they have explicit permissions on.
  556. if (count($usersInRolesChildren))
  557. {
  558. $permitableIds = array();
  559. foreach ($usersInRolesChildren as $user)
  560. {
  561. foreach ($user->groups as $group)
  562. {
  563. $permitableIds[] = $group->getClassId('Permitable');
  564. }
  565. }
  566. $permitableIds = array_unique($permitableIds);
  567. if (count($permitableIds) > 0)
  568. {
  569. $sql = 'select securableitem_id
  570. from permission
  571. where permitable_id in (' . join(', ', $permitableIds) . ')';
  572. $securableItemIds = R::getCol($sql);
  573. }
  574. else
  575. {
  576. $securableItemIds = array();
  577. }
  578. self::$countMethod($mungeTableName, $securableItemIds, $role);
  579. }
  580. if (!$isSet)
  581. {
  582. self::garbageCollect($mungeTableName);
  583. }
  584. }
  585. }
  586. protected static function getAllUsersInRolesChildRolesRecursively(Role $role)
  587. {
  588. $users = array();
  589. foreach ($role->roles as $childRole)
  590. {
  591. if (!RedBeanDatabase::isFrozen() && $role->isSame($childRole)) // Prevent cycles in database auto build.
  592. {
  593. continue;
  594. }
  595. foreach ($childRole->users as $user)
  596. {
  597. $users[] = $user;
  598. }
  599. $users = array_merge($users, self::getAllUsersInRolesChildRolesRecursively($childRole));
  600. }
  601. return $users;
  602. }
  603. public static function userAddedToRole(User $user)
  604. {
  605. assert('$user->role->id > 0');
  606. foreach (self::getMungableModelClassNames() as $modelClassName)
  607. {
  608. $mungeTableName = self::getMungeTableName($modelClassName);
  609. $userId = $user->id;
  610. $sql = "select securableitem_id
  611. from ownedsecurableitem
  612. where owner__user_id = $userId";
  613. $securableItemIds = R::getCol($sql);
  614. //Increment the parent roles for securableItems that the user is the owner on.
  615. self::bulkIncrementParentRolesCounts($mungeTableName, $securableItemIds, $user->role);
  616. //Get all downstream groups the user is in including any groups that are in those groups recursively.
  617. //Then for each group found, add weight for the user's upstream roles.
  618. $groupMungeIds = array();
  619. foreach ($user->groups as $group)
  620. {
  621. $groupMungeIds[] = 'G' . $group->id;
  622. self::getAllUpstreamGroupsRecursively($group, $groupMungeIds);
  623. }
  624. if (count($groupMungeIds) > 0)
  625. {
  626. $inSqlPart = SQLOperatorUtil::resolveOperatorAndValueForOneOf('oneOf', $groupMungeIds, true);
  627. $sql = "select distinct $mungeTableName.securableitem_id
  628. from $mungeTableName
  629. where $mungeTableName.munge_id $inSqlPart";
  630. $securableItemIds = R::getCol($sql);
  631. self::bulkIncrementParentRolesCounts($mungeTableName, $securableItemIds, $user->role);
  632. }
  633. }
  634. }
  635. public static function userBeingRemovedFromRole(User $user, Role $role)
  636. {
  637. foreach (self::getMungableModelClassNames() as $modelClassName)
  638. {
  639. $mungeTableName = self::getMungeTableName($modelClassName);
  640. $userId = $user->id;
  641. $sql = "select securableitem_id
  642. from ownedsecurableitem
  643. where owner__user_id = $userId";
  644. $securableItemIds = R::getCol($sql);
  645. self::bulkDecrementParentRolesCounts($mungeTableName, $securableItemIds, $role);
  646. $sql = "select $mungeTableName.securableitem_id
  647. from $mungeTableName, _group__user
  648. where $mungeTableName.munge_id = concat('G', _group__user._group_id) and
  649. _group__user._user_id = $userId";
  650. $securableItemIds = R::getCol($sql);
  651. self::bulkDecrementParentRolesCounts($mungeTableName, $securableItemIds, $role);
  652. /*
  653. * This additional step I don't think is needed because the sql query above actually traps
  654. * the upstream explicit securableItems because the lower level groups will already have a point for
  655. * each of them.
  656. What groups are the user part of and what groups are those groups children of recursively?
  657. For any models that have that group explicity for read, subtract 1 point for the user's
  658. upstream roles from the disconnected role.
  659. */
  660. self::garbageCollect($mungeTableName);
  661. }
  662. }
  663. ///////////////////////////////////////////////////////////////////////
  664. public static function getAllUpstreamGroupsRecursively(Group $group, & $groupMungeIds)
  665. {
  666. assert('is_array($groupMungeIds)');
  667. if ($group->group->id > 0 )
  668. {
  669. $groupMungeIds[] = 'G' . $group->group->id;
  670. if (!RedBeanDatabase::isFrozen() && $group->isSame($group->group))
  671. {
  672. //Do Nothing. Prevent cycles in database auto build.
  673. }
  674. else
  675. {
  676. self::getAllUpstreamGroupsRecursively($group->group, $groupMungeIds);
  677. }
  678. }
  679. }
  680. public static function getUserRoleIdAndGroupIds(User $user)
  681. {
  682. if ($user->role->id > 0)
  683. {
  684. $roleId = $user->role->id;
  685. }
  686. else
  687. {
  688. $roleId = null;
  689. }
  690. $groupIds = array();
  691. foreach ($user->groups as $group)
  692. {
  693. $groupIds[] = $group->id;
  694. }
  695. return array($roleId, $groupIds);
  696. }
  697. public static function getMungeIdsByUser(User $user)
  698. {
  699. list($roleId, $groupIds) = self::getUserRoleIdAndGroupIds($user);
  700. $mungeIds = array("U$user->id");
  701. if ($roleId != null)
  702. {
  703. $mungeIds[] = "R$roleId";
  704. }
  705. foreach ($groupIds as $groupId)
  706. {
  707. $mungeIds[] = "G$groupId";
  708. }
  709. //Add everyone group
  710. $everyoneGroupId = Group::getByName(Group::EVERYONE_GROUP_NAME)->id;
  711. if (!in_array("G" . $everyoneGroupId, $mungeIds) && $everyoneGroupId > 0)
  712. {
  713. $mungeIds[] = "G" . $everyoneGroupId;
  714. }
  715. return $mungeIds;
  716. }
  717. /**
  718. * Public for testing only. Need to manually create test model tables that would not be picked up normally.
  719. */
  720. public static function recreateTable($mungeTableName)
  721. {
  722. assert('is_string($mungeTableName) && $mungeTableName != ""');
  723. R::exec("drop table if exists $mungeTableName");
  724. R::exec("create table $mungeTableName (
  725. securableitem_id int(11) unsigned not null,
  726. munge_id varchar(12) not null,
  727. count int(8) unsigned not null,
  728. primary key (securableitem_id, munge_id)
  729. )");
  730. R::exec("create index index_${mungeTableName}_securable_item_id
  731. on $mungeTableName (securableitem_id);");
  732. }
  733. protected static function incrementCount($mungeTableName, $securableItemId, $item)
  734. {
  735. assert('is_string($mungeTableName) && $mungeTableName != ""');
  736. assert('is_int($securableItemId) && $securableItemId > 0');
  737. assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
  738. $itemId = $item->id;
  739. $type = self::getMungeType($item);
  740. $mungeId = "$type$itemId";
  741. R::exec("insert into $mungeTableName
  742. (securableitem_id, munge_id, count)
  743. values ($securableItemId, '$mungeId', 1)
  744. on duplicate key
  745. update count = count + 1");
  746. }
  747. protected static function setCount($mungeTableName, $securableItemId, $item, $count)
  748. {
  749. assert('is_string($mungeTableName) && $mungeTableName != ""');
  750. assert('is_int($securableItemId) && $securableItemId > 0');
  751. assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
  752. $itemId = $item->id;
  753. $type = self::getMungeType($item);
  754. $mungeId = "$type$itemId";
  755. R::exec("insert into $mungeTableName
  756. (securableitem_id, munge_id, count)
  757. values ($securableItemId, '$mungeId', $count)
  758. on duplicate key
  759. update count = $count");
  760. }
  761. protected static function decrementCount($mungeTableName, $securableItemId, $item)
  762. {
  763. assert('is_string($mungeTableName) && $mungeTableName != ""');
  764. assert('is_int($securableItemId) && $securableItemId > 0');
  765. assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
  766. $itemId = $item->id;
  767. $type = self::getMungeType($item);
  768. $mungeId = "$type$itemId";
  769. R::exec("update $mungeTableName
  770. set count = count - 1
  771. where securableitem_id = $securableItemId and
  772. munge_id = '$mungeId'");
  773. }
  774. protected static function decrementCountForAllSecurableItems($mungeTableName, $item)
  775. {
  776. assert('is_string($mungeTableName) && $mungeTableName != ""');
  777. assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
  778. $itemId = $item->id;
  779. $type = self::getMungeType($item);
  780. $mungeId = "$type$itemId";
  781. R::exec("update $mungeTableName
  782. set count = count - 1
  783. where munge_id = '$mungeId'");
  784. }
  785. protected static function bulkIncrementCount($mungeTableName, $securableItemIds, $item)
  786. {
  787. assert('is_string($mungeTableName) && $mungeTableName != ""');
  788. assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
  789. foreach ($securableItemIds as $securableItemId)
  790. {
  791. self::incrementCount($mungeTableName, intval($securableItemId), $item);
  792. }
  793. }
  794. protected static function bulkDecrementCount($mungeTableName, $securableItemIds, $item)
  795. {
  796. assert('is_string($mungeTableName) && $mungeTableName != ""');
  797. assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
  798. foreach ($securableItemIds as $securableItemId)
  799. {
  800. self::decrementCount($mungeTableName, intval($securableItemId), $item);
  801. }
  802. }
  803. protected static function incrementParentRolesCounts($mungeTableName, $securableItemId, Role $role)
  804. {
  805. assert('is_string($mungeTableName) && $mungeTableName != ""');
  806. assert('is_int($securableItemId) && $securableItemId > 0');
  807. if (!RedBeanDatabase::isFrozen() && $role->role->isSame($role)) // Prevent cycles in database auto build.
  808. {
  809. return;
  810. }
  811. if ($role->role->id > 0)
  812. {
  813. self::incrementCount ($mungeTableName, $securableItemId, $role->role);
  814. self::incrementParentRolesCounts($mungeTableName, $securableItemId, $role->role);
  815. }
  816. }
  817. protected static function decrementParentRolesCounts($mungeTableName, $securableItemId, Role $role)
  818. {
  819. assert('is_string($mungeTableName) && $mungeTableName != ""');
  820. assert('is_int($securableItemId) && $securableItemId > 0');
  821. if (!RedBeanDatabase::isFrozen() && $role->role->isSame($role)) // Prevent cycles in database auto build.
  822. {
  823. return;
  824. }
  825. if ($role->role->id > 0)
  826. {
  827. self::decrementCount ($mungeTableName, $securableItemId, $role->role);
  828. self::decrementParentRolesCounts($mungeTableName, $securableItemId, $role->role);
  829. }
  830. }
  831. protected static function decrementParentRolesCountsForAllSecurableItems($mungeTableName, Role $role)
  832. {
  833. assert('is_string($mungeTableName) && $mungeTableName != ""');
  834. if (!RedBeanDatabase::isFrozen() && $role->role->isSame($role)) // Prevent cycles in database auto build.
  835. {
  836. return;
  837. }
  838. if ($role->role->id > 0)
  839. {
  840. self::decrementCountForAllSecurableItems ($mungeTableName, $role->role);
  841. self::decrementParentRolesCountsForAllSecurableItems($mungeTableName, $role->role);
  842. }
  843. }
  844. protected static function bulkIncrementParentRolesCounts($mungeTableName, $securableItemIds, Role $role)
  845. {
  846. foreach ($securableItemIds as $securableItemId)
  847. {
  848. self::incrementParentRolesCounts($mungeTableName, intval($securableItemId), $role);
  849. }
  850. }
  851. protected static function bulkDecrementParentRolesCounts($mungeTableName, $securableItemIds, Role $role)
  852. {
  853. foreach ($securableItemIds as $securableItemId)
  854. {
  855. self::decrementParentRolesCounts($mungeTableName, intval($securableItemId), $role);
  856. }
  857. }
  858. // This must be called ny any public method which decrements
  859. // counts after it has done all its count decrementing.
  860. // It is not done in decrementCount to avoid doing it more
  861. // than is necessary.
  862. protected static function garbageCollect($mungeTableName)
  863. {
  864. assert("R::getCell('select count(*)
  865. from $mungeTableName
  866. where count < 0') == 0");
  867. R::exec("delete from $mungeTableName
  868. where count = 0");
  869. assert("R::getCell('select count(*)
  870. from $mungeTableName
  871. where count < 1') == 0");
  872. }
  873. protected static function getMungeType($item)
  874. {
  875. assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
  876. return substr(get_class($item), 0, 1);
  877. }
  878. //public for testing only
  879. public static function getMungableModelClassNames()
  880. {
  881. try
  882. {
  883. return GeneralCache::getEntry('mungableModelClassNames');
  884. }
  885. catch (NotFoundException $e)
  886. {
  887. $mungableClassNames = self::findMungableModelClassNames();
  888. GeneralCache::cacheEntry('mungableModelClassNames', $mungableClassNames);
  889. return $mungableClassNames;
  890. }
  891. }
  892. //public for testing only.
  893. public static function findMungableModelClassNames()
  894. {
  895. $mungableModelClassNames = array();
  896. $modules = Module::getModuleObjects();
  897. foreach ($modules as $module)
  898. {
  899. $modelClassNames = $module::getModelClassNames();
  900. foreach ($modelClassNames as $modelClassName)
  901. {
  902. if (is_subclass_of($modelClassName, 'SecurableItem') &&
  903. $modelClassName::hasReadPermissionsOptimization())
  904. {
  905. $mungableModelClassNames[] = $modelClassName;
  906. }
  907. }
  908. }
  909. return $mungableModelClassNames;
  910. }
  911. protected static function getMainTableName($modelClassName)
  912. {
  913. assert('is_string($modelClassName) && $modelClassName != ""');
  914. return RedBeanModel::getTableName($modelClassName);
  915. }
  916. public static function getMungeTableName($modelClassName)
  917. {
  918. assert('is_string($modelClassName) && $modelClassName != ""');
  919. return self::getMainTableName($modelClassName) . '_read';
  920. }
  921. }
  922. ?>