PageRenderTime 1443ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 1ms

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

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