commit 5217822d86261905bebbca2c50ed1dc5ed619a34
Author: fago <nuppla@zites.net>
Date:   Wed Aug 17 17:27:37 2011 +0200

     #1248912 fix rules-config access to properly check for edit access + improve handling access in general. Includes a small API change.

diff --git a/includes/rules.core.inc b/includes/rules.core.inc
index 0f494b7..a274217 100644
--- a/includes/rules.core.inc
+++ b/includes/rules.core.inc
@@ -656,8 +656,16 @@ abstract class RulesPlugin extends RulesExtendable {
   }
 
   /**
-   * Whether the currently logged in user has access to configure (or to change)
-   * this configuration.
+   * Whether the currently logged in user has access to all configured elements.
+   *
+   * Note that this only checks whether the current user has permission to all
+   * configured elements, but not whether a user has access to configure Rule
+   * configurations in general. Use rules_config_access() for that.
+   *
+   * Use this to determine access permissions for configuring or triggering the
+   * execution of certain configurations independent of the Rules UI.
+   *
+   * @see rules_config_access()
    */
   public function access() {
     $this->processSettings();
@@ -1325,8 +1333,9 @@ abstract class RulesAbstractPlugin extends RulesPlugin {
   }
 
   public function access() {
+    $info = $this->info();
     $this->loadBasicInclude();
-    if (!empty($this->info['access callback']) && !call_user_func($this->info['access callback'], $this->itemName, $this->getElementName())) {
+    if (!empty($info['access callback']) && !call_user_func($info['access callback'], $this->itemName, $this->getElementName())) {
       return FALSE;
     }
     return parent::access() && $this->__call('access');
@@ -1657,9 +1666,9 @@ interface RulesPluginImplInterface {
   public function validate();
 
   /**
-   * Processes $settings independent from a form submission in order to prepare
-   * element execution. Processing results may be stored and accessed on
-   * execution time in $settings.
+   * Processes settings independent from a form submission.
+   *
+   * Processing results may be stored and accessed on execution time in $settings.
    */
   public function process();
 
@@ -1675,8 +1684,13 @@ interface RulesPluginImplInterface {
   public function info_alter(&$element_info);
 
   /**
-   * Checks whether the user has access to configure this element or rule
-   * configuration.
+   * Checks whether the user has access to configure this element.
+   *
+   * Note that this only covers access for already created elements. In order to
+   * control access for creating or using elements specify an 'access callback'
+   * in the element's info array.
+   *
+   * @see hook_rules_action_info()
    */
   public function access();
 
diff --git a/modules/data.rules.inc b/modules/data.rules.inc
index 3188a78..00b3314 100644
--- a/modules/data.rules.inc
+++ b/modules/data.rules.inc
@@ -366,7 +366,7 @@ function rules_action_data_list_add_positions() {
  * Options list callback for variable add action.
  */
 function rules_data_action_variable_add_options() {
-  return RulesPluginUI::getOptions('data_info');
+  return RulesPluginUI::getOptions('data');
 }
 
 /**
diff --git a/modules/rules_core.rules.inc b/modules/rules_core.rules.inc
index 52c2733..6f00996 100644
--- a/modules/rules_core.rules.inc
+++ b/modules/rules_core.rules.inc
@@ -201,6 +201,7 @@ function rules_rules_core_condition_info() {
     'group' => t('Components'),
     'base' => 'rules_element_invoke_component',
     'named parameter' => TRUE,
+    'access callback' => 'rules_element_invoke_component_access_callback',
   );
   $items = array();
   foreach (rules_get_components(FALSE, 'condition') as $name => $config) {
@@ -224,6 +225,7 @@ function rules_rules_core_action_info() {
     'group' => t('Components'),
     'base' => 'rules_element_invoke_component',
     'named parameter' => TRUE,
+    'access callback' => 'rules_element_invoke_component_access_callback',
   );
   $items = array();
   foreach (rules_get_components(FALSE, 'action') as $name => $config) {
@@ -294,5 +296,16 @@ function rules_element_invoke_component_features_export(&$export, &$pipe, $modul
 }
 
 /**
+ * Access callback for the invoke component condition/action.
+ */
+function rules_element_invoke_component_access_callback($type, $name) {
+  // Only allow access to the action/condition if the user has access to the
+  // component.
+  // Cut of the leading 'component_' from the action name.
+  $component = rules_config_load(substr($name, 10));
+  return $component && $component->access();
+}
+
+/**
  * @}
  */
diff --git a/rules.module b/rules.module
index 91cbaa1..8111b29 100644
--- a/rules.module
+++ b/rules.module
@@ -921,10 +921,11 @@ function rules_permission() {
   return array(
     'administer rules' => array(
       'title' => t('Administer rule configurations'),
+      'description' => t('Administer rule configurations including events, conditions and actions for which the user has sufficient access permissions.'),
     ),
     'bypass rules access' => array(
       'title' => t('Bypass Rules access control'),
-      'description' => t('Use any data property, Rules action or input evaluation system regardless of permission restrictions.'),
+      'description' => t('Control all configurations regardless of permission restrictions of events, conditions or actions.'),
       'restrict access' => TRUE,
     ),
     'access rules debug' => array(
@@ -1174,12 +1175,18 @@ function rules_modules_disabled($modules) {
 }
 
 /**
- * Access callback for rules configurations.
+ * Access callback for dealing with Rules configurations.
  *
  * @see entity_access()
  */
-function rules_config_access($op, $rules_config, $account = NULL) {
-  return user_access('administer rules', $account);
+function rules_config_access($op, $rules_config = NULL, $account = NULL) {
+  if (user_access('bypass rules access', $account)) {
+    return TRUE;
+  }
+  if (!isset($rules_config) || (isset($account) && $account->uid != $GLOBALS['user']->uid)) {
+    return FALSE;
+  }
+  return user_access('administer rules', $account) && ($op == 'view' || $rules_config->access());
 }
 
 /**
diff --git a/rules_admin/rules_admin.inc b/rules_admin/rules_admin.inc
index a8a3870..f7afa9c 100644
--- a/rules_admin/rules_admin.inc
+++ b/rules_admin/rules_admin.inc
@@ -39,7 +39,7 @@ function rules_admin_reaction_overview($form, &$form_state, $base_path) {
   $form['filter']['event'] = array(
     '#type' => 'select',
     '#title' => t('Filter by event'),
-    '#options' => array(0 => t('<All>')) + RulesPluginUI::getOptions('event_info'),
+    '#options' => array(0 => t('<All>')) + RulesPluginUI::getOptions('event'),
     '#default_value' => $event,
   );
   $form['filter']['tag'] = array(
@@ -221,6 +221,7 @@ function rules_admin_add_reaction_rule($form, &$form_state, $base_path) {
   }
   // Incorporate the form to add the first event.
   $form['settings'] += rules_ui_add_event(array(), $form_state, $rules_config, $base_path);
+  $form['settings']['event']['#tree'] = FALSE;
   unset($form['settings']['help']);
 
   unset($form['settings']['submit']);
diff --git a/ui/ui.controller.inc b/ui/ui.controller.inc
index da65c09..e89c020 100644
--- a/ui/ui.controller.inc
+++ b/ui/ui.controller.inc
@@ -27,8 +27,8 @@ class RulesUIController {
       'title arguments' => array('Editing !plugin "!label"', $base_count + 1),
       'page callback' => 'drupal_get_form',
       'page arguments' => array('rules_ui_form_edit_rules_config', $base_count + 1, $base_path),
-      'access arguments' => array('administer rules'),
-      'description' => 'Manage reaction rules and rule components.',
+      'access callback' => 'rules_config_access',
+      'access arguments' => array('update', $base_count + 1),
       'type' => MENU_VISIBLE_IN_BREADCRUMB,
       'file' => 'ui/ui.forms.inc',
       'file path' => drupal_get_path('module', 'rules'),
@@ -42,7 +42,8 @@ class RulesUIController {
       'title arguments' => array(array($base_count + 4)),
       'page callback' => 'drupal_get_form',
       'page arguments' => array('rules_ui_add_element', $base_count + 1, $base_count + 4, $base_count + 3, $base_path),
-      'access arguments' => array('administer rules'),
+      'access callback' => 'rules_config_access',
+      'access arguments' => array('update', $base_count + 1),
       'load arguments' => array($base_count + 1),
       'file' => 'ui/ui.forms.inc',
       'file path' => drupal_get_path('module', 'rules'),
@@ -52,7 +53,8 @@ class RulesUIController {
       'title arguments' => array('Adding event to !plugin "!label"', $base_count + 1),
       'page callback' => 'drupal_get_form',
       'page arguments' => array('rules_ui_add_event', $base_count + 1, $base_path),
-      'access arguments' => array('administer rules'),
+      'access callback' => 'rules_config_access',
+      'access arguments' => array('update', $base_count + 1),
       'load arguments' => array($base_count + 1),
       'file' => 'ui/ui.forms.inc',
       'file path' => drupal_get_path('module', 'rules'),
@@ -62,7 +64,8 @@ class RulesUIController {
       'title' => 'Remove event',
       'page callback' => 'drupal_get_form',
       'page arguments' => array('rules_ui_remove_event', $base_count + 1, $base_count + 4, $base_path),
-      'access arguments' => array('administer rules'),
+      'access callback' => 'rules_config_access',
+      'access arguments' => array('update', $base_count + 1),
       'description' => 'Remove an event from a reaction rule.',
       'file' => 'ui/ui.forms.inc',
       'file path' => drupal_get_path('module', 'rules'),
@@ -72,7 +75,8 @@ class RulesUIController {
       'title arguments' => array('Editing !plugin "!label"', $base_count + 3),
       'page callback' => 'drupal_get_form',
       'page arguments' => array('rules_ui_edit_element', $base_count + 1, $base_count + 3, $base_path),
-      'access arguments' => array('administer rules'),
+      'access callback' => 'rules_config_access',
+      'access arguments' => array('update', $base_count + 1),
       'load arguments' => array($base_count + 1),
       'file' => 'ui/ui.forms.inc',
       'file path' => drupal_get_path('module', 'rules'),
@@ -80,7 +84,8 @@ class RulesUIController {
     $items[$base_path . '/manage/%rules_config/autocomplete'] = array(
       'page callback' => 'rules_ui_form_data_selection_auto_completion',
       'page arguments' => array($base_count + 3, $base_count + 4, $base_count + 5),
-      'access arguments' => array('administer rules'),
+      'access callback' => 'rules_config_access',
+      'access arguments' => array('update', $base_count + 1),
       'type' => MENU_CALLBACK,
       'file' => 'ui/ui.forms.inc',
       'file path' => drupal_get_path('module', 'rules'),
@@ -90,7 +95,8 @@ class RulesUIController {
       'title arguments' => array('Editing !plugin "!label"', $base_count + 3),
       'page callback' => 'drupal_get_form',
       'page arguments' => array('rules_ui_delete_element', $base_count + 1, $base_count + 3, $base_path),
-      'access arguments' => array('administer rules'),
+      'access callback' => 'rules_config_access',
+      'access arguments' => array('update', $base_count + 1),
       'load arguments' => array($base_count + 1),
       'file' => 'ui/ui.forms.inc',
       'file path' => drupal_get_path('module', 'rules'),
@@ -98,7 +104,8 @@ class RulesUIController {
     $items[$base_path . '/manage/%rules_config/%'] = array(
       'page callback' => 'drupal_get_form',
       'page arguments' => array('rules_ui_form_rules_config_confirm_op', $base_count + 1, $base_count + 2, $base_path),
-      'access arguments' => array('administer rules'),
+      'access callback' => 'rules_config_access',
+      'access arguments' => array('update', $base_count + 1),
       'file' => 'ui/ui.forms.inc',
       'file path' => drupal_get_path('module', 'rules'),
     );
@@ -107,7 +114,8 @@ class RulesUIController {
       'title arguments' => array('Cloning !plugin "!label"', $base_count + 1),
       'page callback' => 'drupal_get_form',
       'page arguments' => array('rules_ui_form_clone_rules_config', $base_count + 1, $base_path),
-      'access arguments' => array('administer rules'),
+      'access callback' => 'rules_config_access',
+      'access arguments' => array('update', $base_count + 1),
       'file' => 'ui/ui.forms.inc',
       'file path' => drupal_get_path('module', 'rules'),
     );
@@ -116,7 +124,8 @@ class RulesUIController {
       'title arguments' => array('Export of !plugin "!label"', $base_count + 1),
       'page callback' => 'drupal_get_form',
       'page arguments' => array('rules_ui_form_export_rules_config', $base_count + 1, $base_path),
-      'access arguments' => array('administer rules'),
+      'access callback' => 'rules_config_access',
+      'access arguments' => array('view', $base_count + 1),
       'file' => 'ui/ui.forms.inc',
       'file path' => drupal_get_path('module', 'rules'),
     );
@@ -125,7 +134,8 @@ class RulesUIController {
       'title arguments' => array('Executing !plugin "!label"', $base_count + 1),
       'page callback' => 'drupal_get_form',
       'page arguments' => array('rules_ui_form_execute_rules_config', $base_count + 1, $base_path),
-      'access arguments' => array('administer rules'),
+      'access callback' => 'rules_config_access',
+      'access arguments' => array('update', $base_count + 1),
       'file' => 'ui/ui.forms.inc',
       'file path' => drupal_get_path('module', 'rules'),
     );
@@ -135,7 +145,8 @@ class RulesUIController {
         'title arguments' => array('Schedule !plugin "!label"', $base_count + 1),
         'page callback' => 'drupal_get_form',
         'page arguments' => array('rules_scheduler_schedule_form', $base_count + 1, $base_path),
-        'access arguments' => array('administer rules'),
+        'access callback' => 'rules_config_access',
+        'access arguments' => array('update', $base_count + 1),
         'file' => 'rules_scheduler.admin.inc',
         'file path' => drupal_get_path('module', 'rules_scheduler'),
       );
@@ -196,7 +207,9 @@ class RulesUIController {
 
     $rows = array();
     foreach ($entities as $id => $entity) {
-      $rows[] = $this->overviewTableRow($conditions, $id, $entity, $options);
+      if (user_access('bypass rules access') || $entity->access()) {
+        $rows[] = $this->overviewTableRow($conditions, $id, $entity, $options);
+      }
     }
     // Assemble the right table header.
     $header = array(t('Name'), t('Event'), t('Plugin'), t('Status'), array('data' => t('Operations')));
diff --git a/ui/ui.core.inc b/ui/ui.core.inc
index 504ba66..735d12b 100644
--- a/ui/ui.core.inc
+++ b/ui/ui.core.inc
@@ -349,7 +349,7 @@ class RulesPluginUI extends FacesExtender implements RulesPluginUIInterface {
 
     // Make sure the current user really has access to configure this element
     // as well as the used input evaluators and data processors.
-    if (!user_access('bypass rules access') && !$this->element->access()) {
+    if (!user_access('bypass rules access') && !$this->element->root()->access()) {
       form_set_error('', t('Access violation! You have insufficient access permissions to edit this configuration.'));
     }
     try {
@@ -540,7 +540,7 @@ class RulesPluginUI extends FacesExtender implements RulesPluginUIInterface {
   public function getVariableForm($name = '', $info = array(), $provided = FALSE) {
     $form['type'] = array(
       '#type' => 'select',
-      '#options' => array(0 => '--') + RulesPluginUI::getOptions('data_info'),
+      '#options' => array(0 => '--') + RulesPluginUI::getOptions('data'),
       '#default_value' => isset($info['type']) ? $info['type'] : 0,
     );
     $form['label'] = array(
@@ -735,14 +735,24 @@ class RulesPluginUI extends FacesExtender implements RulesPluginUIInterface {
    * Returns an array of options to use with a select for the items specified
    * in the given hook.
    *
-   * @param $hook
-   *   The hook to use to fetch data, or an array of data to use.
+   * @param $item_type
+   *   The item type to get options for. One of 'data', 'event', 'condition' and
+   *   'action'.
+   * @param $items
+   *   (optional) An array of items to restrict the options to.
+   *
+   * @return
+   *   An array of options.
    */
-  public static function getOptions($hook) {
+  public static function getOptions($item_type, $items = NULL) {
     $sorted_data = array();
     $ungrouped = array();
-    $data = !is_array($hook) ? rules_fetch_data($hook) : $hook;
+    $data = $items ? $items : rules_fetch_data($item_type . '_info');
     foreach ($data as $name => $info) {
+      // Verfiy the current user has access to use it.
+      if (!empty($info['access callback']) && !call_user_func($info['access callback'], $item_type, $name)) {
+        continue;
+      }
       if (!empty($info['group'])) {
         $sorted_data[drupal_ucfirst($info['group'])][$name] = drupal_ucfirst($info['label']);
       }
diff --git a/ui/ui.forms.inc b/ui/ui.forms.inc
index eef4cb3..515b5ab 100644
--- a/ui/ui.forms.inc
+++ b/ui/ui.forms.inc
@@ -255,7 +255,7 @@ function rules_ui_add_element($form, &$form_state, $rules_config, $plugin_name,
     $form['element_name'] = array(
       '#type' => 'select',
       '#title' => t('Select the %element to add', array('%element' => $plugin_name)),
-      '#options' => RulesPluginUI::getOptions($plugin_name . '_info'),
+      '#options' => RulesPluginUI::getOptions($plugin_name),
       '#ajax' => rules_ui_form_default_ajax() + array(
         'trigger_as' => array('name' => 'continue'),
       ),
@@ -395,7 +395,7 @@ function rules_ui_add_event($form, &$form_state, RulesReactionRule $rules_config
   $form['event'] = array(
     '#type' => 'select',
     '#title' => t('React on event'),
-    '#options' => RulesPluginUI::getOptions($events),
+    '#options' => RulesPluginUI::getOptions('event', $events),
     '#description' => t('Whenever the event occurrs, rule evaluation is triggered.'),
   );
   $form['submit'] = array(
