You are here

secure_permissions.module in Secure Permissions 6

Same filename and directory in other branches
  1. 7.2 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);

// Define an 'administrative user' role?
define('SECURE_PERMISSIONS_ADMINISTRATIVE_ROLE', TRUE);

// Name of the administrator role.
define('SECURE_PERMISSIONS_ROLE_NAME', 'administrator');

// 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)));
  }
}

/**
 * Implement hook_menu().
 */
function secure_permissions_menu() {
  $items = array();
  $items['admin/user/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/user/secure_permissions/view'] = array(
    'title' => 'Settings',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/user/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;
}

/**
 * Implement hook_perm().
 */
function secure_permissions_perm() {
  return array(
    'export secure permissions',
  );
}

/**
 * Implement 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/user/roles'] = array(
      'access callback' => 'secure_permissions_deny_access',
      'type' => MENU_CALLBACK,
    );
    $items['admin/user/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/user/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;
}

/**
 * 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;
}

/**
 * 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 = user_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;
    drupal_write_record('role', $role);
  }

  // Delete old roles.
  foreach ($remove_roles as $rid => $name) {

    // Never delete the default roles.
    if (!in_array($rid, array(
      DRUPAL_ANONYMOUS_RID,
      DRUPAL_AUTHENTICATED_RID,
    ))) {
      db_query("DELETE FROM {role} WHERE name = '%s'", $name);
      db_query("DELETE FROM {permission} WHERE rid = %d", $rid);
    }
  }
  return TRUE;
}

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

  // Get the active roles on the site.
  $roles = user_roles();

  // List all permissions.
  $permissions = array_values(module_invoke_all('perm'));

  // Now set permissions per role, using our hook.
  foreach ($roles as $rid => $role) {
    $new_permissions = module_invoke_all('secure_permissions', $role);
    if (empty($new_permissions)) {
      return FALSE;
    }

    // Revoke all permissions.
    db_query("DELETE FROM {permission} WHERE rid = %d", $rid);
    permissions_grant_permissions($role, $new_permissions);
  }
  return TRUE;
}

/**
 * Implement 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')) {
    $permissions['anonymous user'] = array(
      'access content',
    );
    $permissions['authenticated user'] = array(
      'access comments',
      'access content',
      'post comments',
      'post comments without approval',
    );
  }

  // Add all permissions to the administrative role?
  if ($role == secure_permissions_variable('secure_permissions_role_name') && secure_permissions_variable('secure_permissions_administrative_role')) {
    $permissions[$role] = array_values(module_invoke_all('perm'));
  }

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

/**
 * Implement 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',
  );
  if (secure_permissions_variable('secure_permissions_administrative_role')) {
    $roles[] = secure_permissions_variable('secure_permissions_role_name');
  }
  return $roles;
}

/**
 * Implement hook_form_alter().
 *
 * Disables editing of permissions through the user interface.
 */
function secure_permissions_form_user_admin_perm_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['submit']);
}

/**
 * Implement hook_form_alter().
 *
 * Disables editing of roles through the user interface.
 */
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['submit']);
  unset($form['delete']);
}

/**
 * Implement hook_form_alter().
 *
 * Disables creation of roles through the user interface.
 */
function secure_permissions_form_user_admin_new_role_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['submit']);
  unset($form['name']);
}

/**
 * Page callback to generate roles and permissions in code.
 */
function secure_permissions_export(&$form_state) {
  $form = array();
  $form['help'] = array(
    '#value' => 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 = 6.x\n";
  $output .= "dependencies[] = secure_permissions\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 = '';

  // Get roles.
  $roles = user_roles();
  $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() {';
  $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 = permissions_get_permissions_for_role($role);
    foreach ($permissions as $key => $permission) {
      $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();

  // Check which modules run our hook,
  $modules = module_implements('secure_permissions');
  $files = module_rebuild_cache();
  $items = array();
  foreach ($modules as $module) {
    if ($module != 'secure_permissions') {
      $items[] = check_plain($files[$module]->info['name']);
    }
  }
  $module_list = theme('item_list', $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/user/secure_permissions/export'),
    ));
  }
  $form['help'] = array(
    '#type' => 'markup',
    '#value' => 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.'),
  );
  $form['code']['secure_permissions_administrative_role'] = array(
    '#type' => 'checkbox',
    '#default_value' => secure_permissions_variable('secure_permissions_administrative_role'),
    '#title' => t('Use administrative role'),
    '#description' => t('Maintains an administrative role that is granted all permissions.'),
  );
  $form['code']['secure_permissions_role_name'] = array(
    '#type' => 'textfield',
    '#default_value' => secure_permissions_variable('secure_permissions_role_name'),
    '#title' => t('Administrative role name'),
    '#required' => TRUE,
    '#description' => t('The name of the administrative role. Normally this need not be changed.'),
  );

  // 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 baed 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_administrative_role'] = $form_state['values']['secure_permissions_administrative_role'];
    $conf['secure_permissions_role_name'] = $form_state['values']['secure_permissions_role_name'];
    $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.'));
  }
}

/**
 * In Drupal 6, we do not have a hook for when modules are enabled or
 * disabled, so we must add a submit to the form.
 */
function secure_permissions_form_system_modules_alter($form, &$form_state) {
  $modules = module_implements('secure_permissions');
  $form['secure_permissions'] = array(
    '#type' => 'value',
    '#value' => $modules,
  );
  $form['#submit'][] = 'secure_permissions_modules_submit';
}

/**
 * Rebuild permissions on module page submission.
 */
function secure_permissions_modules_submit($form, &$form_state) {
  $status = $form_state['values']['status'];
  $modules = $form_state['values']['secure_permissions'];
  $modules_new = module_implements('secure_permissions');

  // If module_implements is greater than the form value, a new module
  // has been activated for secure_permissions.
  $rebuild = TRUE;

  // If only the core module is enabled, do not rebuild.
  if (count($modules_new) == 1) {
    variable_set('secure_permissions_active', 0);
    drupal_set_message(t('Loading permissions from code has been disabled. You may <a href="!url">re-enable it</a>.', array(
      '!url' => url('admin/user/secure_permissions'),
    )));
    return;
  }
  foreach ($modules as $module) {
    if (empty($status[$module])) {
      $rebuild = FALSE;
    }
  }
  if ($rebuild) {
    secure_permissions_rebuild();
  }
}

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_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 baed on a setting during form submit, at least, not without special handling.
secure_permissions_form_system_modules_alter In Drupal 6, we do not have a hook for when modules are enabled or disabled, so we must add a submit to the form.
secure_permissions_form_user_admin_new_role_alter Implement hook_form_alter().
secure_permissions_form_user_admin_perm_alter Implement hook_form_alter().
secure_permissions_form_user_admin_role_alter Implement hook_form_alter().
secure_permissions_get_roles Get all roles defined by the API.
secure_permissions_menu Implement hook_menu().
secure_permissions_menu_alter Implement hook_menu_alter().
secure_permissions_modules_submit Rebuild permissions on module page submission.
secure_permissions_perm Implement hook_perm().
secure_permissions_rebuild Rebuild permissions, based on presets from the API.
secure_permissions_secure_permissions Implement hook_secure_permissions().
secure_permissions_secure_permissions_roles Implement hook_secure_permissions_roles().
secure_permissions_variable Internal variable hook.

Constants