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