У вас 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;
Так можно строить сколь угодно глубокие иерархии.
Даже если список заказов отфильтрован, пользователь может перейти по 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
Если ничего не вернулось — доступ запрещён.
Всё делается на чистом SQL. Мы помогаем с настройкой за 1-2 дня (20-50 тыс. руб).
Гранулярный доступ — это не роскошь, а требование безопасности и удобства. Настройте его один раз, и ваши сотрудники будут видеть только то, что им положено.