PageRenderTime 64ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

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

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