You are here

secure_permissions.module in Secure Permissions 7.2

Same filename and directory in other branches
  1. 6 secure_permissions.module
  2. 7 secure_permissions.module

Secure Permissions module file.

This module was inspired by the Plone security paradigm of only allowing permissions to be set in code.

Inspired by @djay75 via Twitter.

File

secure_permissions.module
View source
<?php

/**
 * @file
 * Secure Permissions module file.
 *
 * This module was inspired by the Plone security paradigm
 * of only allowing permissions to be set in code.
 *
 * @see http://plone.org/products/plone/security/overview/security-overview-of-plone
 *
 * Inspired by @djay75 via Twitter.
 */

/**
 * These definitions set the security options for the module, and
 * can be reset in settings.php using $conf.
 */

// Disable forms?
define('SECURE_PERMISSIONS_DISABLE_FORMS', FALSE);

// Run the rebuild process?
define('SECURE_PERMISSIONS_ACTIVE', FALSE);

// Show the permissions page at all?
define('SECURE_PERMISSIONS_SHOW_PERMISSIONS_PAGE', TRUE);

// Show the roles page at all?
define('SECURE_PERMISSIONS_SHOW_ROLES_PAGE', TRUE);

// Display message when rebuilding permisisons?
define('SECURE_PERMISSIONS_VERBOSE', TRUE);

// Rebuild default site permissions?
define('SECURE_PERMISSIONS_USE_DEFAULT', FALSE);

/**
 * Internal variable hook.
 *
 * @param $name
 *   The name of the variable to return.
 * @return
 *   The value of the variable.
 */
function secure_permissions_variable($name) {
  if (!empty($name)) {
    return variable_get($name, constant(strtoupper($name)));
  }
}

/**
 * Implements hook_menu().
 */
function secure_permissions_menu() {
  $items = array();
  $items['admin/config/people/secure_permissions'] = array(
    'title' => 'Secure permissions',
    'description' => 'Configuration for the secure permissions module.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'secure_permissions_form',
    ),
    'access arguments' => array(
      'export secure permissions',
    ),
  );
  $items['admin/config/people/secure_permissions/view'] = array(
    'title' => 'Secure permissions',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/config/people/secure_permissions/export'] = array(
    'title' => 'Export permissions',
    'description' => 'Export site permissions for use by Secure Permissions.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'secure_permissions_export',
    ),
    'access arguments' => array(
      'export secure permissions',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 5,
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function secure_permissions_permission() {
  return array(
    'export secure permissions' => array(
      'title' => t('Export permission definitions'),
      'description' => t('Allows the export of permission settings to code, so that the permission and roles forms may be disabled.'),
    ),
  );
}

/**
 * Implements hook_menu_alter().
 *
 * If required, remove access to the permissions and roles screens.
 */
function secure_permissions_menu_alter(&$items) {

  // Permission administration pages.
  $show_roles = secure_permissions_variable('secure_permissions_show_roles_page');
  if (!$show_roles) {
    $items['admin/people/roles'] = array(
      'access callback' => 'secure_permissions_deny_access',
      'type' => MENU_CALLBACK,
    );
    $items['admin/people/roles/edit'] = array(
      'access callback' => 'secure_permissions_deny_access',
      'type' => MENU_CALLBACK,
    );
  }
  $show_permissions = secure_permissions_variable('secure_permissions_show_permissions_page');
  if (!$show_permissions) {
    $items['admin/people/permissions'] = array(
      'access callback' => 'secure_permissions_deny_access',
      'type' => MENU_CALLBACK,
    );
  }
}

/**
 * Menu access callback; always return FALSE to deny access
 * to the roles and permissions screens.
 */
function secure_permissions_deny_access() {
  return FALSE;
}

/**
 * Implements hook_modules_enabled().
 *
 * Rebuild permissions any time a module is enabled.
 */
function secure_permissions_modules_enabled($modules) {
  secure_permissions_rebuild();
}

/**
 * Implements hook_modules_disabled().
 *
 * Rebuild permissions any time a module is disabled.
 */
function secure_permissions_modules_disabled($modules) {
  secure_permissions_rebuild();
}

/**
 * Rebuild permissions, based on presets from the API.
 *
 * It is important to always call this function, instead of the individual
 * build functions, since this rebuild call sanity-checks the module settings.
 */
function secure_permissions_rebuild() {

  // Killswitch for the module, to let admins export permissions before continuing.
  // If only one module responds, it is the core module and we cannot rebuild.
  $modules = module_implements('secure_permissions');
  if (!secure_permissions_variable('secure_permissions_active') || count($modules) < 2) {
    return;
  }
  $rebuild_roles = secure_permissions_build_roles();
  $rebuild_perms = secure_permissions_build_permissions();
  if (secure_permissions_variable('secure_permissions_verbose') && $rebuild_roles && $rebuild_perms) {
    drupal_set_message(t('Site roles and permissions have been rebuilt successfully.'), 'status', FALSE);
  }
}

/**
 * Get all roles defined by the API.
 */
function secure_permissions_get_roles() {
  $roles = array_unique(module_invoke_all('secure_permissions_roles'));
  sort($roles);
  return $roles;
}

/**
 * Get all roles stored by Drupal.
 */
function secure_permissions_get_existing_roles() {

  // Function user_roles fetches translated names
  // for anonymous and authenticated roles.
  // which leads to inconsistencies in rebuilding.
  // Query it thus adapted from core user_roles.
  $query = db_select('role', 'r');
  $query
    ->fields('r', array(
    'rid',
    'name',
  ));
  $query
    ->orderBy('name');
  $result = $query
    ->execute();
  $roles = array();
  foreach ($result as $role) {
    $roles[$role->rid] = $role->name;
  }
  return $roles;
}

/**
 * Build the roles table correctly.
 */
function secure_permissions_build_roles() {

  // Get the currently defined roles for the site, and sort() them so
  // we can diff the arrays properly.
  $roles = secure_permissions_get_existing_roles();
  sort($roles);

  // Get the roles defined by this module's hook.
  $secure_roles = secure_permissions_get_roles();
  if (empty($secure_roles)) {
    return FALSE;
  }

  // Compute the difference for add/delete.
  $new_roles = array_diff($secure_roles, $roles);
  $remove_roles = array_diff($roles, $secure_roles);

  // Add new roles.
  foreach ($new_roles as $rid => $name) {
    $role = new stdClass();
    $role->name = $name;
    user_role_save($role);
  }

  // Delete old roles.
  $omit = array(
    DRUPAL_ANONYMOUS_RID,
    DRUPAL_AUTHENTICATED_RID,
  );
  $admin_rid = variable_get('user_admin_role', 0);
  if (!empty($admin_rid)) {
    $omit[] = $admin_rid;
  }
  foreach ($remove_roles as $name) {
    $role = user_role_load_by_name($name);
    if (!empty($role) && !in_array($role->rid, $omit)) {
      user_role_delete($name);
    }
  }
  return TRUE;
}

/**
 * Build function to create the permissions arrays.
 */
function secure_permissions_build_permissions() {

  // Get the active roles on the site.
  $roles = secure_permissions_get_existing_roles();
  $admin_rid = variable_get('user_admin_role', 0);

  // Do not touch the administrative role.
  if (!empty($admin_rid) && isset($roles[$admin_rid])) {
    unset($roles[$admin_rid]);
  }

  // List all permissions.
  $permissions = array_keys(module_invoke_all('permission'));

  // Now set permissions per role, using our hook.
  $permissions_rebuilt = FALSE;
  foreach ($roles as $rid => $role) {
    $perms = array();
    $new_permissions = module_invoke_all('secure_permissions', $role);

    // Change dynamically created permission values.
    $new_permissions = secure_permissions_dynamic_permissions($new_permissions);
    foreach ($permissions as $perm) {
      $perms[$perm] = FALSE;
      if (in_array($perm, $new_permissions)) {
        $perms[$perm] = TRUE;
        $permissions_rebuilt = TRUE;
      }
    }
    user_role_change_permissions($rid, $perms);
  }
  return $permissions_rebuilt;
}

/**
 * Check permissions for dynamic values and replace with consistent values.
 *
 * Right now this function can replace permissions for:
 *   - Role IDs
 *   - Taxonomy Vocabularies
 *
 * Logic has to be added for each check since database schemas differ.
 *
 * @param array $perms
 *   An array of permissions to check for dynamic values.
 */
function secure_permissions_dynamic_permissions(array $perms = array()) {

  // Get role IDs.
  $role_ids = db_select('role', 'r')
    ->fields('r', array(
    'rid',
    'name',
  ))
    ->execute()
    ->fetchAllKeyed();

  // Get vocabulary IDs.
  $vocabulary_ids = db_select('taxonomy_vocabulary', 't')
    ->fields('t', array(
    'vid',
    'machine_name',
  ))
    ->execute()
    ->fetchAllKeyed();

  // Check for role permissions and replace role names with role IDs.
  foreach ($perms as $key => $perm) {

    // Replace placeholders for Role IDs.
    $role_split = preg_split('/((edit|cancel) users with role )/', $perm, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);

    // If there is more than one part, then assume permission is role based.
    if (count($role_split) >= 2) {

      // If there second part of the string is a role ID, don't proceed.
      if (!is_numeric($role_split[2])) {
        $role_id = array_search($role_split[2], $role_ids);
        $perm_name = $role_split[0] . $role_id;
        $perms[$key] = $perm_name;
      }
    }

    // Replace placeholders for Vocabulary IDs.
    $vocab_split = preg_split('/((edit|delete) terms in )/', $perm, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);

    // If there is more than one part, then assume permission is vocabulary based.
    if (count($vocab_split) >= 2) {

      // If there second part of the string is a vocabulary ID, don't proceed.
      if (!is_numeric($vocab_split[2])) {
        $vocab_id = array_search($vocab_split[2], $vocabulary_ids);
        $perm_name = $vocab_split[0] . $vocab_id;
        $perms[$key] = $perm_name;
      }
    }
  }
  return $perms;
}

/**
 * Implements hook_secure_permissions().
 *
 * If configured to do so, this function will restore the default site
 * permissions that ship with Drupal. It will also maintain an
 * administrative role that has all permissions.
 *
 * Important: If you use this module, you must implement this
 * hook in your own code, or else risk having permissions reset.
 */
function secure_permissions_secure_permissions($role) {
  $permissions = array();

  // Use the default permissions granted by Drupal core?
  if (secure_permissions_variable('secure_permissions_use_default')) {

    // Enable default permissions for system roles. See standard.install.
    $filtered_html_format = filter_format_load('filtered_html');
    $filtered_html_permission = filter_permission_name($filtered_html_format);
    $permissions['anonymous user'] = array(
      'access content',
      $filtered_html_permission,
    );
    $permissions['authenticated user'] = array(
      'access content',
      'access comments',
      'post comments',
      'post comments without approval',
      $filtered_html_permission,
    );
  }

  // Return the permissions.
  if (isset($permissions[$role])) {
    return $permissions[$role];
  }
}

/**
 * Implements hook_secure_permissions_roles().
 *
 * Defines the roles available on a site.
 *
 * Important: If you have custom roles on your site, you must
 * implement this hook to retain those roles.
 */
function secure_permissions_secure_permissions_roles() {
  $roles = array(
    'anonymous user',
    'authenticated user',
  );
  return $roles;
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Disables editing of permissions through the user interface if configured
 * to do so.
 */
function secure_permissions_form_user_admin_permissions_alter(&$form, $form_state) {
  if (!secure_permissions_variable('secure_permissions_disable_forms')) {
    return;
  }
  foreach (element_children($form['checkboxes']) as $key) {
    $form['checkboxes'][$key]['#disabled'] = TRUE;
  }
  drupal_set_message(t('Editing of permissions is not permitted through the user interface. The table below shows the active permissions for the site.'));
  unset($form['actions']);
  unset($form['#submit']);
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Disables editing of roles through the user interface if configured to do so.
 */
function secure_permissions_form_user_admin_role_alter(&$form, $form_state) {
  if (!secure_permissions_variable('secure_permissions_disable_forms')) {
    return;
  }
  $form['name']['#disabled'] = TRUE;
  drupal_set_message(t('Editing of roles is not permitted through the user interface.'));
  unset($form['actions']);
  unset($form['#submit']);
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Disables creation of roles through the user interface if configured to do so.
 */
function secure_permissions_form_user_admin_roles_alter(&$form, $form_state) {
  if (!secure_permissions_variable('secure_permissions_disable_forms')) {
    return;
  }
  drupal_set_message(t('Editing of roles is not permitted through the user interface.'));
  unset($form['actions']);
  unset($form['add']);
  unset($form['submit']);
  unset($form['name']);
}

/**
 * Page callback to generate roles and permissions in code.
 */
function secure_permissions_export($form, $form_state) {
  $form = array();
  $form['help'] = array(
    '#markup' => t('The Secure permissions module stores the permissions in a module (file) that is inaccessible through
      the user interface.<br />You now need to create and enable that module in 4 easy steps.<ol><li>Create directory.
      cd to /sites/all/modules and issue the command: mkdir secure_permissions_data<li>Create 2 empty files. cd to
      /sites/all/modules/secure_permissions_data and issue the command: touch secure_permissions_data.info
      secure_permissions_data.module<li>Copy data. Copy the text from the fields below into the respective files you just
      created using the tools of your choice.<li>Enable the module. Navigate to admin/build/modules/list and enable your
      new module.</ol>To change permissions with the module enabled, you must now edit your
      /sites/all/modules/secure_permissions_data/secure_permissions_data.module file. After editing the file navigate to
      /admin/user/secure_permissions/view select \'Load permissions from code\' and click \'Save configuration\' to update
      the permissions. You may rename the module; remember to rename all the functions.'),
  );
  $output = '';
  $output .= "name = Secure Permissions Data\n";
  $output .= "description = Role and permission settings for the site.\n";
  $output .= "core = 7.x\n";
  $output .= "dependencies[] = secure_permissions\n";
  $output .= "files[] = secure_permissions_data.module\n";
  $lines = explode("\n", $output);
  $form['info'] = array(
    '#title' => t('Permissions output -- secure_permissions_data.info'),
    '#type' => 'textarea',
    '#cols' => 40,
    '#rows' => count($lines),
    '#default_value' => $output,
    '#description' => t('Module .info file for storing secure permissions.'),
  );
  $output = '';
  $output .= <<<EOT
<?php

/**
 * @file Secure Permissions Data
 * Module file for secure permissions in code.
 */

/**
 * Define site roles in code.
 *
 * Create a secure_permissions_data module directory and place this function
 * in secure_permissions_data.module.
 *
 * @return
 *   An array defining all the roles for the site.
 */

EOT;
  $output .= 'function secure_permissions_data_secure_permissions_roles() {';

  // Get roles.
  $roles = secure_permissions_get_existing_roles();
  $admin_rid = variable_get('user_admin_role', 0);
  $secure_permissions_ignore_in_export = array(
    variable_get('secure_permissions_ignore_in_export'),
  );

  // Do not export the administrative role.
  if (!empty($admin_rid) && isset($roles[$admin_rid])) {
    unset($roles[$admin_rid]);
  }
  $output .= "\n  return array(\n";
  foreach ($roles as $role) {
    $output .= "    '" . $role . "',\n";
  }
  $output .= "  );";
  $output .= "\n}\n\n";

  // Now get permissions.
  $output .= <<<EOT
/**
 * Define site permissions in code.
 *
 * Create a secure_permissions_data module directory and place this function
 * in secure_permissions_data.module.
 *
 * @param \$role
 *   The role for which the permissions are being requested.
 *
 * @return
 *   An array defining all the permissions for the site.
 */

EOT;
  $output .= 'function secure_permissions_data_secure_permissions($role) {';
  $output .= "\n  \$permissions = array(\n";
  foreach ($roles as $rid => $role) {
    $output .= "    '{$role}' => array(\n";
    $permissions = user_role_permissions(array(
      $rid => $role,
    ));
    foreach (current($permissions) as $permission => $value) {
      dsm($permission);
      if (!$value) {
        continue;
      }
      if (in_array($permission, $secure_permissions_ignore_in_export)) {
        continue;
      }
      $output .= "      '{$permission}',\n";
    }
    $output .= "    ),\n";
  }
  $output .= "  );\n";
  $output .= "  if (isset(\$permissions[\$role])) {";
  $output .= "\n    return \$permissions[\$role];\n";
  $output .= "  }\n";
  $output .= "}";
  $lines = explode("\n", $output);
  $form['export'] = array(
    '#title' => t('Permissions output -- secure_permissions_data.module'),
    '#type' => 'textarea',
    '#cols' => 40,
    '#rows' => count($lines),
    '#default_value' => $output,
    '#description' => t('Module .module file for storing secure permissions.'),
  );
  return $form;
}

/**
 * Configuration form for the module.
 */
function secure_permissions_form() {
  $form = array();
  $items = array();

  // Check which modules run our hook,
  $modules = module_implements('secure_permissions');
  $files = system_rebuild_module_data();
  foreach ($modules as $module) {
    if ($module != 'secure_permissions') {
      $items[] = check_plain($files[$module]->info['name']);
    }
  }
  $module_list = theme('item_list', array(
    'items' => $items,
  ));
  $extra = '';
  if (count($modules) == 1) {
    $extra = t('Your permissions have not been <a href="!url">exported to code</a> yet. You may need to do so before activating this module.', array(
      '!url' => url('admin/people/secure_permissions/export'),
    ));
  }
  $form['help'] = array(
    '#markup' => t('The following modules implement secure permissions: !list !extra', array(
      '!list' => $module_list,
      '!extra' => $extra,
    )),
  );
  $form['user_interface'] = array(
    '#type' => 'fieldset',
    '#title' => t('User interface settings'),
  );
  $form['user_interface']['secure_permissions_disable_forms'] = array(
    '#type' => 'checkbox',
    '#default_value' => secure_permissions_variable('secure_permissions_disable_forms'),
    '#title' => t('Disable permissions and roles forms'),
    '#description' => t('Disables the ability to edit or add permissions and roles through the user interface.'),
  );
  $form['user_interface']['secure_permissions_show_permissions_page'] = array(
    '#type' => 'checkbox',
    '#default_value' => secure_permissions_variable('secure_permissions_show_permissions_page'),
    '#title' => t('Show permissions page'),
    '#description' => t('Allows administrators to view the permissions overview page.'),
  );
  $form['user_interface']['secure_permissions_show_roles_page'] = array(
    '#type' => 'checkbox',
    '#default_value' => secure_permissions_variable('secure_permissions_show_roles_page'),
    '#title' => t('Show roles page'),
    '#description' => t('Allows administrators to view the roles overview page.'),
  );
  $form['user_interface']['secure_permissions_verbose'] = array(
    '#type' => 'checkbox',
    '#default_value' => secure_permissions_variable('secure_permissions_verbose'),
    '#title' => t('Display permissions updates'),
    '#description' => t('Prints a message to the screen whenever permissions are updated.'),
  );
  $form['code'] = array(
    '#type' => 'fieldset',
    '#title' => t('API settings'),
    '#description' => t('If the <em>Load permissions from code</em> setting is not enabled, none of the features below will be enabled.'),
  );
  $form['code']['secure_permissions_active'] = array(
    '#type' => 'checkbox',
    '#default_value' => secure_permissions_variable('secure_permissions_active'),
    '#title' => t('Load permissions from code'),
    '#description' => t('Allows permissions and roles to be defined in code, replacing values set through the user interface.'),
  );
  $form['code']['secure_permissions_use_default'] = array(
    '#type' => 'checkbox',
    '#default_value' => secure_permissions_variable('secure_permissions_use_default'),
    '#title' => t('Reload default permissions on rebuild'),
    '#description' => t('Sets the default Drupal permissions for anonymous and authenticated users.'),
  );
  $collapsed = variable_get('secure_permissions_ignore_in_export') ? FALSE : TRUE;
  $form['advanced_export'] = array(
    '#type' => 'fieldset',
    '#title' => t('Advanced export settings'),
    '#collapsible' => TRUE,
    '#collapsed' => $collapsed,
  );
  $form['advanced_export']['secure_permissions_ignore_in_export'] = array(
    '#type' => 'textarea',
    '#cols' => 40,
    '#rows' => 5,
    '#default_value' => variable_get('secure_permissions_ignore_in_export'),
    '#title' => t('Ignore these permissions'),
    '#description' => t('Comma seperated list of the machine names of the permissions to ignore like \'access devel information\'.'),
  );

  // Make sure the menu is rebuilt correctly.
  $form['#submit'][] = 'secure_permissions_form_submit';
  if (isset($_SESSION['secure_permissions_rebuild'])) {
    unset($_SESSION['secure_permissions_rebuild']);
    menu_rebuild();
  }
  return system_settings_form($form);
}

/**
 * We cannot rebuild the menu based on a setting during form submit,
 * at least, not without special handling.
 *
 * So set a session value to indicate we must rebuild the menus.
 *
 * Also rebuilds permissions, if necessary.
 */
function secure_permissions_form_submit($form, &$form_state) {
  global $conf;
  $modules = module_implements('secure_permissions');
  $_SESSION['secure_permissions_rebuild'] = TRUE;
  if ($form_state['values']['secure_permissions_active'] && count($modules) > 1) {

    // We must do this to pass the value to the calling function during submit.
    $conf['secure_permissions_active'] = TRUE;
    $conf['secure_permissions_use_default'] = $form_state['values']['secure_permissions_use_default'];
    secure_permissions_rebuild();
  }
  else {
    drupal_set_message(t('Permissions cannot be rebuilt from code at this time.'));
  }
}

Functions

Namesort descending Description
secure_permissions_build_permissions Build function to create the permissions arrays.
secure_permissions_build_roles Build the roles table correctly.
secure_permissions_deny_access Menu access callback; always return FALSE to deny access to the roles and permissions screens.
secure_permissions_dynamic_permissions Check permissions for dynamic values and replace with consistent values.
secure_permissions_export Page callback to generate roles and permissions in code.
secure_permissions_form Configuration form for the module.
secure_permissions_form_submit We cannot rebuild the menu based on a setting during form submit, at least, not without special handling.
secure_permissions_form_user_admin_permissions_alter Implements hook_form_FORM_ID_alter().
secure_permissions_form_user_admin_roles_alter Implements hook_form_FORM_ID_alter().
secure_permissions_form_user_admin_role_alter Implements hook_form_FORM_ID_alter().
secure_permissions_get_existing_roles Get all roles stored by Drupal.
secure_permissions_get_roles Get all roles defined by the API.
secure_permissions_menu Implements hook_menu().
secure_permissions_menu_alter Implements hook_menu_alter().
secure_permissions_modules_disabled Implements hook_modules_disabled().
secure_permissions_modules_enabled Implements hook_modules_enabled().
secure_permissions_permission Implements hook_permission().
secure_permissions_rebuild Rebuild permissions, based on presets from the API.
secure_permissions_secure_permissions Implements hook_secure_permissions().
secure_permissions_secure_permissions_roles Implements hook_secure_permissions_roles().
secure_permissions_variable Internal variable hook.

Constants