Source for file access.php

Documentation is available at access.php

  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[$assetself::getAssetRules($assettrue);
  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[$assetself::getAssetRules($assettrue);
  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($groupIdself::$userGroups))
  185.         {
  186.             return array();
  187.         }
  188.  
  189.         // Get parent groups and leaf group
  190.         if (!isset(self::$userGroupPaths[$groupId]))
  191.         {
  192.             self::$userGroupPaths[$groupIdarray();
  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. }

Documentation generated on Tue, 19 Nov 2013 14:53:15 +0100 by phpDocumentor 1.4.3