You are here

userprotect.module in User protect 8

Same filename and directory in other branches
  1. 5 userprotect.module
  2. 6 userprotect.module
  3. 7 userprotect.module

Allows admins to protect users from being edited or cancelled.

File

userprotect.module
View source
<?php

/**
 * @file
 * Allows admins to protect users from being edited or cancelled.
 */
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemList;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\UserInterface;
use Drupal\userprotect\UserProtect;

/**
 * Loads a ProtectionRule object.
 *
 * @param string $name
 *   The ID of the ProtectionRule object to load.
 *
 * @return Drupal\userprotect\Entity\ProtectionRuleInterface
 *   An instance of an userprotect_rule entity.
 */
function userprotect_rule_load($name) {
  return \Drupal::entityTypeManager()
    ->getStorage('userprotect_rule')
    ->load($name);
}

/**
 * Implements hook_ENTITY_TYPE_access() for entity type "user".
 */
function userprotect_user_access(UserInterface $entity, $op, AccountInterface $account) {

  // User Protect doesn't limit view access in any way, so bail out early to
  // save time.
  if (in_array($op, [
    'view',
    'view label',
  ])) {
    return AccessResult::neutral();
  }

  // Check if the account has the permission "userprotect.bypass_all".
  // If so, all protections rules should be ignored.
  if (!$account
    ->hasPermission('userprotect.bypass_all')) {

    // Users editing their own accounts have the permissions for e-mail
    // and password determined by the role-based setting in the userprotect
    // section at admin/config/people/permissions. This is done for consistency
    // with the way core handles the self-editing of usernames.
    if ($entity
      ->id() == $account
      ->id()) {
      switch ($op) {
        case 'user_name':
          if (!$account
            ->hasPermission('change own username')) {
            return AccessResult::forbidden();
          }
          break;
        case 'user_mail':
          if (!$account
            ->hasPermission('userprotect.mail.edit')) {
            return AccessResult::forbidden();
          }
          break;
        case 'user_pass':
          if (!$account
            ->hasPermission('userprotect.pass.edit')) {
            return AccessResult::forbidden();
          }
          break;
        case 'user_edit':
        case 'update':
          if (!$account
            ->hasPermission('userprotect.account.edit')) {
            return AccessResult::forbidden();
          }
          break;
        case 'user_delete':
          if (!$account
            ->hasPermission('cancel account')) {
            return AccessResult::forbidden();
          }
          break;
      }
    }
    else {
      $protection_rules = userprotect_get_user_protection_rules($entity);
      foreach ($protection_rules as $rule) {

        // Check if the given account may bypass this rule.
        if ($account
          ->hasPermission($rule
          ->getPermissionName())) {

          // The given account has the permission to bypass this rule.
          continue;
        }
        if ($rule
          ->isProtected($entity, $op, $account)) {
          return AccessResult::forbidden();
        }
      }
    }
  }

  // Fallback to other operation checks for operations defined by this module.
  switch ($op) {
    case 'user_name':
    case 'user_mail':
    case 'user_pass':
    case 'user_status':
    case 'user_roles':
    case 'user_edit':
      return $entity
        ->access('update', $account) ? AccessResult::allowed() : AccessResult::forbidden();
    case 'user_delete':
      return $entity
        ->access('delete', $account) ? AccessResult::allowed() : AccessResult::forbidden();
  }
  return AccessResult::neutral();
}

/**
 * Implements hook_entity_field_access().
 *
 * If the field in question is a field on the user entity, protection rules are
 * checked to verify if access to edit the field is allowed.
 *
 * @see userprotect_user_access()
 */
function userprotect_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemList $items = NULL) {
  if (is_null($items)) {

    // Sometimes no field item list is passed. In this case, there is nothing
    // for userprotect to check.
    return AccessResult::neutral();
  }
  if ($operation != 'edit') {

    // Field access checks are limited to the edit operation.
    return AccessResult::neutral();
  }
  if ($field_definition
    ->getTargetEntityTypeId() != 'user') {

    // Access checks are only performed on user entities.
    return AccessResult::neutral();
  }

  // Get entity for which field access is checked.
  $entity = $items
    ->getEntity();
  if ($entity
    ->isNew()) {

    // Access checks are only performed on existing users.
    return AccessResult::neutral();
  }

  // Check access based on the field's name.
  $name = $field_definition
    ->getName();
  switch ($name) {
    case 'name':
    case 'mail':
    case 'pass':
    case 'status':
    case 'roles':

      // User protect defines each protection as an operation on the entity. See
      // userprotect_user_access().
      $entity_operation = 'user_' . $name;
      return $entity
        ->access($entity_operation, $account) ? AccessResult::neutral() : AccessResult::forbidden();

    // Make sure this module also works when role_delegation is enabled.
    case 'role_change':
      $entity_operation = 'user_roles';
      return $entity
        ->access($entity_operation, $account) ? AccessResult::neutral() : AccessResult::forbidden();
  }

  // The field is not one of the fields that userprotect supports.
  return AccessResult::neutral();
}

/**
 * Implements hook_user_delete().
 *
 * When an user is deleted, delete all associated protection rules as well.
 */
function userprotect_user_delete($account) {

  // Lookup all protection rules for this user.
  $entity_ids = \Drupal::entityQuery('userprotect_rule')
    ->condition('protectedEntityTypeId', 'user')
    ->condition('protectedEntityId', $account
    ->id())
    ->execute();

  // Delete protection rules.
  if (!empty($entity_ids)) {
    $storage_handler = \Drupal::entityTypeManager()
      ->getStorage('userprotect_rule');
    $entities = $storage_handler
      ->loadMultiple($entity_ids);
    $storage_handler
      ->delete($entities);
  }
}

/**
 * Implements hook_user_role_delete().
 *
 * When an role is deleted, delete all associated protection rules as well.
 */
function userprotect_user_role_delete($role) {

  // Lookup all protection rules for this user.
  $entity_ids = \Drupal::entityQuery('userprotect_rule')
    ->condition('protectedEntityTypeId', 'user_role')
    ->condition('protectedEntityId', $role
    ->id())
    ->execute();

  // Delete protection rules.
  if (!empty($entity_ids)) {
    $storage_handler = \Drupal::entityTypeManager()
      ->getStorage('userprotect_rule');
    $entities = $storage_handler
      ->loadMultiple($entity_ids);
    $storage_handler
      ->delete($entities);
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for form "user_form".
 */
function userprotect_form_user_form_alter(&$form, &$form_state) {

  // Get account that is being edited.
  $build_info = $form_state
    ->getBuildInfo();
  $entity = $build_info['callback_object']
    ->getEntity();
  if ($entity
    ->isNew()) {

    // Don't protect fields when adding a new user.
    return;
  }

  // Get operating account.
  $account = \Drupal::currentUser();

  // Get available protection plugins.
  $manager = UserProtect::pluginManager();
  $protection_definitions = $manager
    ->getDefinitions();

  // For each protection plugin, check if the current user has access
  // to the element the plugin protects. If not, apply the protection.
  $applied = [];
  foreach ($protection_definitions as $protection_definition) {
    if (!$entity
      ->access($protection_definition['id'], $account)) {

      // Apply protection.
      $protection = $manager
        ->createInstance($protection_definition['id'], $protection_definition);
      $success = $protection
        ->applyAccountFormProtection($form, $form_state);
      if ($success) {
        $applied[$protection
          ->getPluginId()] = $protection
          ->label();
      }
    }
  }

  // Display a message about the applied protections if there were protections
  // applied and if the current user is an admin user.
  if (count($applied) && $account
    ->hasPermission('administer users')) {
    $message = t('%user has been protected from the following editing operations: @operations', [
      '%user' => $entity
        ->getAccountName(),
      '@operations' => implode(', ', $applied),
    ]);
    \Drupal::messenger()
      ->addMessage($message);
  }
}

/**
 * Returns the protection rules that apply for the given account.
 *
 * @param \Drupal\user\UserInterface $account
 *   The account to get protection rules for.
 *
 * @return array
 *   A list of \Drupal\userprotect\Entity\ProtectionRuleInterface
 *   instances.
 */
function userprotect_get_user_protection_rules(UserInterface $account) {
  $query = \Drupal::entityQuery('userprotect_rule');
  $group_user = $query
    ->andConditionGroup()
    ->condition('protectedEntityTypeId', 'user')
    ->condition('protectedEntityId', $account
    ->id());
  $group_user_role = $query
    ->andConditionGroup()
    ->condition('protectedEntityTypeId', 'user_role')
    ->condition('protectedEntityId', $account
    ->getRoles());
  $group = $query
    ->orConditionGroup()
    ->condition($group_user)
    ->condition($group_user_role);
  $entity_ids = $query
    ->condition($group)
    ->execute();
  return \Drupal::entityTypeManager()
    ->getStorage('userprotect_rule')
    ->loadMultiple($entity_ids);
}

Functions

Namesort descending Description
userprotect_entity_field_access Implements hook_entity_field_access().
userprotect_form_user_form_alter Implements hook_form_FORM_ID_alter() for form "user_form".
userprotect_get_user_protection_rules Returns the protection rules that apply for the given account.
userprotect_rule_load Loads a ProtectionRule object.
userprotect_user_access Implements hook_ENTITY_TYPE_access() for entity type "user".
userprotect_user_delete Implements hook_user_delete().
userprotect_user_role_delete Implements hook_user_role_delete().