diff --git a/masquerade.info b/masquerade.info
--- a/masquerade.info Tue Jun 21 17:30:07 2011
+++ b/masquerade.info Tue Jun 21 17:30:15 2011
@@ -2,4 +2,5 @@
description = "This module allows permitted users to masquerade as other users."
core = 7.x
files[] = masquerade.test
+files[] = views/masquerade_handler_field_user_link.inc
configure = admin/config/people/masquerade
diff --git a/masquerade.module b/masquerade.module
--- a/masquerade.module Wed May 04 08:58:00 2011
+++ b/masquerade.module Tue Jun 21 17:34:26 2011
@@ -23,15 +22,23 @@
* @return array
*/
function masquerade_permission() {
- return array(
- 'masquerade as user' => array(
- 'title' => t('Masquerade as user'),
- 'description' => t('Masquerade as another user.'),
- ),
- 'masquerade as admin' => array(
- 'title' => t('Masquerade as admin'),
- 'description' => t('Masquerade as the site admin (UID 1).'),
- ),
+ $roles = array();
+ // load and build permissions for all roles in our site
+ // filter off the Anonymous role.
+ foreach (user_roles() as $rid => $role) {
+ $title = 'masquerade as '. $role;
+ $key = drupal_strtolower($title);
+ $description = t('Masquerade as a member of the @role role.', array('@role' => $role));
+ if ($rid == DRUPAL_AUTHENTICATED_RID) {
+ $description = t('Grant this permission with caution. Any user signed in is given this role.');
+ }
+ $roles[$title] = array(
+ 'title' => t($title),
+ 'description' => $description,
+ );
+ }
+
+ return $roles + array(
'administer masquerade' => array(
'title' => t('Administer Masquerade'),
'description' => t('Perform administration tasks and configure the Masquerade module.'),
@@ -188,7 +194,7 @@
function masquerade_user_operations() {
return array(
'masquerade' => array(
- 'label' => t('Masquerade as user'),
+ 'label' => t('Masquerade as this user'),
'callback' => 'masquerade_user_operations_masquerade',
),
);
@@ -223,7 +229,7 @@
case 'unswitch':
return isset($_SESSION['masquerading']) || arg(2) == 'menu-customize' || arg(2) == 'menu';
case 'autocomplete':
- return isset($_SESSION['masquerading']) || (user_access('masquerade as user') || user_access('masquerade as admin'));
+ return isset($_SESSION['masquerading']) || masquerade_has_any_masquerade_permission();
break;
case 'user':
global $user;
@@ -232,7 +238,6 @@
case 'switch':
$switch_to_account = FALSE;
global $user;
- if ($uid) {
if (!is_numeric($uid)) {
return FALSE;
}
@@ -241,8 +246,11 @@
':uid_from' => $user->uid,
':uid_to' => $account->uid
))->fetchField();
+ // ensure the user we want to switch to has only roles we're permitted to access.
+ if (!masquerade_check_user_roles($account)) {
+ return FALSE;
}
- return !isset($_SESSION['masquerading']) && (user_access('masquerade as user') || user_access('masquerade as admin') || $switch_to_account);
+ return !isset($_SESSION['masquerading']) || $switch_to_account;
break;
}
}
@@ -252,25 +260,6 @@
*/
function masquerade_admin_settings() {
// create a list of roles; all selected roles are considered administrative.
- $roles = user_roles();
- $form['masquerade_admin_roles'] = array(
- '#type' => 'checkboxes',
- '#title' => t('Roles that are considered "administrators" for masquerading'),
- '#options' => $roles,
- '#default_value' => variable_get('masquerade_admin_roles', array()),
- );
-
- $test_name = _masquerade_user_load(variable_get('masquerade_test_user', ''));
-
- $form['masquerade_test_user'] = array(
- '#type' => 'textfield',
- '#title' => t('Menu Quick Switch user'),
- '#autocomplete_path' => 'masquerade/autocomplete',
- '#default_value' => isset($test_name->name) ? check_plain($test_name->name) : '',
- '#description' => t('Enter the username of an account you wish to switch easily between via a menu item.'),
- '#maxlength' => NULL,
- );
-
$quick_switch = user_load_multiple(variable_get('masquerade_quick_switches', array()));
$quick_switch_users = array();
foreach ($quick_switch as $uid => $account) {
@@ -296,15 +285,6 @@
}
function masquerade_admin_settings_validate($form, &$form_state) {
- if (!empty($form_state['values']['masquerade_test_user'])) {
- $test_user = _masquerade_user_load($form_state['values']['masquerade_test_user']);
- if (!$test_user) {
- form_set_error('masquerade_test_user', t('%user does not exist. Please enter a valid username.', array('%user' => $form_state['values']['masquerade_test_user'])));
- }
- }
- // Needs to rebuild menu in masquerade_admin_settings_submit().
- $form_state['masquerade_rebuild_menu'] = (variable_get('masquerade_test_user', '') != $form_state['values']['masquerade_test_user']);
-
// A comma-separated list of users.
$masquerade_switches = drupal_explode_tags($form_state['values']['masquerade_quick_switches']);
// Change user names to user ID's for system_settings_form_submit() to save.
@@ -373,15 +353,16 @@
* Implements hook_user_view().
*/
function masquerade_user_view($account, $view_mode, $langcode) {
- // check if user qualifies as admin
- $roles = array_keys(array_filter(variable_get('masquerade_admin_roles', array())));
- $perm = $account->uid == 1 || array_intersect(array_keys((array)$account->roles), $roles) ?
- 'masquerade as admin' :
- 'masquerade as user';
-
global $user;
+ // make sure the account viewed isn't our own
+ // and that we have adequate permissions to masquerade as it
+ if ($user->uid == $account->uid || !masquerade_check_user_roles($account)) {
+ return;
+ }
- if (user_access($perm) && empty($account->masquerading) && $user->uid != $account->uid) {
+ // Ensure we have access to: the user page, that we are not presently masquerading,
+ // that the current account isn't our own, and that the user we're looking at only has roles we can become.
+ if (empty($account->masquerading)) {
$account->content['masquerade'] = array(
'#markup' => l(t('Masquerade as !user', array('!user' => $account->name)),
'masquerade/switch/' . $account->uid,
@@ -439,6 +420,9 @@
if (!_masquerade_user_load($username)) {
form_set_error('masquerade_users', t('%user is not a valid user name.', array('%user' => $username)));
}
+ if (!masquerade_check_user_roles($username)) {
+ form_set_error('masquerade_users', t('This account has been assigned roles you are not permitted to access.'));
+ }
}
}
}
@@ -527,8 +511,10 @@
$block = array();
switch ($delta) {
case 'masquerade':
+ if (isset($_SESSION['masquerading']) || masquerade_has_any_masquerade_permission()) {
$block['subject'] = t('Masquerade');
$block['content'] = drupal_get_form('masquerade_block_1');
+ }
break;
}
return $block;
@@ -560,6 +546,10 @@
foreach ($masquerade_switches as $switch_user) {
if (!isset($_SESSION['user']->uid) || $switch_user != $_SESSION['user']->uid) {
$user_name = user_load($switch_user);
+ // make sure the users in the quick switch list only have roles we're allowed to become.
+ if (!masquerade_check_user_roles($user_name)) {
+ continue;
+ }
$switch_link = 'masquerade/switch/' . $user_name->uid;
if ($user_name->uid) {
$quick_switch_links[] = l($user_name->name, $switch_link, array('query' => array('token' => drupal_get_token($switch_link))));
@@ -625,6 +615,9 @@
if (!$masq_user) {
form_set_error('masquerade_user_field', t('User %masq_as does not exist. Please enter a valid username.', array('%masq_as' => $form_state['values']['masquerade_user_field'])));
}
+ elseif (!masquerade_check_user_roles($masq_user)) {
+ form_set_error('masquerade_user_field', t('You cannot masquerade as %masq_as because this person has roles you are not permitted to switch to.', array('%masq_as' => $form_state['values']['masquerade_user_field'])));
+ }
elseif ($masq_user->uid == $user->uid) {
form_set_error('masquerade_user_field', t('You cannot masquerade as yourself. Please choose a different user to masquerade as.'));
}
@@ -757,23 +750,14 @@
$new_user = user_load($uid);
- $roles = array_keys(array_filter(variable_get('masquerade_admin_roles', array())));
- $perm = $uid == 1 || array_intersect(array_keys($new_user->roles), $roles) ?
- 'masquerade as admin' :
- 'masquerade as user';
-
- // Check to see if we need admin permission.
- $results = db_query('SELECT TRUE FROM {masquerade_users} WHERE uid_from = :uid_from AND uid_to = :uid_to', array(
- ':uid_from' => $user->uid,
- ':uid_to' => $new_user->uid,
- ));
- if (!user_access($perm) && !isset($_SESSION['masquerading']) && !$results->fetchField()) {
- watchdog('masquerade', 'This user requires administrative permissions to switch to the user %user.', array('%user' => $new_user->name), WATCHDOG_ERROR);
+ if ($user->uid == $uid || isset($user->masquerading)) {
+ watchdog('masquerade', 'This user is already %user.', array('%user' => $new_user->name), WATCHDOG_ERROR);
return FALSE;
}
- if ($user->uid == $uid || isset($user->masquerading)) {
- watchdog('masquerade', 'This user is already %user.', array('%user' => $new_user->name), WATCHDOG_ERROR);
+ // ensure the account we're switching to isn't our own and that we have permission to masquerade with these roles
+ if ($user->uid !== $new_user->uid && !masquerade_check_user_roles($new_user)) {
+ watchdog('masquerade', 'The user %user has roles %user2 does not have permission to obtain.', array('%user' => $new_user->name, '%user2' => $user->name), WATCHDOG_ERROR);
return FALSE;
}
@@ -781,6 +765,7 @@
drupal_set_message(t('It is not possible to masquerade in off-line mode as %user does not have the %config-perm permission. Please set the site status to "online" to masquerade as %user.', array('%user' => $new_user->name, '%config-perm' => 'administer site configuration', '@site-maintenance' => url('admin/settings/site-maintenance'))));
return FALSE;
}
+ // hooray, we made it!
$query = db_insert('masquerade');
$query->fields(array(
@@ -836,4 +821,134 @@
$oldname = ($user->uid == 0 ? variable_get('anonymous', t('Anonymous')) : $user->name);
$user = user_load($uid);
watchdog('masquerade', 'User %user no longer masquerading as %masq_as.', array('%user' => $user->name, '%masq_as' => $oldname), WATCHDOG_INFO);
+}
+
+/**
+ * Implements hook_views_api()
+ */
+function masquerade_views_api() {
+ return array(
+ 'api' => 2,
+ 'path' => drupal_get_path('module', 'masquerade') . '/views',
+ );
+}
+
+/**
+ * Tell ctools where to look for plugins
+ */
+function masquerade_ctools_plugin_directory($owner, $plugin_type) {
+ // Treat ctools requests similarly to the way they normally work.
+ if ($owner == 'ctools') {
+ return "ctools/$plugin_type";
+ }
+ // we don't know what's being requsted
+ return '';
+}
+
+/**
+ * Adds a masquerading condition to the context module
+ */
+function masquerade_context_plugins() {
+ $plugins = array();
+ $plugins['masquerade_is_masquerading'] = array(
+ 'handler' => array(
+ 'path' => drupal_get_path('module', 'masquerade') .'/context/condition',
+ 'file' => 'masquerade_is_masquerading.inc',
+ 'class' => 'masquerade_is_masquerading_context_condition',
+ 'parent' => 'context_condition',
+ ),
+ );
+ return $plugins;
+}
+
+/**
+ * Tells the Context module to look for our custom plugins.
+ */
+function masquerade_context_registry() {
+ return array(
+ 'conditions' => array(
+ 'masquerade_is_masquerading' => array(
+ 'title' => t('User is Masquerading'),
+ 'description' => t('Sets this context when the current user is posing as someone else.'),
+ 'plugin' => 'masquerade_is_masquerading',
+ ),
+ ),
+ );
+}
+
+/**
+ * Triggers our custom context reactions.
+ * Why isn't this handled by the Context plugin manager??
+ */
+function masquerade_context_page_reaction() {
+ if ($plugin = context_get_plugin('condition', 'masquerade_is_masquerading')) {
+ $plugin->execute();
+ }
+}
+
+/**
+ * Checks if the user has access to masquerade as a specific role.
+ * @return bool
+ * Whether or not the current user is allowed to masquerade as someone with this role.
+ */
+function masquerade_check_role_permission($role) {
+ $permission = stripos($role, 'masquerade as') ? $role : 'masquerade as ' . $role;
+ return user_access($permission);
+}
+
+/**
+ * Checks an array of roles to see if we have access to them.
+ * @param $roles
+ * An array of roles to check
+ * @return bool
+ * Return FALSE if any role exists that the current user isn't permitted to become.
+ * Return TRUE otherwise.
+ */
+function masquerade_check_roles($roles = array()) {
+ foreach ($roles as $rid => $role) {
+ if ($rid == DRUPAL_AUTHENTICATED_RID) {
+ continue;
+ }
+ if (!masquerade_check_role_permission($role)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Checks all roles of the user we're trying to become.
+ * @param mixed
+ * The user object to check roles.
+ * @return bool
+ * Return FALSE if any role exists that the current user isn't permitted to become.
+ * Return TRUE otherwise.
+ */
+function masquerade_check_user_roles($user) {
+ if (is_numeric($user)) {
+ $user = user_load($user);
+ }
+ elseif (is_string($user)) {
+ $user = user_load_by_name($user);
+ }
+ elseif (!is_object($user) || !isset($user->roles)) {
+ return FALSE;
+ }
+ return masquerade_check_roles($user->roles);
+}
+
+/**
+ * Check if the user has ANY masquerade permission.
+ * @return bool
+ * Return TRUE if user has access to masquerade at all.
+ * Return FALSE if user can't masquerade.
+ */
+function masquerade_has_any_masquerade_permission() {
+ $return = FALSE;
+ foreach(user_roles() as $rid => $role) {
+ if (masquerade_check_role_permission($role)) {
+ $return = TRUE;
+ }
+ }
+ return $return;
}
diff --git a/context/condition/masquerade_is_masquerading.inc b/context/condition/masquerade_is_masquerading.inc
--- a/context/condition/masquerade_is_masquerading.inc Tue Jun 21 17:28:50 2011
+++ b/context/condition/masquerade_is_masquerading.inc Fri May 27 10:27:20 2011
@@ -0,0 +1,27 @@
+ t('Evaluate this context when the current user is posing as someone else.'));
+ }
+
+ /**
+ * @note
+ * hook_context_reaction() is required of your module and will have to call $plugin->execute()
+ * or this plugin won't do anything.
+ */
+ function execute() {
+ if (isset($_SESSION['masquerading']) && is_numeric($_SESSION['masquerading'])) {
+ foreach ($this->get_contexts() as $context) {
+ $this->condition_met($context, 'is_masquerading');
+ }
+ }
+ }
+}
diff --git a/ctools/access/is_masquerading.inc b/ctools/access/is_masquerading.inc
--- a/ctools/access/is_masquerading.inc Tue Jun 21 17:28:36 2011
+++ b/ctools/access/is_masquerading.inc Thu May 26 08:58:00 2011
@@ -0,0 +1,39 @@
+ t('User: Is Masquerading'),
+ 'description' => t('Checks whether or not a user is masquerading as another.'),
+ 'callback' => 'masquerade_is_masquerading_access_check',
+ 'default' => array(
+ ),
+ 'summary' => 'masquerade_is_masquerading_access_summary',
+ 'required context' => array(),
+);
+
+/**
+ * Checks if the user is currently masquerading as another.
+ * If no context is provided, this will automatically return FALSE.
+ * @return
+ * TRUE if the current user is masquerading, FALSE otherwise.
+ */
+function masquerade_is_masquerading_access_check($conf, $context) {
+ if (isset($_SESSION['masquerading']) && is_numeric($_SESSION['masquerading'])) {
+ return TRUE;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+/**
+ * Provides a simple access summary to administrators for this plugin.
+ * @return
+ * A string which states whether or not the current user is masquerading.
+ */
+function masquerade_is_masquerading_access_summary($conf, $context) {
+ return t('Current User: is masquerading as someone else.');
+}
diff --git a/views/masquerade.views.inc b/views/masquerade.views.inc
--- a/views/masquerade.views.inc Tue Jun 21 17:28:07 2011
+++ b/views/masquerade.views.inc Thu May 26 11:17:42 2011
@@ -0,0 +1,106 @@
+ array(
+ 'left_table' => 'users',
+ 'left_field' => 'uid',
+ 'field' => 'uid_as',
+ ),
+ 'user_is' => array(
+ 'left_table' => 'users',
+ 'left_field' => 'uid',
+ 'field' => 'uid_from',
+ ),
+ );
+
+ // A faux field that should allow us to access any user ID
+ $data['users']['masquerade_as'] = array(
+ // tell views that 'masquerade_as' isn't a real field
+ // and to use 'uid' instead
+ 'real field' => 'uid',
+ 'title' => t('Masquerade Link'),
+ 'help' => t('The user you want to masquerade as.'),
+ 'field' => array(
+ 'handler' => 'masquerade_handler_field_user',
+ 'click sortable' => TRUE,
+ ),
+ 'relationship' => array(
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'users',
+ 'base field' => 'uid',
+ 'field' => 'uid',
+ 'label' => t('user'),
+ ),
+ );
+
+ $data['masquerade']['sid'] = array(
+ 'title' => t('Session SID'),
+ 'help' => t('The session ID of the person masquerading.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ );
+
+ // Who is BEING masqueraded.
+ $data['masquerade']['uid_as'] = array(
+ 'title' => t('Masquerade As'),
+ 'help' => t('The user this person is posing as.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'relationship' => array(
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'users',
+ 'base field' => 'uid',
+ 'field' => 'uid_as',
+ 'label' => t('Masquerading as'),
+ ),
+ );
+
+ // Who actually IS masquerading.
+ $data['masquerade']['uid_from'] = array(
+ 'title' => t('Masquerade From'),
+ 'help' => t('The user this person actually is.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'relationship' => array(
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'users',
+ 'base field' => 'uid',
+ 'field' => 'uid_from',
+ 'label' => t('Who is masquerading'),
+ ),
+ );
+
+ return $data;
+}
+
+/**
+ * Use hook_views_handlers() to avoid forcing our handler to be loaded
+ * on every page call with files[] in the .info file.
+ */
+function masquerade_views_handlers() {
+ return array(
+ 'info' => array(
+ 'path' => drupal_get_path('module', 'masquerade') . '/views',
+ ),
+ 'handlers' => array(
+ 'masquerade_handler_field_user' => array(
+ 'parent' => 'views_handler_field_user'
+ ),
+ ),
+ );
+}
diff --git a/views/masquerade_handler_field_user_link.inc b/views/masquerade_handler_field_user_link.inc
--- a/views/masquerade_handler_field_user_link.inc Tue Jun 21 17:28:14 2011
+++ b/views/masquerade_handler_field_user_link.inc Tue May 31 10:01:10 2011
@@ -0,0 +1,80 @@
+ FALSE);
+ $options['custom_destination'] = array('default' => '');
+ return $options;
+ }
+
+ /**
+ * Adds additional options to the Field options form
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['provide_custom_destination'] = array(
+ '#title' => t('Provide a custom destination.'),
+ '#description' => t('This is helpful for providing a shortcut to a certain function of the site from one area or another.'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['provide_custom_destination']) ? $this->options['provide_custom_destination'] : FALSE,
+ );
+
+ // provide the an optional redirect immediately after switching to another user
+ $form['custom_destination'] = array(
+ '#title' => t('Alternate destination'),
+ '#description' => t('Redirect the browser to this page immediately after switching to another user. There is a caveat, however. When you switch back, masquerade will not remember where you last were.'),
+ '#type' => 'textfield',
+ '#dependency' => array( 'edit-options-provide-custom-destination' => array(TRUE) ),
+ '#default_value' => !empty($this->options['custom_destination']) && valid_url($this->options['custom_destination']) ? $this->sanitize_value($this->options['custom_destination']) : '',
+ );
+
+ // override the normal link-to title and description
+ $form['link_to_user']['#title'] = t('Generate a link to Masquerade as this user.');
+ $form['link_to_user']['#description'] = t('Be careful not to rewrite this field with tokens that generate links. This field will only display a link to users who have permission to masquerade.');
+ }
+
+ /**
+ * Generates a link
+ */
+ function render_link($data, $values) {
+ if (isset($values->{$this->aliases['uid']}) &&
+ masquerade_check_user_roles($values->{$this->aliases['uid']})) {
+ $data !== NULL &&
+ $data !== '' &&
+ // make sure any data being used to generate the link is clean
+ $switchto = $this->sanitize_value($values->{$this->aliases['uid']});
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "masquerade/switch/" . $switchto;
+
+ // let drupal know who we are before we switch users.
+ $this->options['alter']['query'] = array('token' => drupal_get_token($this->options['alter']['path']));
+
+ // if the user provided a custom destination (that isn't blank), tack it onto the link
+ if ($this->options['provide_custom_destination']== TRUE &&
+ $this->options['custom_destination'] != '') {
+ $this->options['alter']['query'] += array('destination' => $this->options['custom_destination']);
+ }
+
+ return $data;
+ }
+ return '';
+ }
+
+}