Как правильно работать с правами доступа (Grants) в Drupal 7

В этой статье мы обсудим возможности настройки прав доступа к материалам. Людям, которые более-менее знакомы с этой системой известно, что базовые права пользователей регулируются по ссылке /admin/people/permissions.

Можно настроить CRUD действия для типов материалов в целом. Но что делать, если нужна детальная настройка прав? Если нужно ограничить доступ на просмотр или запретить редактировать только конкретные материалы?

Задача: Настроить права доступа к материалам, находящимся в закрытой группе.

Представим, что вы создаете треккер задач для своей команды. Ваши коллеги могут просматривать все проекты и задачи, но редактировать и создавать их могут только сотрудники, задействованные на конкретном проекте. Согласитесь, стандартная таблица настройки прав здесь будет бессильна. На помощь вам придут Гранты (Gratns).

Drupal Grants API — верхнеуровневая система настройки прав доступа к материалам.

Основная архитектура Drupal является масштабируемой и достаточной, поэтому все верхнеуровненвые права транслируются вниз. Все действия ядра Drupal и некоторых Contrib-модули над материалами используют гранты для настройки необходимых прав. Самым ярким примером может послужить модуль Views: все представления проверяют доступ к данным с помощью грантов.

Если взглянуть на грант детальнее, то его можно сравнить с ключом, который дает пользователю право на совершение действия, например, позволяет создавать материал или изменять его. В применении к нашей задачи у нас будет три типа ключей:

  • Просмотр определенного сообщения в группе: Грант1;
  • Редактирование определенного сообщения в группе: Грант2;
  • Удаление определенного сообщения в группе: Грант3.

Логично предположить, что если вы можете удалять сообщения, то можете и просматривать их. В таком случае можно немного модифицировать ключи, применив наследование. Таким образом, ключ на удаление также позволит просматривать и редактировать сообщения.

Теперь давайте подумаем, как мы будем выдавать эти ключи. Действия с сообщениями могут совершать только те пользователи, которые состоят в группе. Поэтому первоначальные типы ключей дополняются следующим образом:

  • Пользователь состоит в группе: Грант1;
  • Пользователь состоит в группе и имеет право редактировать: Грант2;
  • Пользователь состоит в группе и имеет право удалять: Грант3.

Допустим, что клиент изъявил желание участвовать в работе над проектом. Вы добавили его в треккер и теперь в праве скрыть от него некоторые сообщения. Для этого потребуется ввести еще один ключ, который запрещает чтение: Грант4. Выбрав пункт «Не показывать клиенту», мы сможем ограничить доступ на чтение. Следовательно членам группы выдается два ключа Грант1 и Грант4. Таким образом, клиенты не могут просматривать сообщения с дополнительной блокировкой или сообщения «неклиента».

Попробуем это реализовать:

/**
 * Implements hook_node_access_records().
 */
function laresan_test_node_access_records($node) {
  if ($node) {
    // Get 'show clients' field.
    $items = field_get_items('node', $node, 'field_shared_show_clients');

    // Default to zero for now.
    $client_switch = 0;
    // Check if set.
    if ($items != FALSE) {
      foreach ($items as $item) {
        $client_switch = $item['value'];
      }
    }

    // Initialise grants array.
    $grants = array();

    // We start with setting grants for group.
    if ($node->type == "work_group") {

      $grants[] = array(
        'realm' => 'laresan_test_node_access_view',
        'gid' => $node->nid,
        'grant_view' => 1,
        'grant_update' => 0,
        'grant_delete' => 0,
        'priority' => 0,
      );
      // Add extra realm for non-client groups.
      if ($client_switch == 0) {
        $grants[] = array(
          'realm' => 'laresan_test_node_access_view_nonclients',
          'gid' => $node->nid,
          'grant_view' => 1,
          'grant_update' => 0,
          'grant_delete' => 0,
          'priority' => 10,
        );
      }
    }

    // So now we've done Groups, it's time for other content types.
    // We need the Group reference nid from these guys, not nid.
    // Companies and teams are not referenced to a group in this context, skip
    // them.
    elseif ($node->type == "workgroup_message") {
      // Get reference fields value.
      $items = field_get_items('node', $node, 'field_shared_group_reference');
      foreach ($items as $item) {
        $nid = $item['nid'];
      }

      // Set Grants.
      else {
        $grants[] = array(
          'realm' => 'laresan_test_node_access_view',
          'gid' => $nid,
          'grant_view' => 1,
          'grant_update' => 0,
          'grant_delete' => 0,
          'priority' => 0,
        );
        $grants[] = array(
          'realm' => 'laresan_test_node_access_edit',
          'gid' => $nid,
          'grant_view' => 1,
          'grant_update' => 1,
          'grant_delete' => 0,
          'priority' => 0,
        );
        // Add extra realm for non-client nodes.
        if ($client_switch == 0) {
          $grants[] = array(
            'realm' => 'laresan_test_node_access_view_nonclients',
            'gid' => $nid,
            'grant_view' => 1,
            'grant_update' => 0,
            'grant_delete' => 0,
            'priority' => 10,
          );
          $grants[] = array(
            'realm' => 'laresan_test_node_access_edit_nonclients',
            'gid' => $nid,
            'grant_view' => 1,
            'grant_update' => 1,
            'grant_delete' => 0,
            'priority' => 10,
          );
        }
      }
    }

    return $grants;
  }
}

В этом коде мы определили какие бывают «замки» на каждом материале в нашем случае и сделали это с помощью хук-функции hook_node_access_records. Данная функция вызывается каждый раз, когда материалы изменяются или по нажатию на «переопределить права» в административной панели /admin/reports/status/rebuild. Вся информация о замках хранится в таблице node_access.

Осталось только раздать ключи пользователям. Для этого воспользуемся хук-функцией hook_node_grants. Она вызывается на каждой странице и проверяет доступы к текущему материалу и всем с ним связанным.

Замечание: Результат выполнения данной функции не кэшируется! Старайтесь не перегружать ее.

/**
 * Implements hook_node_grants().
 */
function laresan_test_node_grants($account, $op) {

  // Load user entity.
  $user = user_load($account->uid);

  // Load groups that user is granted.
  $group_ids = field_get_items('user', $user, 'field_groups');

  // Provide the user his group grants.
  $grants = array();

  foreach ($group_ids as $data) {

    // Grants access for non-client nodes.
    if (user_access("ol show non-client content")) {
      $grants['laresan_test_node_access_view_nonclients'][] = $data['nid'];
    }
    if (user_access("create ol_group content")) {
      // Check if Group is Archived, then don't give edit perms.
      // @TODO: for efficiency, do custom query.
      $groupnode = node_load($data['nid']);
      if (isset($groupnode->status) && $groupnode->status == 1) {
        $grants['laresan_test_node_access_edit'][] = $data['nid'];
        $grants['laresan_test_node_access_edit_nonclients'][] = $data['nid'];
      }
    }
    // Grants access for all other the nodes in users group.
    $grants['laresan_test_node_access_view'][] = $data['nid'];
  }

  // Tell good old Drupal about users grants.
  return $grants;
}

Данный код загружает данные пользователя, проверяет его роль и затем назначает гранты на действия с материалами.

Поделиться