Skip to content
masquerade.module 8.54 KiB
Newer Older
Earl Miles's avatar
Earl Miles committed
<?php
Earl Miles's avatar
Earl Miles committed

Earl Miles's avatar
Earl Miles committed
/**
 * Allows privileged users to masquerade as another user.
Earl Miles's avatar
Earl Miles committed
 */

use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\user\UserInterface;
Earl Miles's avatar
Earl Miles committed
/**
Earl Miles's avatar
Earl Miles committed
 */
function masquerade_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.masquerade':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Masquerade module allows users to temporarily switch to another user account. It records the original user account, so users can easily switch back.') . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Granting masquerade access') . '</dt>';
      $output .= '<dd>' . t('Users may only masquerade as another user if they have the <a href=":permission_link">Masquerade as any user</a> permission or if they have all the <a href=":permission_link">Masquerade as ROLE</a> permissions for all the target account\'s roles.', [
        ':permission_link' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-masquerade'])->toString(),
      ]) . '</dd>';
      $output .= '<dt>' . t('Masquerading as another user') . '</dt>';
      $output .= '<dd>' . t('There are multiple ways to masquerade as another user:');
      $output .= '<ul>';
      $output .= '<li>' . t('On the <a href=":admin-people-url">administrative user listing</a>, choose the <em>Masquerade</em> operation of a certain user account.', [
        ':admin-people-url' => Url::fromRoute('entity.user.collection')->toString(),
      ]) . '</li>';
      $output .= '<li>' . t('Masquerade can be used directly from menus provided by the %toolbar module.', [
      $output .= '</ul>';
      $output .= '</dd>';
      $output .= '<dt>' . t('Switching back') . '</dt>';
      $output .= '<dd>' . t('To stop masquerading as another user, click the <em>Unmasquerade</em> link in the user account links menu.') . '</dd>';
Earl Miles's avatar
Earl Miles committed
  }
}

/**
 * Implements hook_toolbar().
 *
 * @todo Nest this with the "View profile", "Edit profile", and "Log out"
 *   links under the username tab.
 */
function masquerade_toolbar() {
  $items = [
    'masquerade_switch_back' => [
      '#cache' => [
        'contexts' => ['session.is_masquerading'],
      ],
    ],
  ];
  if (\Drupal::service('masquerade')->isMasquerading()) {
      '#type' => 'toolbar_item',
        '#type' => 'link',
        '#url' => Url::fromRoute('masquerade.unmasquerade'),
      // Hopefully shows immediately after the username tab.
      '#weight' => 101,
 * Returns whether the current user is allowed to masquerade as a target user.
Andrey Postnikov's avatar
Andrey Postnikov committed
 * @param \Drupal\user\UserInterface $target_account
 *   The user account object to masquerade as.
 *
 * @return bool
 *   TRUE if allowed, FALSE otherwise.
 *
 * @see hook_masquerade_access()
function masquerade_target_user_access(UserInterface $target_account) {
  $user = \Drupal::currentUser();

  // Deny access if the current user is masquerading already or tries to
  // masquerade as himself.
  if (\Drupal::service('masquerade')->isMasquerading() || $user->id() == $target_account->id()) {
    return FALSE;
  }

  // Invoke hook_masquerade_access() implementations.
  $results = \Drupal::moduleHandler()->invokeAll('masquerade_access', [
    $user,
    $target_account,
  ]);

  // If an implementation explicitly denies access, then there is nothing else
  // to check.
  if (in_array(FALSE, $results, TRUE)) {

  // If access is not explicitly denied, allow access if at least one hook
  // implementation allowed access.
  return in_array(TRUE, $results, TRUE);
}

/**
 * Implements hook_masquerade_access().
 *
 * This default implementation only returns TRUE and never FALSE, since
 * alternative access implementations could not work otherwise.
Andrey Postnikov's avatar
Andrey Postnikov committed
function masquerade_masquerade_access($user, UserInterface $target_account) {
Andrey Postnikov's avatar
Andrey Postnikov committed
  if ($user->id() == 1) {
  // Uid 1 gets special treatment with its own permission.
    if ($user->hasPermission('masquerade as super user')) {
      return TRUE;
    }
    else {
  if ($user->hasPermission('masquerade as any user')) {
  // Permissions may be granted on a per-role basis.
  $target_account_roles = $target_account->getRoles();
  foreach ($target_account_roles as $role_id) {
    if (!$user->hasPermission("masquerade as $role_id")) {
      return NULL;

  // Only allow masquerade if a user has access to all the target account roles.
  return TRUE;
/**
 * Implements hook_entity_extra_field_info().
 */
function masquerade_entity_extra_field_info() {
  $fields['user']['user']['display']['masquerade'] = [
    'description' => t('Masquerade as user link.'),
    'weight' => 50,
function masquerade_user_view(array &$build, UserInterface $account, EntityViewDisplayInterface $display, $view_mode) {
  if ($display->getComponent('masquerade')) {
    // Use post render to allow caching.
    $build['masquerade'] = [
        'masquerade.callbacks:renderCacheLink', [
      '#create_placeholder' => TRUE,
/**
 * Implements hook_entity_type_alter().
 *
 * Adds useful link template to user entity.
 */
function masquerade_entity_type_alter(array &$entity_types) {
  /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
  $entity_types['user']->setLinkTemplate('masquerade', '/user/{user}/masquerade');
}

/**
 * Implements hook_entity_operation().
 */
function masquerade_entity_operation(EntityInterface $entity) {
  if ($entity->getEntityTypeId() === 'user') {
    if (masquerade_target_user_access($entity)) {
        'title' => t('Masquerade as'),
        'weight' => 100,
        'url' => $entity->toUrl('masquerade'),
 * Validates whether the current user can masquerade as a given target user.
 *
 * Use this function to generate user-friendly error messages to show in the
 * user interface.
Andrey Postnikov's avatar
Andrey Postnikov committed
 * @param \Drupal\user\UserInterface $target_account
 *   The user account object to masquerade as.
 * @return string|null
 *   A string containing a validation error message, or NULL if the current user
 *   can masquerade as $target_account.
Earl Miles's avatar
Earl Miles committed
 */
Andrey Postnikov's avatar
Andrey Postnikov committed
function masquerade_switch_user_validate(UserInterface $target_account) {
  $user = \Drupal::currentUser();
  if (\Drupal::service('masquerade')->isMasquerading()) {
    return t('You are masquerading already. Please <a href="@unmasquerade-url">switch back</a> to your account to masquerade as another user.', [
      '@unmasquerade-url' => Url::fromRoute('masquerade.unmasquerade')->toString(),
Andrey Postnikov's avatar
Andrey Postnikov committed
  if ($target_account->id() == $user->id()) {
    return t('You cannot masquerade as yourself. Please choose a different user to masquerade as.');
  if (\Drupal::config('system.maintenance')->get('enabled') && !$target_account->hasPermission('access site in maintenance mode')) {
    return t('@user is not permitted to %permission. <a href=":maintenance-url">Disable maintenance mode</a> to masquerade as @user.', [
      '@user' => $target_account->getDisplayName(),
      '%permission' => t('Use the site in maintenance mode'),
      ':maintenance-url' => Url::fromRoute('system.site_maintenance_mode')->toString(),
  if (!masquerade_target_user_access($target_account)) {
    return t('You are not allowed to masquerade as %name.', [
      '%name' => $target_account->getDisplayName(),

/**
 * Implements hook_migration_plugins_alter().
 *
 * Names of masquerade permissions changed between Drupal 7 and 8|9. Map the
 * permission names respectively.
 */
function masquerade_migration_plugins_alter(array &$migrations) {
  if (
    !empty($migrations['d7_user_role']['process']['permissions'][0]) &&
    $migrations['d7_user_role']['process']['permissions'][0]['plugin'] === 'static_map'
  ) {
    $migrations['d7_user_role']['process']['permissions'][0]['map']['masquerade as admin'] = 'masquerade as super user';
  }
}