PageRenderTime 39ms CodeModel.GetById 8ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/joomla/access/access.php

http://github.com/joomla/joomla-platform
PHP | 538 lines | 275 code | 67 blank | 196 comment | 31 complexity | 6e7105292b22a6a6c3ac031aa754f123 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * @package Joomla.Platform
  4. * @subpackage Access
  5. *
  6. * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  7. * @license GNU General Public License version 2 or later; see LICENSE
  8. */
  9. defined('JPATH_PLATFORM') or die;
  10. jimport('joomla.utilities.arrayhelper');
  11. /**
  12. * Class that handles all access authorisation routines.
  13. *
  14. * @package Joomla.Platform
  15. * @subpackage Access
  16. * @since 11.1
  17. */
  18. class JAccess
  19. {
  20. /**
  21. * Array of view levels
  22. *
  23. * @var array
  24. * @since 11.1
  25. */
  26. protected static $viewLevels = array();
  27. /**
  28. * Array of rules for the asset
  29. *
  30. * @var array
  31. * @since 11.1
  32. */
  33. protected static $assetRules = array();
  34. /**
  35. * Array of user groups.
  36. *
  37. * @var array
  38. * @since 11.1
  39. */
  40. protected static $userGroups = array();
  41. /**
  42. * Array of user group paths.
  43. *
  44. * @var array
  45. * @since 11.1
  46. */
  47. protected static $userGroupPaths = array();
  48. /**
  49. * Array of cached groups by user.
  50. *
  51. * @var array
  52. * @since 11.1
  53. */
  54. protected static $groupsByUser = array();
  55. /**
  56. * Method for clearing static caches.
  57. *
  58. * @return void
  59. *
  60. * @since 11.3
  61. */
  62. public static function clearStatics()
  63. {
  64. self::$viewLevels = array();
  65. self::$assetRules = array();
  66. self::$userGroups = array();
  67. self::$userGroupPaths = array();
  68. self::$groupsByUser = array();
  69. }
  70. /**
  71. * Method to check if a user is authorised to perform an action, optionally on an asset.
  72. *
  73. * @param integer $userId Id of the user for which to check authorisation.
  74. * @param string $action The name of the action to authorise.
  75. * @param mixed $asset Integer asset id or the name of the asset as a string. Defaults to the global asset node.
  76. *
  77. * @return boolean True if authorised.
  78. *
  79. * @since 11.1
  80. */
  81. public static function check($userId, $action, $asset = null)
  82. {
  83. // Sanitise inputs.
  84. $userId = (int) $userId;
  85. $action = strtolower(preg_replace('#[\s\-]+#', '.', trim($action)));
  86. $asset = strtolower(preg_replace('#[\s\-]+#', '.', trim($asset)));
  87. // Default to the root asset node.
  88. if (empty($asset))
  89. {
  90. $db = JFactory::getDbo();
  91. $assets = JTable::getInstance('Asset', 'JTable', array('dbo' => $db));
  92. $rootId = $assets->getRootId();
  93. $asset = $rootId;
  94. }
  95. // Get the rules for the asset recursively to root if not already retrieved.
  96. if (empty(self::$assetRules[$asset]))
  97. {
  98. self::$assetRules[$asset] = self::getAssetRules($asset, true);
  99. }
  100. // Get all groups against which the user is mapped.
  101. $identities = self::getGroupsByUser($userId);
  102. array_unshift($identities, $userId * -1);
  103. return self::$assetRules[$asset]->allow($action, $identities);
  104. }
  105. /**
  106. * Method to check if a group is authorised to perform an action, optionally on an asset.
  107. *
  108. * @param integer $groupId The path to the group for which to check authorisation.
  109. * @param string $action The name of the action to authorise.
  110. * @param mixed $asset Integer asset id or the name of the asset as a string. Defaults to the global asset node.
  111. *
  112. * @return boolean True if authorised.
  113. *
  114. * @since 11.1
  115. */
  116. public static function checkGroup($groupId, $action, $asset = null)
  117. {
  118. // Sanitize inputs.
  119. $groupId = (int) $groupId;
  120. $action = strtolower(preg_replace('#[\s\-]+#', '.', trim($action)));
  121. $asset = strtolower(preg_replace('#[\s\-]+#', '.', trim($asset)));
  122. // Get group path for group
  123. $groupPath = self::getGroupPath($groupId);
  124. // Default to the root asset node.
  125. if (empty($asset))
  126. {
  127. $db = JFactory::getDbo();
  128. $assets = JTable::getInstance('Asset', 'JTable', array('dbo' => $db));
  129. $rootId = $assets->getRootId();
  130. }
  131. // Get the rules for the asset recursively to root if not already retrieved.
  132. if (empty(self::$assetRules[$asset]))
  133. {
  134. self::$assetRules[$asset] = self::getAssetRules($asset, true);
  135. }
  136. return self::$assetRules[$asset]->allow($action, $groupPath);
  137. }
  138. /**
  139. * Gets the parent groups that a leaf group belongs to in its branch back to the root of the tree
  140. * (including the leaf group id).
  141. *
  142. * @param mixed $groupId An integer or array of integers representing the identities to check.
  143. *
  144. * @return mixed True if allowed, false for an explicit deny, null for an implicit deny.
  145. *
  146. * @since 11.1
  147. */
  148. protected static function getGroupPath($groupId)
  149. {
  150. // Preload all groups
  151. if (empty(self::$userGroups))
  152. {
  153. $db = JFactory::getDbo();
  154. $query = $db->getQuery(true)
  155. ->select('parent.id, parent.lft, parent.rgt')
  156. ->from('#__usergroups AS parent')
  157. ->order('parent.lft');
  158. $db->setQuery($query);
  159. self::$userGroups = $db->loadObjectList('id');
  160. }
  161. // Make sure groupId is valid
  162. if (!array_key_exists($groupId, self::$userGroups))
  163. {
  164. return array();
  165. }
  166. // Get parent groups and leaf group
  167. if (!isset(self::$userGroupPaths[$groupId]))
  168. {
  169. self::$userGroupPaths[$groupId] = array();
  170. foreach (self::$userGroups as $group)
  171. {
  172. if ($group->lft <= self::$userGroups[$groupId]->lft && $group->rgt >= self::$userGroups[$groupId]->rgt)
  173. {
  174. self::$userGroupPaths[$groupId][] = $group->id;
  175. }
  176. }
  177. }
  178. return self::$userGroupPaths[$groupId];
  179. }
  180. /**
  181. * Method to return the JAccessRules object for an asset. The returned object can optionally hold
  182. * only the rules explicitly set for the asset or the summation of all inherited rules from
  183. * parent assets and explicit rules.
  184. *
  185. * @param mixed $asset Integer asset id or the name of the asset as a string.
  186. * @param boolean $recursive True to return the rules object with inherited rules.
  187. *
  188. * @return JAccessRules JAccessRules object for the asset.
  189. *
  190. * @since 11.1
  191. */
  192. public static function getAssetRules($asset, $recursive = false)
  193. {
  194. // Get the database connection object.
  195. $db = JFactory::getDbo();
  196. // Build the database query to get the rules for the asset.
  197. $query = $db->getQuery(true);
  198. $query->select($recursive ? 'b.rules' : 'a.rules');
  199. $query->from('#__assets AS a');
  200. // SQLsrv change
  201. $query->group($recursive ? 'b.id, b.rules, b.lft' : 'a.id, a.rules, a.lft');
  202. // If the asset identifier is numeric assume it is a primary key, else lookup by name.
  203. if (is_numeric($asset))
  204. {
  205. $query->where('(a.id = ' . (int) $asset . ')');
  206. }
  207. else
  208. {
  209. $query->where('(a.name = ' . $db->quote($asset) . ')');
  210. }
  211. // If we want the rules cascading up to the global asset node we need a self-join.
  212. if ($recursive)
  213. {
  214. $query->leftJoin('#__assets AS b ON b.lft <= a.lft AND b.rgt >= a.rgt');
  215. $query->order('b.lft');
  216. }
  217. // Execute the query and load the rules from the result.
  218. $db->setQuery($query);
  219. $result = $db->loadColumn();
  220. // Get the root even if the asset is not found and in recursive mode
  221. if (empty($result))
  222. {
  223. $db = JFactory::getDbo();
  224. $assets = JTable::getInstance('Asset', 'JTable', array('dbo' => $db));
  225. $rootId = $assets->getRootId();
  226. $query = $db->getQuery(true);
  227. $query->select('rules');
  228. $query->from('#__assets');
  229. $query->where('id = ' . $db->quote($rootId));
  230. $db->setQuery($query);
  231. $result = $db->loadResult();
  232. $result = array($result);
  233. }
  234. // Instantiate and return the JAccessRules object for the asset rules.
  235. $rules = new JAccessRules;
  236. $rules->mergeCollection($result);
  237. return $rules;
  238. }
  239. /**
  240. * Method to return a list of user groups mapped to a user. The returned list can optionally hold
  241. * only the groups explicitly mapped to the user or all groups both explicitly mapped and inherited
  242. * by the user.
  243. *
  244. * @param integer $userId Id of the user for which to get the list of groups.
  245. * @param boolean $recursive True to include inherited user groups.
  246. *
  247. * @return array List of user group ids to which the user is mapped.
  248. *
  249. * @since 11.1
  250. */
  251. public static function getGroupsByUser($userId, $recursive = true)
  252. {
  253. // Creates a simple unique string for each parameter combination:
  254. $storeId = $userId . ':' . (int) $recursive;
  255. if (!isset(self::$groupsByUser[$storeId]))
  256. {
  257. // TODO: Uncouple this from JComponentHelper and allow for a configuration setting or value injection.
  258. if (class_exists('JComponentHelper'))
  259. {
  260. $guestUsergroup = JComponentHelper::getParams('com_users')->get('guest_usergroup', 1);
  261. }
  262. else
  263. {
  264. $guestUsergroup = 1;
  265. }
  266. // Guest user (if only the actually assigned group is requested)
  267. if (empty($userId) && !$recursive)
  268. {
  269. $result = array($guestUsergroup);
  270. }
  271. // Registered user and guest if all groups are requested
  272. else
  273. {
  274. $db = JFactory::getDbo();
  275. // Build the database query to get the rules for the asset.
  276. $query = $db->getQuery(true);
  277. $query->select($recursive ? 'b.id' : 'a.id');
  278. if (empty($userId))
  279. {
  280. $query->from('#__usergroups AS a');
  281. $query->where('a.id = ' . (int) $guestUsergroup);
  282. }
  283. else
  284. {
  285. $query->from('#__user_usergroup_map AS map');
  286. $query->where('map.user_id = ' . (int) $userId);
  287. $query->leftJoin('#__usergroups AS a ON a.id = map.group_id');
  288. }
  289. // If we want the rules cascading up to the global asset node we need a self-join.
  290. if ($recursive)
  291. {
  292. $query->leftJoin('#__usergroups AS b ON b.lft <= a.lft AND b.rgt >= a.rgt');
  293. }
  294. // Execute the query and load the rules from the result.
  295. $db->setQuery($query);
  296. $result = $db->loadColumn();
  297. // Clean up any NULL or duplicate values, just in case
  298. JArrayHelper::toInteger($result);
  299. if (empty($result))
  300. {
  301. $result = array('1');
  302. }
  303. else
  304. {
  305. $result = array_unique($result);
  306. }
  307. }
  308. self::$groupsByUser[$storeId] = $result;
  309. }
  310. return self::$groupsByUser[$storeId];
  311. }
  312. /**
  313. * Method to return a list of user Ids contained in a Group
  314. *
  315. * @param integer $groupId The group Id
  316. * @param boolean $recursive Recursively include all child groups (optional)
  317. *
  318. * @return array
  319. *
  320. * @since 11.1
  321. * @todo This method should move somewhere else
  322. */
  323. public static function getUsersByGroup($groupId, $recursive = false)
  324. {
  325. // Get a database object.
  326. $db = JFactory::getDbo();
  327. $test = $recursive ? '>=' : '=';
  328. // First find the users contained in the group
  329. $query = $db->getQuery(true);
  330. $query->select('DISTINCT(user_id)');
  331. $query->from('#__usergroups as ug1');
  332. $query->join('INNER', '#__usergroups AS ug2 ON ug2.lft' . $test . 'ug1.lft AND ug1.rgt' . $test . 'ug2.rgt');
  333. $query->join('INNER', '#__user_usergroup_map AS m ON ug2.id=m.group_id');
  334. $query->where('ug1.id=' . $db->Quote($groupId));
  335. $db->setQuery($query);
  336. $result = $db->loadColumn();
  337. // Clean up any NULL values, just in case
  338. JArrayHelper::toInteger($result);
  339. return $result;
  340. }
  341. /**
  342. * Method to return a list of view levels for which the user is authorised.
  343. *
  344. * @param integer $userId Id of the user for which to get the list of authorised view levels.
  345. *
  346. * @return array List of view levels for which the user is authorised.
  347. *
  348. * @since 11.1
  349. */
  350. public static function getAuthorisedViewLevels($userId)
  351. {
  352. // Get all groups that the user is mapped to recursively.
  353. $groups = self::getGroupsByUser($userId);
  354. // Only load the view levels once.
  355. if (empty(self::$viewLevels))
  356. {
  357. // Get a database object.
  358. $db = JFactory::getDBO();
  359. // Build the base query.
  360. $query = $db->getQuery(true);
  361. $query->select('id, rules');
  362. $query->from($query->qn('#__viewlevels'));
  363. // Set the query for execution.
  364. $db->setQuery((string) $query);
  365. // Build the view levels array.
  366. foreach ($db->loadAssocList() as $level)
  367. {
  368. self::$viewLevels[$level['id']] = (array) json_decode($level['rules']);
  369. }
  370. }
  371. // Initialise the authorised array.
  372. $authorised = array(1);
  373. // Find the authorised levels.
  374. foreach (self::$viewLevels as $level => $rule)
  375. {
  376. foreach ($rule as $id)
  377. {
  378. if (($id < 0) && (($id * -1) == $userId))
  379. {
  380. $authorised[] = $level;
  381. break;
  382. }
  383. // Check to see if the group is mapped to the level.
  384. elseif (($id >= 0) && in_array($id, $groups))
  385. {
  386. $authorised[] = $level;
  387. break;
  388. }
  389. }
  390. }
  391. return $authorised;
  392. }
  393. /**
  394. * Method to return a list of actions from a file for which permissions can be set.
  395. *
  396. * @param string $file The path to the XML file.
  397. * @param string $xpath An optional xpath to search for the fields.
  398. *
  399. * @return boolean|array False if case of error or the list of actions available.
  400. *
  401. * @since 12.1
  402. */
  403. public static function getActionsFromFile($file, $xpath = "/access/section[@name='component']/")
  404. {
  405. if (!is_file($file) || !is_readable($file))
  406. {
  407. // If unable to find the file return false.
  408. return false;
  409. }
  410. else
  411. {
  412. // Else return the actions from the xml.
  413. $xml = simplexml_load_file($file);
  414. return self::getActionsFromData($xml, $xpath);
  415. }
  416. }
  417. /**
  418. * Method to return a list of actions from a string or from an xml for which permissions can be set.
  419. *
  420. * @param string|SimpleXMLElement $data The XML string or an XML element.
  421. * @param string $xpath An optional xpath to search for the fields.
  422. *
  423. * @return boolean|array False if case of error or the list of actions available.
  424. *
  425. * @since 12.1
  426. */
  427. public static function getActionsFromData($data, $xpath = "/access/section[@name='component']/")
  428. {
  429. // If the data to load isn't already an XML element or string return false.
  430. if ((!($data instanceof SimpleXMLElement)) && (!is_string($data)))
  431. {
  432. return false;
  433. }
  434. // Attempt to load the XML if a string.
  435. if (is_string($data))
  436. {
  437. try
  438. {
  439. $data = new SimpleXMLElement($data);
  440. }
  441. catch (Exception $e)
  442. {
  443. return false;
  444. }
  445. // Make sure the XML loaded correctly.
  446. if (!$data)
  447. {
  448. return false;
  449. }
  450. }
  451. // Initialise the actions array
  452. $actions = array();
  453. // Get the elements from the xpath
  454. $elements = $data->xpath($xpath . 'action[@name][@title][@description]');
  455. // If there some elements, analyse them
  456. if (!empty($elements))
  457. {
  458. foreach ($elements as $action)
  459. {
  460. // Add the action to the actions array
  461. $actions[] = (object) array(
  462. 'name' => (string) $action['name'],
  463. 'title' => (string) $action['title'],
  464. 'description' => (string) $action['description']
  465. );
  466. }
  467. }
  468. // Finally return the actions array
  469. return $actions;
  470. }
  471. }