paranoia.module in Paranoia 7
Same filename and directory in other branches
Paranoia module file. Provides various extra security features.
File
paranoia.moduleView source
<?php
/**
* @file
* Paranoia module file. Provides various extra security features.
*/
/**
* Implements hook_menu().
*/
function paranoia_menu() {
$items['admin/config/system/paranoia'] = array(
'title' => 'Paranoia',
'description' => 'Configure settings for protecting old accounts that have
not been accessed recently.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'paranoia_admin_form',
),
'access arguments' => array(
'administer paranoia',
),
'file' => 'paranoia.admin.inc',
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Implements hook_permission().
*/
function paranoia_permission() {
return array(
'administer paranoia' => array(
'title' => t('Administer paranoia'),
'description' => t('Perform administration tasks for the Paranoia module.'),
'restrict access' => TRUE,
),
);
}
/**
* Implements hook_menu_alter().
*
* Disable specific menu paths.
*/
function paranoia_menu_alter(&$items) {
$hidden_paths = module_invoke_all('paranoia_hide_paths');
foreach ($hidden_paths as $path) {
if (isset($items[$path])) {
$items[$path]['access callback'] = FALSE;
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Hide Paranoia and PHP modules from module admin form.
*/
function paranoia_form_system_modules_alter(&$form, &$form_state) {
$hidden_modules = module_invoke_all('paranoia_hide_modules');
foreach ($hidden_modules as $module => $package) {
// Unset instead of using #access because #access => FALSE shows an empty
// table row.
if (isset($form['modules'][$module])) {
unset($form['modules'][$module]);
}
// Adds support for module_filter.
if (isset($form['modules'][$package][$module])) {
unset($form['modules'][$package][$module]);
}
}
// Invoke custom validation function to disable banned modules on submit.
$form['#validate'][] = 'paranoia_module_validate';
}
/**
* Custom validation function to make sure no banned modules were enabled.
*/
function paranoia_module_validate($form, &$form_state) {
paranoia_remove_disabled_modules();
}
/**
* Disables modules based on the list in hook_paranoia_disable_modules().
*/
function paranoia_remove_disabled_modules() {
$disabled_modules = module_invoke_all('paranoia_disable_modules');
foreach ($disabled_modules as $module) {
if (module_exists($module)) {
drupal_set_message(t('The module %module has been disabled as it is not allowed on this site.', array(
'%module' => $module,
)));
module_disable(array(
$module,
));
}
}
}
/**
* Implements hook_user_update().
*/
function paranoia_user_update(&$edit, $account, $category) {
// If they are changing their password.
if (isset($account->pass) && isset($account->original) && isset($account->original->pass) && $account->original->pass != $account->pass) {
// Confirm a db based session destruction is going to work.
if (variable_get('session_inc', 'includes/session.inc') == 'includes/session.inc') {
// Destroy all sessions.
db_delete('sessions')
->condition('uid', $account->uid, '=')
->condition('sid', session_id(), '!=')
->execute();
}
else {
// If db deletion of sessions won't work, log that problem.
watchdog('paranoia', 'Tried deleting sessions after a password change, but sessions are stored in an unknown place. See <a href="https://www.drupal.org/node/2294061">this issue</a> for details.', array(), WATCHDOG_CRITICAL);
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function paranoia_form_user_profile_form_alter(&$form, &$form_state) {
if ($form_state['user']->uid === '1') {
global $user;
// Allow user/1 to edit own details.
if ($user->uid != 1) {
drupal_set_message(t('You must login as this user (user/1) to modify the name, email address, and password for this account.'), 'warning');
$form['account']['name']['#access'] = FALSE;
$form['account']['mail']['#access'] = FALSE;
$form['account']['pass']['#access'] = FALSE;
$form['account']['current_pass']['#access'] = FALSE;
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Hides permissions considered risky by hook_paranoia_hide_permissions().
*/
function paranoia_form_user_admin_permissions_alter(&$form, &$form_state) {
$banned_permissions = module_invoke_all('paranoia_hide_permissions');
foreach ($banned_permissions as $permission) {
if (isset($form['permission'][$permission])) {
$form['permission'][$permission]['#markup'] .= ' ' . t('<strong>Disabled by paranoia module.<strong>');
}
foreach ($form['checkboxes'] as $index => $elements) {
if (isset($elements['#options'][$permission])) {
$form['checkboxes'][$index][$permission]['#access'] = FALSE;
}
}
}
$form['#validate'][] = 'paranoia_permissions_validate';
$form['#submit'][] = 'paranoia_permissions_submit';
}
/**
* Form validation prevents granting permissions to untrusted roles.
*
* @see paranoia_form_user_admin_permissions_alter()
*/
function paranoia_permissions_validate($form, &$form_state) {
$permissions = module_invoke_all('permission');
foreach ($permissions as $machine_name => $attributes) {
if (!empty($attributes['restrict access'])) {
if (!empty($form_state['values'][1][$machine_name])) {
form_set_error('1][' . $machine_name, t('The permission %name can affect site security and should not be granted to anonymous users.', array(
'%name' => $attributes['title'],
)));
}
if (!empty($form_state['values'][2][$machine_name])) {
form_set_error('2][' . $machine_name, t('The permission %name can affect site security and should not be granted to authenticated users.', array(
'%name' => $attributes['title'],
)));
}
}
}
}
/**
* Helper function to remove all risky permissions from any role.
*
* Separated out from paranoia_permissions_submit so that there is
* clearly no dependency on a form or form state.
*/
function _paranoia_remove_risky_permissions() {
$banned_permissions = module_invoke_all('paranoia_hide_permissions');
foreach ($banned_permissions as $permission) {
db_query("DELETE FROM {role_permission} WHERE permission = :permission", array(
':permission' => $permission,
));
}
}
/**
* Remove extremely-risky permissions from any role.
*/
function paranoia_permissions_submit($form, &$form_state) {
_paranoia_remove_risky_permissions();
}
/**
* Implements hook_paranoia_disable_modules().
*/
function paranoia_paranoia_disable_modules() {
return array(
'php',
'computed_field',
'ds_format',
'skinr_ui',
'skinr_context_ui',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of Drupal Core.
*/
function paranoia_paranoia_hide_permissions() {
return array(
'use PHP for settings',
'use text format php_code',
);
}
/**
* Implements hook_paranoia_hide().
*/
function paranoia_paranoia_hide_modules() {
return array(
'computed_field' => 'Fields',
'ds_format' => 'Display Suite',
'php' => 'Core',
'paranoia' => 'Other',
'skinr_ui' => 'Skinr',
'skinr_context_ui' => 'Skinr',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of backup_migrate.module.
*/
function backup_migrate_paranoia_hide_permissions() {
return array(
'restore from backup',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of cck.module.
*/
function cck_paranoia_hide_permissions() {
return array(
'Use PHP input for field settings (dangerous - grant with care)',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of devel.module.
*/
function devel_paranoia_hide_permissions() {
return array(
'execute php code',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of googleanalytics.module.
*/
function googleanalytics_paranoia_hide_permissions() {
return array(
'use PHP for tracking visibility',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of bueditor.module.
*/
function bueditor_paranoia_hide_permissions() {
return array(
'administer bueditor',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of auto_nodetitle.module.
*/
function auto_nodetitle_paranoia_hide_permissions() {
return array(
'use PHP for title patterns',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of service_links.module.
*/
function service_links_paranoia_hide_permissions() {
return array(
'use PHP for service visibility',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of auto_username.module.
*/
function auto_username_paranoia_hide_permissions() {
return array(
'use PHP for username patterns',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of visual_website_optimizer.module.
*/
function visual_website_optimizer_paranoia_hide_permissions() {
return array(
'use PHP for filtering condition',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of live_person.module.
*/
function live_person_paranoia_hide_permissions() {
return array(
'use PHP for live person visibility',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of auto_entitylabel.module.
*/
function auto_entitylabel_paranoia_hide_permissions() {
return array(
'use PHP for label patterns',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of ctools.module.
*/
function ctools_paranoia_hide_permissions() {
return array(
'use ctools import',
);
}
/**
* Implements hook_paranoia_hide_permissions().
*
* On behalf of custom_breadcrumbs.module.
*/
function custom_breadcrumbs_paranoia_hide_permissions() {
return array(
'use php in custom breadcrumbs',
);
}
/**
* Implements hook_paranoia_hide_paths().
*
* On behalf of devel.module.
*/
function devel_paranoia_hide_paths() {
return array(
'devel/php',
);
}
/**
* Implements hook_paranoia_hide_paths().
*
* On behalf of views.module.
*/
function views_paranoia_hide_paths() {
return array(
'admin/structure/views/import',
);
}
/**
* Implements hook_form_alter().
*
* Hides forms that allow php arrays for importing to avoid RCE.
*
* @see http://heine.familiedeelstra.com/security/unserialize
*/
function paranoia_form_alter(&$form, &$form_state, $form_id) {
$forms_to_disable = module_invoke_all('paranoia_risky_forms');
$forms_to_disable = drupal_map_assoc($forms_to_disable);
if (array_key_exists($form_id, $forms_to_disable)) {
$form['#access'] = FALSE;
$form['#validate'][] = 'paranoia_form_validate_always_fail';
$message = variable_get('paranoia_form_disabled_message', 'This form is disabled for security reasons. See <a href="https://www.drupal.org/node/2313945">details</a> on why this form is disabled.');
drupal_set_message($message, 'error');
}
if ($form_id == "views_ui_config_item_form") {
// Block VBO's "Execute arbitrary PHP script" operation.
$form['options']['vbo_operations']['action::views_bulk_operations_script_action']['selected']['#default_value'] = FALSE;
$form['options']['vbo_operations']['action::views_bulk_operations_script_action']['selected']['#disabled'] = TRUE;
// Block Draggable Views's "Prepare arguments with PHP code" option.
unset($form['options']['draggableviews_setting_arguments']['#options']['php']);
}
// Disable Automatic Nodetitles's "Evaluate PHP in pattern" setting.
if ($form_id == "node_type_form") {
$form['auto_nodetitle']['ant_php']['#default_value'] = FALSE;
$form['auto_nodetitle']['ant_php']['#disabled'] = TRUE;
}
// Disable Custom Breadcrumbs's ability to use PHP to determine breadcrumb visibility.
if ($form_id == "custom_breadcrumbs_form") {
unset($form['visibility_php']);
}
// Disable the ability to use PHP in Views Contextual Filters.
// See https://www.drupal.org/docs/7/modules/views/views-howtos/php-contextual-filters for example use cases.
if ($form_id == "views_ui_config_item_form") {
unset($form['options']['default_argument_type']['#options']['php']);
unset($form['options']['validate']['type']['#options']['php']);
unset($form['options']['validate']['options']['php']);
}
}
/**
* Form validation that will always throw an error to prevent submits.
*/
function paranoia_form_validate_always_fail() {
$message = variable_get('paranoia_form_disabled_message', 'This form is disabled for security reasons. See <a href="https://www.drupal.org/node/2313945">details</a> on why this form is disabled.');
drupal_set_message($message, 'error');
}
/**
* Implements paranoia_risky_forms().
*
* On behalf of backup_migrate module.
*/
function backup_migrate_paranoia_risky_forms() {
return array(
'backup_migrate_crud_import_form',
'backup_migrate_ui_manual_restore_form',
);
}
/**
* Implements paranoia_risky_forms().
*
* On behalf of bundle_copy module.
*/
function bundle_copy_paranoia_risky_forms() {
return array(
'bundle_copy_import',
);
}
/**
* Implements paranoia_risky_forms().
*
* On behalf of ctools module.
*/
function ctools_paranoia_risky_forms() {
return array(
'ctools_export_ui_edit_item_wizard_form',
);
}
/**
* Implements paranoia_risky_forms().
*
* On behalf of devel module.
*/
function devel_paranoia_risky_forms() {
return array(
'devel_execute_block_form',
);
}
/**
* Implements paranoia_risky_forms().
*
* On behalf of feeds_ui module.
*/
function feeds_ui_paranoia_risky_forms() {
return array(
'feeds_ui_importer_import',
);
}
/**
* Implements paranoia_risky_forms().
*
* On behalf of flag module.
*/
function flag_paranoia_risky_forms() {
return array(
'flag_import_form',
);
}
/**
* Implements paranoia_risky_forms().
*
* On behalf of page_manager module.
*/
function page_manager_paranoia_risky_forms() {
return array(
'page_manager_page_import_subtask',
);
}
/**
* Implements paranoia_risky_forms().
*
* On behalf of relation_ui module.
*/
function relation_ui_paranoia_risky_forms() {
return array(
'relation_ui_type_import',
);
}
/**
* Implements paranoia_risky_forms().
*
* On behalf of rules_admin module.
*/
function rules_admin_paranoia_risky_forms() {
return array(
'rules_ui_import_form',
);
}
/**
* Implements paranoia_risky_forms().
*
* On behalf of views module.
*/
function views_paranoia_risky_forms() {
return [
'views_ui_import_page',
];
}
/**
* Implements hook_cron_queue_info().
*/
function paranoia_cron_queue_info() {
$queues['paranoia_stale_expirations'] = array(
'worker callback' => 'paranoia_reset_stale',
'time' => variable_get('paranoia_queue_time', 15),
);
return $queues;
}
/**
* Worker callback for the paranoia_stale_expirations queue.
*/
function paranoia_reset_stale($item) {
paranoia_reset_password_for_uid($item);
}
/**
* Resets a password on a uid and sends a mail as appropriate.
*
* @param int $uid
* A user ID that should have their password reset.
*/
function paranoia_reset_password_for_uid($uid) {
if ($account = user_load($uid)) {
// The ZZZ prefix ensures the password comparison will fail until a reset.
// See user_check_password().
$result = db_query("UPDATE {users} SET pass = CONCAT('ZZZ', SHA(CONCAT(pass, MD5(RAND())))) WHERE uid = :uid", array(
':uid' => $account->uid,
));
if ($result) {
watchdog('paranoia', 'Password randomized for @user.', array(
'@user' => $account->name,
), WATCHDOG_INFO);
if (variable_get('paranoia_email_notification', FALSE)) {
paranoia_expired_mail_send($account->uid);
}
}
else {
watchdog('paranoia', 'Failed to randomize password for uid @uid.', array(
'@uid' => $uid,
), WATCHDOG_ERROR);
}
}
}
/**
* Implements hook_mail().
*/
function paranoia_mail($key, &$message, $params) {
switch ($key) {
case 'paranoia_expired':
$account = user_load($params['uid']);
$options = array(
'langcode' => $message['language']->language,
);
$message['subject'] = t('@site-name account password randomized', array(
'@site-name' => variable_get('site_name', 'Drupal'),
), $options);
$message['body'][] = t("@name,\n\nAs a security precaution, since you have not logged in to your account for\n@threshold days, the password on the account has been randomized.", array(
'@name' => $account->name,
'@threshold' => variable_get('paranoia_access_threshold', 730),
), $options);
$message['body'][] = check_plain($params['message']);
break;
}
}
/**
* Sends an e-mail if a password has been reset.
*
* @param int $uid
* The User ID of an account to email, to notify that the account's
* password has been randomized, since the account had not been
* accessed for a long period of time (the default threshold is
* 2 years).
*/
function paranoia_expired_mail_send($uid) {
$account = user_load($uid);
$params = array(
'uid' => $account->uid,
);
// Send the mail, and check for success.
$result = drupal_mail('paranoia', 'paranoia_expired', $account->mail, language_default(), $params);
if ($result['result']) {
watchdog('paranoia', 'Notification of randomized password sent to @account.', array(
'@account' => $account->name,
), WATCHDOG_INFO);
}
else {
watchdog('paranoia', 'There was a problem sending a notification message to @account.', array(
'@account' => $account->name,
), WATCHDOG_ERROR);
}
}
Functions
Name | Description |
---|---|
auto_entitylabel_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
auto_nodetitle_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
auto_username_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
backup_migrate_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
backup_migrate_paranoia_risky_forms | Implements paranoia_risky_forms(). |
bueditor_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
bundle_copy_paranoia_risky_forms | Implements paranoia_risky_forms(). |
cck_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
ctools_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
ctools_paranoia_risky_forms | Implements paranoia_risky_forms(). |
custom_breadcrumbs_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
devel_paranoia_hide_paths | Implements hook_paranoia_hide_paths(). |
devel_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
devel_paranoia_risky_forms | Implements paranoia_risky_forms(). |
feeds_ui_paranoia_risky_forms | Implements paranoia_risky_forms(). |
flag_paranoia_risky_forms | Implements paranoia_risky_forms(). |
googleanalytics_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
live_person_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
page_manager_paranoia_risky_forms | Implements paranoia_risky_forms(). |
paranoia_cron_queue_info | Implements hook_cron_queue_info(). |
paranoia_expired_mail_send | Sends an e-mail if a password has been reset. |
paranoia_form_alter | Implements hook_form_alter(). |
paranoia_form_system_modules_alter | Implements hook_form_FORM_ID_alter(). |
paranoia_form_user_admin_permissions_alter | Implements hook_form_FORM_ID_alter(). |
paranoia_form_user_profile_form_alter | Implements hook_form_FORM_ID_alter(). |
paranoia_form_validate_always_fail | Form validation that will always throw an error to prevent submits. |
paranoia_mail | Implements hook_mail(). |
paranoia_menu | Implements hook_menu(). |
paranoia_menu_alter | Implements hook_menu_alter(). |
paranoia_module_validate | Custom validation function to make sure no banned modules were enabled. |
paranoia_paranoia_disable_modules | Implements hook_paranoia_disable_modules(). |
paranoia_paranoia_hide_modules | Implements hook_paranoia_hide(). |
paranoia_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
paranoia_permission | Implements hook_permission(). |
paranoia_permissions_submit | Remove extremely-risky permissions from any role. |
paranoia_permissions_validate | Form validation prevents granting permissions to untrusted roles. |
paranoia_remove_disabled_modules | Disables modules based on the list in hook_paranoia_disable_modules(). |
paranoia_reset_password_for_uid | Resets a password on a uid and sends a mail as appropriate. |
paranoia_reset_stale | Worker callback for the paranoia_stale_expirations queue. |
paranoia_user_update | Implements hook_user_update(). |
relation_ui_paranoia_risky_forms | Implements paranoia_risky_forms(). |
rules_admin_paranoia_risky_forms | Implements paranoia_risky_forms(). |
service_links_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
views_paranoia_hide_paths | Implements hook_paranoia_hide_paths(). |
views_paranoia_risky_forms | Implements paranoia_risky_forms(). |
visual_website_optimizer_paranoia_hide_permissions | Implements hook_paranoia_hide_permissions(). |
_paranoia_remove_risky_permissions | Helper function to remove all risky permissions from any role. |