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

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

У вас CRM с менеджерами. Каждый менеджер работает со своими клиентами. Проблема: менеджеры не должны видеть чужие заказы, чтобы случайно их не испортить. А директор должен видеть все заказы. И ещё начальник отдела должен видеть заказы подчинённых, но не других отделов. Это называется гранулярный доступ на уровне строк (row-level security).

В Falcon Space такая настройка делается через передачу параметра @username в SQL-процедуры и условия WHERE. Расскажу, как это работает и как настроить у себя.

Почему простых ролей недостаточно

Роли (администратор, менеджер, клиент) ограничивают доступ к страницам. Например, страница «Список заказов» доступна и менеджеру, и директору. Но менеджер должен видеть только заказы, где он назначен ответственным, а директор — все. Это и есть гранулярный доступ на уровне строк.

В Falcon Space нет встроенного «магического» фильтра по строке, но есть стандартный механизм: каждая хранимая процедура, возвращающая данные, получает параметр @username (текущий пользователь). Вы внутри процедуры пишете условие WHERE, которое фильтрует записи в зависимости от роли и привязки к пользователю.

Базовая схема: фильтр по владельцу

Самый простой случай: заказы привязаны к менеджеру через поле manager_id. Процедура:

CREATE PROCEDURE [app].[get_orders]
    @username nvarchar(100)
AS
BEGIN
    DECLARE @role nvarchar(50), @user_id int;
    SELECT @role = role, @user_id = user_id FROM users WHERE username = @username;
    
    IF @role = 'Director'
        SELECT * FROM orders;
    ELSE IF @role = 'Manager'
        SELECT o.* FROM orders o
        WHERE o.manager_id = @user_id;
    ELSE
        SELECT * FROM orders WHERE customer_username = @username;
END

Директор видит всё, менеджер — только свои заказы, клиент — только свои.

Реальный кейс: сеть автосервисов с филиалами

В проекте для сети СТО требовалось, чтобы менеджер филиала видел заказы только своего филиала, директор сети — все филиалы, а мастер — только назначенные ему заказы.

Мы добавили таблицу users с полями branch_id, role. Заказы имеют branch_id (филиал) и master_id.

Процедура для мастера:

IF @role = 'Master'
    SELECT * FROM orders 
    WHERE master_id = @user_id AND branch_id = (SELECT branch_id FROM users WHERE user_id = @user_id);

Для менеджера филиала:

ELSE IF @role = 'BranchManager'
    SELECT * FROM orders WHERE branch_id = (SELECT branch_id FROM users WHERE user_id = @user_id);

Директор сети не имеет branch_id или имеет NULL — для него условие без фильтра.

Это позволило каждому сотруднику работать только со своими данными. Ошибок доступа не было ни разу за год.

Иерархия: начальник видит подчинённых

Для иерархии добавляем поле manager_id в таблицу users. Процедура рекурсивно находит подчинённых:

WITH subordinates AS (
    SELECT user_id FROM users WHERE manager_id = @user_id
    UNION ALL
    SELECT u.user_id FROM users u
    INNER JOIN subordinates s ON u.manager_id = s.user_id
)
SELECT o.* FROM orders o
WHERE o.manager_id IN (SELECT user_id FROM subordinates)
   OR o.manager_id = @user_id;

Так можно строить сколь угодно глубокие иерархии.

Защита от подмены ID в URL

Даже если список заказов отфильтрован, пользователь может перейти по URL /order/123 (где 123 — чужой заказ). Чтобы это предотвратить, в процедуре получения одного заказа также проверяем права:

CREATE PROCEDURE [app].[get_order_details]
    @order_id int,
    @username nvarchar(100)
AS
BEGIN
    DECLARE @user_id int, @role nvarchar(50);
    SELECT @user_id = user_id, @role = role FROM users WHERE username = @username;
    
    SELECT o.* FROM orders o
    WHERE o.order_id = @order_id
      AND (
          @role = 'Director'
          OR (@role = 'Manager' AND o.manager_id = @user_id)
          OR (@role = 'Client' AND o.client_username = @username)
      );
END

Если ничего не вернулось — доступ запрещён.

Как настроить это в Falcon Space

  1. В таблице, которую нужно фильтровать, добавьте поле, связывающее запись с пользователем (например, responsible_user_id).
  2. Во всех хранимых процедурах, которые возвращают данные из этой таблицы, добавьте параметр @username (если его нет — добавьте).
  3. Внутри процедуры получите role и user_id текущего пользователя.
  4. Добавьте условный WHERE с фильтром по роли.
  5. Проверьте, что процедуры редактирования/удаления также содержат проверку прав.

Всё делается на чистом SQL. Мы помогаем с настройкой за 1-2 дня (20-50 тыс. руб).

Гранулярный доступ — это не роскошь, а требование безопасности и удобства. Настройте его один раз, и ваши сотрудники будут видеть только то, что им положено.

Страница-источник на сайте falconspace.ru