From 8a42cfb37a16efd6904cdfaadbf8c7dd02d427aa Mon Sep 17 00:00:00 2001 From: sun Date: Sun, 24 Feb 2013 16:22:01 +0100 Subject: [PATCH 1/3] Fixed missing module dependencies. --- masquerade.info | 1 + masquerade_advanced/masquerade_advanced.info | 1 + 2 files changed, 2 insertions(+) diff --git a/masquerade.info b/masquerade.info index 1779e6e..88b1887 100644 --- a/masquerade.info +++ b/masquerade.info @@ -2,4 +2,5 @@ name = Masquerade description = Allows privileged users to masquerade as another user. core = 8.x package = Development +dependencies[] = user tags[] = admin diff --git a/masquerade_advanced/masquerade_advanced.info b/masquerade_advanced/masquerade_advanced.info index 6d6134a..6f923a5 100644 --- a/masquerade_advanced/masquerade_advanced.info +++ b/masquerade_advanced/masquerade_advanced.info @@ -3,5 +3,6 @@ description = Provides per-user masquerade access, blocks, and settings. core = 8.x package = Development configure = admin/config/people/masquerade +dependencies[] = masquerade tags[] = admin tags[] = dev -- 1.7.11.msysgit.1 From 48dc6641d43f328a38cfd79f8e7b7aecea7c761a Mon Sep 17 00:00:00 2001 From: sun Date: Sun, 24 Feb 2013 17:12:43 +0100 Subject: [PATCH 2/3] - #1836516 by sun: Restored Masquerade block. --- .../Plugin/block/block/MasqueradeBlock.php | 69 ++++++++ lib/Drupal/masquerade/Tests/MasqueradeTest.php | 13 +- masquerade.module | 186 ++++++++------------- masquerade.routing.yml | 9 + masquerade_advanced/masquerade_advanced.module | 4 +- 5 files changed, 154 insertions(+), 127 deletions(-) create mode 100644 lib/Drupal/masquerade/Plugin/block/block/MasqueradeBlock.php create mode 100644 masquerade.routing.yml diff --git a/lib/Drupal/masquerade/Plugin/block/block/MasqueradeBlock.php b/lib/Drupal/masquerade/Plugin/block/block/MasqueradeBlock.php new file mode 100644 index 0000000..215c664 --- /dev/null +++ b/lib/Drupal/masquerade/Plugin/block/block/MasqueradeBlock.php @@ -0,0 +1,69 @@ + 10, + ); + } + + /** + * Overrides \Drupal\block\BlockBase::blockAccess(). + */ + public function blockAccess() { + return user_access('masquerade') && !isset($_SESSION['masquerading']); + } + + /** + * Overrides \Drupal\block\BlockBase::blockForm(). + */ + public function DISABLEDblockForm($form, &$form_state) { + $form['block_count'] = array( + '#type' => 'select', + '#title' => t('Number of recent content items to display'), + '#default_value' => $this->configuration['block_count'], + '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 30)), + ); + return $form; + } + + /** + * Overrides \Drupal\block\BlockBase::blockSubmit(). + */ + public function DISABLEDblockSubmit($form, &$form_state) { + $this->configuration['block_count'] = $form_state['values']['block_count']; + } + + /** + * Implements \Drupal\block\BlockBase::build(). + */ + public function build() { + return drupal_get_form('masquerade_block_form'); + } + +} diff --git a/lib/Drupal/masquerade/Tests/MasqueradeTest.php b/lib/Drupal/masquerade/Tests/MasqueradeTest.php index ad63d2f..da27fb8 100644 --- a/lib/Drupal/masquerade/Tests/MasqueradeTest.php +++ b/lib/Drupal/masquerade/Tests/MasqueradeTest.php @@ -28,18 +28,13 @@ class MasqueradeTest extends WebTestBase { public function testMasquerade() { $admin_perms = array( - 'administer masquerade', - 'masquerade as user', + 'masquerade', ); $this->admin_user = $this->drupalCreateUser($admin_perms); $this->web_user = $this->drupalCreateUser(); $this->drupalLogin($this->admin_user); - //test admin form - $this->drupalGet('admin/config/people/masquerade'); - $this->assertResponse(200); - //test switch $this->drupalGet('masquerade/switch/' . $this->web_user->uid); $this->assertResponse(403); @@ -52,11 +47,11 @@ class MasqueradeTest extends WebTestBase { $this->assertText('You are now masquerading as ' . $this->web_user->name); //test unswitch - $this->drupalGet('masquerade/unswitch'); + $this->drupalGet('unmasquerade'); $this->assertResponse(403); - $this->drupalGet('masquerade/unswitch', array( + $this->drupalGet('unmasquerade', array( 'query' => array( - 'token' => $this->drupalGetToken('masquerade/unswitch'), + 'token' => $this->drupalGetToken('unmasquerade'), ), )); $this->assertResponse(200); diff --git a/masquerade.module b/masquerade.module index 09f3011..76fc6b3 100644 --- a/masquerade.module +++ b/masquerade.module @@ -49,7 +49,7 @@ function masquerade_init() { if ($uid) { $_SESSION['masquerading'] = $uid; } - else { + elseif (isset($_SESSION['masquerading'])) { unset($_SESSION['masquerading']); } } @@ -80,37 +80,34 @@ function masquerade_menu() { 'page arguments' => array(2), 'access callback' => 'masquerade_menu_access', 'access arguments' => array('switch', 2), + // Invoke masquerade_translated_menu_link_alter() to append token. + 'options' => array('alter' => TRUE), ); - $items['masquerade/unswitch'] = array( - 'title' => 'Switch back', + + // @todo Core: It is not possible to add this link as user/unmasquerade. + $items['unmasquerade'] = array( + 'title' => 'Unmasquerade', 'page callback' => 'masquerade_switch_back_page', 'access callback' => 'masquerade_menu_access', - 'access arguments' => array('unswitch'), + 'access arguments' => array('unmasquerade'), + 'menu_name' => 'account', + 'weight' => 9, + // Invoke masquerade_translated_menu_link_alter() to append token. + 'options' => array('alter' => TRUE), ); + + // @todo Remove once drupal_valid_path() is fixed to find and access check + // paths managed by the new routing system: http://drupal.org/node/1793520 + // @see user_menu() $items['masquerade/autocomplete'] = array( - 'page callback' => 'masquerade_autocomplete', - 'access callback' => 'masquerade_menu_access', - 'access arguments' => array('autocomplete'), + 'page callback' => 'NOT_USED', + 'access arguments' => array('masquerade'), 'type' => MENU_CALLBACK, ); return $items; } /** - * Implements hook_menu_alter(). - * - * We need to add a token to the Masquerade paths to protect against CSRF - * attacks. Since menu items in Drupal do not support dynamic elements these - * tokens need to be added during rendering via masquerade_translated_menu_link_alter(). - * Set the 'alter'-option to TRUE to make sure - * the links get passed through masquerade_translated_menu_link_alter. - */ -function masquerade_menu_alter(&$items) { - $items['masquerade/switch/%']['options']['alter'] = TRUE; - $items['masquerade/unswitch']['options']['alter'] = TRUE; -} - -/** * Implements hook_translated_menu_link_alter(). * * Dynamically add the CSRF protection token to the Masquerade menu items. @@ -121,7 +118,7 @@ function masquerade_translated_menu_link_alter(&$item, $map) { $item['localized_options']['query']['token'] = drupal_get_token('masquerade/switch/' . $map[2]); } elseif ($item['page_callback'] == 'masquerade_switch_back_page') { - $item['localized_options']['query']['token'] = drupal_get_token('masquerade/unswitch'); + $item['localized_options']['query']['token'] = drupal_get_token('unmasquerade'); } } } @@ -152,7 +149,7 @@ function masquerade_user_operations_masquerade(array $accounts) { * Determine if the current user has permission to switch users. * * @param string $type - * Either 'switch', 'unswitch', 'user', or 'autocomplete'. + * Either 'switch', 'unmasquerade', 'user', or 'autocomplete'. * * @param object $uid * An optional parameter indicating a specific uid to switch to. @@ -162,11 +159,8 @@ function masquerade_user_operations_masquerade(array $accounts) { */ function masquerade_menu_access($type, $uid = NULL) { switch ($type) { - case 'unswitch': - return isset($_SESSION['masquerading']) || arg(2) == 'menu-customize' || arg(2) == 'menu'; - - case 'autocomplete': - return isset($_SESSION['masquerading']) || user_access('masquerade'); + case 'unmasquerade': + return isset($_SESSION['masquerading']); case 'switch': return !isset($_SESSION['masquerading']) && user_access('masquerade'); @@ -210,68 +204,26 @@ function masquerade_user_view($account, $view_mode, $langcode) { } /** - * Implements hook_block_info(). - */ -function masquerade_block_info() { - $blocks['masquerade'] = array( - 'info' => t('Masquerade'), - 'cache' => DRUPAL_NO_CACHE, - ); - return $blocks; -} - -/** - * Implements hook_block_view(). - */ -function masquerade_block_view($delta = '') { - $block = array(); - switch ($delta) { - case 'masquerade': - if (isset($_SESSION['masquerading']) || user_access('masquerade')) { - $block['subject'] = t('Masquerade'); - $block['content'] = drupal_get_form('masquerade_block_1'); - } - break; - } - return $block; -} - -/** * Masquerade block form. */ -function masquerade_block_1() { - global $user; - $quick_switch_links = array(); - $markup_value = ''; - if (isset($_SESSION['masquerading'])) { - $quick_switch_links[] = l(t('Switch back'), 'masquerade/unswitch', array('query' => array('token' => drupal_get_token('masquerade/unswitch')))); - $markup_value = t('You are masquerading as %masq_as.', array('@user-url' => url('user/' . $user->uid), '%masq_as' => $user->name)); - } - else { - if (masquerade_menu_access('autocomplete')) { - $markup_value .= t('Enter the username to masquerade as.'); - $form['masquerade_user_field'] = array( - '#prefix' => '
', - '#type' => 'textfield', - '#size' => '18', - '#default_value' => '', - '#autocomplete_path' => 'masquerade/autocomplete', - '#required' => TRUE, - ); - $form['submit'] = array( - '#type' => 'submit', - '#value' => t('Go'), - '#suffix' => '
', - ); - } - } - if ($quick_switch_links) { - $markup_value .= theme('item_list', array('items' => $quick_switch_links)); - } - $form['masquerade_desc'] = array( - '#prefix' => '
', - '#markup' => $markup_value, - '#suffix' => '
', +function masquerade_block_form() { + $form['autocomplete'] = array( + '#type' => 'container', + '#attributes' => array('class' => array('container-inline')), + ); + $form['autocomplete']['masquerade_as'] = array( + '#type' => 'textfield', + '#title' => t('Username'), + '#title_display' => 'invisible', + '#required' => TRUE, + '#placeholder' => t('Masquerade as…'), + '#size' => '18', + '#autocomplete_path' => 'masquerade/autocomplete', + ); + $form['autocomplete']['actions'] = array('#type' => 'actions'); + $form['autocomplete']['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Switch'), ); return $form; } @@ -279,32 +231,44 @@ function masquerade_block_1() { /** * Masquerade block form validation. */ -function masquerade_block_1_validate($form, &$form_state) { +function masquerade_block_form_validate($form, &$form_state) { global $user; - $name = $form_state['values']['masquerade_user_field']; + if (isset($_SESSION['masquerading'])) { - form_set_error('masquerade_user_field', t('You are already masquerading. Please switch back to your account to masquerade as another user.', array('@unswitch' => url('masquerade/unswitch', array('query' => array('token' => drupal_get_token('masquerade/unswitch'))))))); + form_set_error('masquerade_as', t('You are masquerading already. Please switch back to your account to masquerade as another user.', array( + '@unmasquerade-url' => url('unmasquerade', array( + 'query' => array('token' => drupal_get_token('unmasquerade')), + )), + ))); + return; } - $masq_user = user_load_by_name($name); - 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']))); + $name = $form_state['values']['masquerade_as']; + $target_user = user_load_by_name($name); + if (!$target_user) { + form_set_error('masquerade_as', t('The username %taret_user does not exist. Please enter a valid username.', array( + '%target_user' => $name, + ))); } - 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.')); + elseif ($target_user->uid == $user->uid) { + form_set_error('masquerade_as', t('You cannot masquerade as yourself. Please choose a different user to masquerade as.')); } - elseif (variable_get('maintenance_mode', 0) && !user_access('access site in maintenance mode', $masq_user)) { - form_set_error('masquerade_user_field', 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' => theme('username', array('account' => $masq_user)), '%config-perm' => 'use the site in maintenance mode', '@site-maintenance' => url('admin/settings/site-maintenance')))); + elseif (variable_get('maintenance_mode', 0) && !user_access('access site in maintenance mode', $target_user)) { + form_set_error('masquerade_as', t('!user is not permitted to %permission. Disable maintenance mode to masquerade as !user.', array( + '!user' => theme('username', array('account' => $target_user)), + '%permission' => t('Use the site in maintenance mode'), + '@maintenance-url' => url('admin/config/development/maintenance'), + ))); } else { - $form_state['masquerade_target_user'] = $masq_user; + $form_state['masquerade_target_user'] = $target_user; } } /** * Masquerade block form submission. */ -function masquerade_block_1_submit($form, &$form_state) { +function masquerade_block_form_submit($form, &$form_state) { if (!masquerade_switch_user($form_state['masquerade_target_user']->uid)) { throw new AccessDeniedHttpException(); } @@ -314,20 +278,6 @@ function masquerade_block_1_submit($form, &$form_state) { } /** - * Returns JS array for Masquerade autocomplete fields. - */ -function masquerade_autocomplete($string) { - $matches = array(); - $result = db_query_range("SELECT name FROM {users} WHERE LOWER(name) LIKE LOWER(:string)", 0, 10, array( - ':string' => $string . '%', - )); - foreach ($result as $user) { - $matches[$user->name] = check_plain($user->name); - } - return new JsonResponse($matches); -} - -/** * Page callback to switch users. */ function masquerade_switch_user_page($uid) { @@ -430,7 +380,7 @@ function masquerade_masquerade_access($user, $target_user) { */ function masquerade_switch_back_page() { $token = drupal_container()->get('request')->query->get('token'); - if (isset($token) && drupal_valid_token($token, 'masquerade/unswitch')) { + if (isset($token) && drupal_valid_token($token, 'unmasquerade')) { global $user; $olduser = $user; masquerade_switch_back(); @@ -447,10 +397,11 @@ function masquerade_switch_back_page() { */ function masquerade_switch_back() { global $user; - $uid = db_query("SELECT m.uid_from FROM {masquerade} m WHERE m.sid = :sid AND m.uid_as = :uid_as ", array( + $uid = db_query('SELECT uid_from FROM {masquerade} WHERE sid = :sid AND uid_as = :uid_as', array( ':sid' => session_id(), ':uid_as' => $user->uid, ))->fetchField(); + // Clean-up session masquerate. db_delete('masquerade') ->condition('sid', session_id()) @@ -467,5 +418,8 @@ function masquerade_switch_back() { // Call all login hooks when switching back to original user. module_invoke_all('user_login', $user); + // Remove the masquerading flag from the user's session. + unset($_SESSION['masquerading']); + watchdog('masquerade', 'User %user no longer masquerading as %masq_as.', array('%user' => $user->name, '%masq_as' => $oldname), WATCHDOG_INFO); } diff --git a/masquerade.routing.yml b/masquerade.routing.yml new file mode 100644 index 0000000..d03709d --- /dev/null +++ b/masquerade.routing.yml @@ -0,0 +1,9 @@ +# Only difference to /user/autocomplete is the access permission; Users that are +# allowed to masquerade do not necessarily need to have access to view user +# profiles. +masquerade_autocomplete: + pattern: '/masquerade/autocomplete' + defaults: + _controller: 'user.autocomplete_controller:autocompleteUser' + requirements: + _permission: 'masquerade' diff --git a/masquerade_advanced/masquerade_advanced.module b/masquerade_advanced/masquerade_advanced.module index de5fbab..b3e62bf 100644 --- a/masquerade_advanced/masquerade_advanced.module +++ b/masquerade_advanced/masquerade_advanced.module @@ -88,7 +88,7 @@ function masquerade_advanced_menu_alter(&$items) { * Determine if the current user has permission to switch users. * * @param string $type - * Either 'switch', 'unswitch', 'user', or 'autocomplete'. + * Either 'switch' or 'user'. * * @param object $uid * An optional parameter indicating a specific uid to switch to. @@ -336,7 +336,7 @@ function masquerade_advanced_user_delete($account) { /** * Implements hook_form_FORMID_alter(). */ -function masquerade_advanced_form_masquerade_block_1_alter(&$form, &$form_state) { +function masquerade_advanced_form_masquerade_block_form_alter(&$form, &$form_state) { global $user; if (isset($_SESSION['masquerading'])) { -- 1.7.11.msysgit.1 From 26029f8539368d8527c5fde40dd29e140b1c6458 Mon Sep 17 00:00:00 2001 From: sun Date: Sun, 24 Feb 2013 18:05:54 +0100 Subject: [PATCH 3/3] - #1836516 by sun: Fixed tests via helper methods to work around drupalGetToken() core bug. --- lib/Drupal/masquerade/Tests/MasqueradeTest.php | 85 +++++++++++++++++++++----- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/lib/Drupal/masquerade/Tests/MasqueradeTest.php b/lib/Drupal/masquerade/Tests/MasqueradeTest.php index da27fb8..88f19fe 100644 --- a/lib/Drupal/masquerade/Tests/MasqueradeTest.php +++ b/lib/Drupal/masquerade/Tests/MasqueradeTest.php @@ -8,11 +8,14 @@ namespace Drupal\masquerade\Tests; use Drupal\simpletest\WebTestBase; +use Drupal\user\Plugin\Core\Entity\User; /** * Tests form permissions and user switching functionality. * - * Requires Drupal core fix from http://drupal.org/node/1555862 + * @todo Core: $this->session_id is reset to NULL upon every internal browser + * request. + * @see http://drupal.org/node/1555862 */ class MasqueradeTest extends WebTestBase { @@ -26,44 +29,96 @@ class MasqueradeTest extends WebTestBase { ); } - public function testMasquerade() { - $admin_perms = array( - 'masquerade', - ); - $this->admin_user = $this->drupalCreateUser($admin_perms); + function testMasquerade() { + $this->admin_user = $this->drupalCreateUser(array('masquerade')); $this->web_user = $this->drupalCreateUser(); $this->drupalLogin($this->admin_user); - //test switch + // Verify that a token is required. + $this->drupalGet('masquerade/switch/'); + $this->assertResponse(404); + $this->drupalGet('masquerade/switch/0'); + $this->assertResponse(403); $this->drupalGet('masquerade/switch/' . $this->web_user->uid); $this->assertResponse(403); - $this->drupalGet('masquerade/switch/' . $this->web_user->uid, array( + + // Verify that the admin user is able to masquerade. + $this->masqueradeAs($this->web_user); + + // Verify that a token is required to unmasquerade. + $this->drupalGet('unmasquerade'); + $this->assertResponse(403); + + // Verify that the web user cannot masquerade. + $this->drupalGet('masquerade/switch/' . $this->admin_user->uid, array( 'query' => array( - 'token' => $this->drupalGetToken('masquerade/switch/' . $this->web_user->uid), + 'token' => $this->drupalGetToken('masquerade/switch/' . $this->admin_user->uid), + ), + )); + $this->assertResponse(403); + + // Verify that the user can unmasquerade. + $this->unmasquerade($this->web_user); + } + + /** + * Masquerades as another user. + * + * @param \Drupal\user\Plugin\Core\Entity\User $account + * The user account to masquerade as. + */ + protected function masqueradeAs(User $account) { + $this->drupalGet('masquerade/switch/' . $account->uid, array( + 'query' => array( + 'token' => $this->drupalGetToken('masquerade/switch/' . $account->uid), ), )); $this->assertResponse(200); - $this->assertText('You are now masquerading as ' . $this->web_user->name); + $this->assertText('You are now masquerading as ' . $account->name); - //test unswitch - $this->drupalGet('unmasquerade'); - $this->assertResponse(403); + // Update the logged in user account. + // @see WebTestBase::drupalLogin() + if (isset($this->session_id)) { + $this->loggedInUser = $account; + $this->loggedInUser->session_id = $this->session_id; + } + } + + /** + * Unmasquerades the current user. + * + * @param \Drupal\user\Plugin\Core\Entity\User $account + * The user account to unmasquerade from. + */ + protected function unmasquerade(User $account) { $this->drupalGet('unmasquerade', array( 'query' => array( 'token' => $this->drupalGetToken('unmasquerade'), ), )); $this->assertResponse(200); - $this->assertText('You are no longer masquerading as ' . $this->web_user->name); + $this->assertText('You are no longer masquerading as ' . $account->name); + + // Update the logged in user account. + // @see WebTestBase::drupalLogin() + if (isset($this->session_id)) { + $this->loggedInUser = $account; + $this->loggedInUser->session_id = $this->session_id; + } } /** + * Stop-gap fix. + * * @see http://drupal.org/node/1555862 */ protected function drupalGetToken($value = '') { $private_key = drupal_get_private_key(); - return drupal_hmac_base64($value, $this->session_id . $private_key . drupal_get_hash_salt()); + // Use the session_id assigned by WebTestBase::drupalLogin() instead of + // $this->session_id until the core bug is fixed. + $session_id = isset($this->loggedInUser->session_id) ? $this->loggedInUser->session_id : ''; + return drupal_hmac_base64($value, $session_id . $private_key . drupal_get_hash_salt()); } } -- 1.7.11.msysgit.1