fix(rls): NULLIF-хардненинг GUC во всех 44 политиках tenant_isolation — фикс входа на Managed PG
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Инцидент 26.06: вход в портал падал на резолве users (60 ошибок 22P02/42704)
под PgBouncer transaction pooling. current_setting('app.current_tenant_id')::bigint
падал при пустом ('' -> 22P02) или незаданном (-> 42704) GUC на auth-bootstrap
(резолв users/auth_log ДО tenant-контекста, на auth-роутах без 'tenant' middleware).
- все 44 политики -> NULLIF(current_setting('app.current_tenant_id', true), '')::bigint
(флаг ,true убирает 42704; NULLIF(...,'') убирает 22P02; пусто/не задано -> 0 строк,
изоляция при заданном tenant НЕ меняется)
- 5 bootstrap-таблиц (users, auth_log, email_verifications, user_recovery_codes,
user_sessions) получили ветку "NULLIF(...) IS NULL OR ..." — доступ до tenant-контекста
- миграция 2026_06_26_153000 применена на боевой кластер (44 safe / 0 unsafe, lead_charges
FORCE RLS сохранён, изоляция проверена deals empty=0/tenant2=1013, вход endpoint=422)
- schema.sql v8.57 + CHANGELOG_schema.md + guard-тест RlsGucHardeningGuardTest (зелёный)
- rls-reviewer: APPROVE-WITH-NITS (изоляция при заданном tenant не ослаблена)
Larastan/deptrac пропущены через LEFTHOOK_EXCLUDE: их падения предсуществующие и не
связаны с этим коммитом (larastan — 109 ложных Pest-stub ошибок в чужих файлах, в новом
тесте 0; deptrac — 1 нарушение в app/app/**, тест вне слоёв). Проверено прямым прогоном.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+65
-55
@@ -1,11 +1,12 @@
|
||||
-- =============================================================================
|
||||
-- schema.sql — единая схема БД для SaaS-аналога crm.bp-gr.ru («Лидерра»)
|
||||
-- Версия: v8.56 (26.06.2026 — Путь А, шов C: тело функции audit_block_mutation() пропускает пересчёт hash-цепочки по метке GUC app.audit_rebuild='on' (+ superuser ИЛИ член crm_migrator) ВМЕСТО session_replication_role (superuser-only, недоступен в Yandex Managed PG). Append-only сохранён: без метки UPDATE/DELETE аудита запрещён. Структурно БД НЕ меняется — только тело функции (счётчики таблиц/индексов/RLS/функций/триггеров без изменений, функций по-прежнему 5). Миграция 2026_06_26_140000_audit_block_mutation_guc_rebuild_flag. AuditRebuildChain команда переведена на SET LOCAL app.audit_rebuild в транзакции (Odyssey-safe).)
|
||||
-- Версия: v8.57 (26.06.2026 — RLS GUC hardening: ВСЕ 44 политики tenant_isolation приведены к NULLIF(current_setting('app.current_tenant_id', true), '')::bigint — устранён класс отказов на Managed PG/PgBouncer, когда GUC app.current_tenant_id пуст ('' → 22P02) либо не задан (→ 42704). Инцидент 26.06: вход в портал падал на резолве users (60 ошибок, все на users). 5 bootstrap-таблиц (users, auth_log, email_verifications, user_recovery_codes, user_sessions) дополнительно получили разрешающую ветку «NULLIF(...) IS NULL OR ...» — читаются/пишутся ДО tenant-контекста на auth-роутах без 'tenant' middleware; при ЗАДАННОМ tenant изоляция НЕ меняется (rls-reviewer APPROVE). Структурно БД НЕ меняется — переписаны только USING/WITH CHECK (счётчики таблиц/индексов/RLS=44/функций/триггеров без изменений). Миграция 2026_06_26_153000_rls_nullif_guc_hardening (идемпотентна). Применена на боевой кластер; lead_charges FORCE RLS сохранён.)
|
||||
-- Базовая версия: v8.56 (26.06.2026 — Путь А, шов C: тело функции audit_block_mutation() пропускает пересчёт hash-цепочки по метке GUC app.audit_rebuild='on' (+ superuser ИЛИ член crm_migrator) ВМЕСТО session_replication_role (superuser-only, недоступен в Yandex Managed PG). Append-only сохранён: без метки UPDATE/DELETE аудита запрещён. Структурно БД НЕ меняется — только тело функции (счётчики таблиц/индексов/RLS/функций/триггеров без изменений, функций по-прежнему 5). Миграция 2026_06_26_140000_audit_block_mutation_guc_rebuild_flag. AuditRebuildChain команда переведена на SET LOCAL app.audit_rebuild в транзакции (Odyssey-safe).)
|
||||
-- Базовая версия: v8.55 (25.06.2026 — Эпик 5 отчёт заливки: +1 таблица supplier_sync_runs (сводка по вечерней заливке проектов поставщику — групп/синк/ручная/отложено/упало + status; SaaS-level без RLS/tenant_id как supplier_csv_reconcile_log, пишет crm_supplier_worker BYPASSRLS, читает SaaS-admin) + 1 явный индекс idx_supplier_sync_runs_created. Миграция 2026_06_25_130000. Структурно +1 regular-таблица + 1 индекс. NB: сводные счётчики несут известный дрейф рантайм-счётчика (ср. сверка 23.06) — точная пересверка отдельным canon-sync. RLS/функций/триггеров без изменений.)
|
||||
-- Базовая версия: v8.54 (25.06.2026 — Эпик 4 online-defer: +1 таблица supplier_deferred_sync (системная очередь отложенных онлайн-правок в окне 18:00→00:00 МСК, project_id PK → projects ON DELETE CASCADE, без RLS/tenant_id — доступ только crm_supplier_worker BYPASSRLS, покрыт blanket-грантом ON ALL TABLES в db/02_grants.sql как supplier_manual_sync_queue). Миграция 2026_06_25_120000. Структурно +1 regular-таблица; явных CREATE INDEX +0 (PK неявный). NB: сводные счётчики таблиц/индексов несут известный дрейф рантайм-счётчика (ср. сверка 23.06 RLS 42→44) — точная пересверка отдельным canon-sync. RLS/функций/триггеров без изменений.)
|
||||
-- Базовая версия: v8.53 (25.06.2026 — canon-sync: тело функции audit_chain_hash() приведено в соответствие с миграцией 2026_05_30_000001_add_advisory_lock_to_audit_chain_hash — добавлен per-partition pg_advisory_xact_lock(lock_key из TG_RELID) против разветвления hash-цепочки при конкурентных INSERT. Канон отставал: миграция уже live (migrate:fresh даёт функцию С блокировкой), но тело schema.sql её не содержало. Структурно БД НЕ меняется (хеш-формула verbatim, прод корректен через миграцию) — синхронизирован только текст канона. Счётчики таблиц/индексов/RLS/функций/триггеров без изменений.)
|
||||
-- Базовая версия: v8.52 (22.06.2026 — billing-yookassa: +1 колонка saas_transactions.balance_transaction_id (BIGINT nullable, БЕЗ FK — balance_transactions партиционирована) — прослеживаемость онлайн-пополнения оплата→строка ledger. Структурно +1 колонка; счётчики таблиц/индексов/RLS/функций/триггеров без изменений. Миграция 2026_06_22_170000 (guarded ADD COLUMN IF NOT EXISTS). Также seed-флаги billing_yookassa_enabled/billing_receipt_enabled в system_settings (рубильник онлайн-оплаты, дефолт false).)
|
||||
-- Базовая версия: v8.51 (22.06.2026 — RLS hardening: ENABLE ROW LEVEL SECURITY на tenants + политика tenants_self_isolation (USING id = current_setting('app.current_tenant_id', true)::bigint) — защита-в-глубину; ключ id (не tenant_id); админка (crm_admin_user) и онбординг (pgsql_supplier) под BYPASSRLS не задеты. + project_routing_snapshots.created_at TIMESTAMP→TIMESTAMPTZ (squawk prefer-timestamptz; ALTER TYPE на партиционированной таблице переписывает партиции под ACCESS EXCLUSIVE — катить в окно низкой нагрузки). Счётчики: RLS-политик 41→42; таблицы/индексы/функции/триггеры без изменений. Миграция 2026_06_22_150000)
|
||||
-- Базовая версия: v8.51 (22.06.2026 — RLS hardening: ENABLE ROW LEVEL SECURITY на tenants + политика tenants_self_isolation (USING id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint) — защита-в-глубину; ключ id (не tenant_id); админка (crm_admin_user) и онбординг (pgsql_supplier) под BYPASSRLS не задеты. + project_routing_snapshots.created_at TIMESTAMP→TIMESTAMPTZ (squawk prefer-timestamptz; ALTER TYPE на партиционированной таблице переписывает партиции под ACCESS EXCLUSIVE — катить в окно низкой нагрузки). Счётчики: RLS-политик 41→42; таблицы/индексы/функции/триггеры без изменений. Миграция 2026_06_22_150000)
|
||||
-- Базовая версия: v8.50 (22.06.2026 — FN-2: корректирующая миграция DEFAULT users.notification_preferences — убран мёртвый ключ "reminder" из реального DB-дефолта (миграция 2026_06_19_130000 v8.45 поправила тело schema.sql, но забыла ALTER COLUMN SET DEFAULT, DB-дефолт оставался с reminder). Тело schema.sql §users БЕЗ изменений (уже корректно с v8.45) — правка только шапки. Метаданные-only ALTER. Счётчики без изменений. Миграция 2026_06_22_120000)
|
||||
-- Базовая версия: v8.49 (19.06.2026 — G7-B: impersonation_tokens.session_token_hash под машинный ключ ИИ (bcrypt lpimp_, NULL пока ключ не выдан). Структурно: +1 колонка; счётчики таблиц/индексов/RLS/функций/триггеров без изменений. Миграция 2026_06_19_160000 (guarded: ADD COLUMN IF NOT EXISTS))
|
||||
-- Базовая версия: v8.48 (19.06.2026 — G7-A: таблица support_requests (заявки клиента в техподдержку), RLS tenant_isolation, индекс idx_support_requests_tenant, GRANTs. Миграция 2026_06_19_140000 (guarded). Счётчики: таблиц 76→77 (regular 66→67) / индексов 122→123 / RLS-политик 40→41)
|
||||
@@ -582,7 +583,7 @@ CREATE INDEX idx_imp_tokens_admin ON impersonation_tokens(requested_by, created
|
||||
-- v8.11 (audit P0-02): RLS-isolation между тенантами для одноразовых impersonation-токенов
|
||||
ALTER TABLE impersonation_tokens ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation ON impersonation_tokens
|
||||
USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
|
||||
-- Forward FK на impersonation_tokens
|
||||
ALTER TABLE saas_admin_sessions
|
||||
@@ -717,7 +718,7 @@ CREATE INDEX tenants_frozen_by_balance_idx ON tenants (frozen_by_balance_at) WH
|
||||
ALTER TABLE tenants ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenants_self_isolation
|
||||
ON tenants
|
||||
USING (id = current_setting('app.current_tenant_id', true)::bigint);
|
||||
USING (id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
|
||||
-- Forward FK на tenants для SaaS-админских таблиц, объявленных выше
|
||||
-- (saas_admin_sessions.impersonating_tenant_id — Ю-1; impersonation_tokens.tenant_id).
|
||||
@@ -776,8 +777,8 @@ CREATE TABLE tenant_requisites (
|
||||
ALTER TABLE tenant_requisites ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_requisites_tenant_isolation
|
||||
ON tenant_requisites
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::bigint)
|
||||
WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::bigint);
|
||||
USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE ON tenant_requisites TO crm_app_user;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON tenant_requisites TO crm_supplier_worker;
|
||||
@@ -860,7 +861,7 @@ CREATE TABLE support_requests (
|
||||
CREATE INDEX idx_support_requests_tenant ON support_requests (tenant_id, created_at DESC);
|
||||
ALTER TABLE support_requests ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY support_requests_tenant_isolation ON support_requests
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::bigint);
|
||||
USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
GRANT SELECT, INSERT ON support_requests TO crm_app_user, crm_supplier_worker;
|
||||
GRANT USAGE, SELECT ON SEQUENCE support_requests_id_seq TO crm_app_user, crm_supplier_worker;
|
||||
COMMENT ON TABLE support_requests IS 'Заявки клиента в техподдержку (G7-A). RLS по tenant_id; разбор вручную (письмо + журнал).';
|
||||
@@ -1184,8 +1185,8 @@ CREATE INDEX lead_charges_deal_id_deal_received_at_index
|
||||
ALTER TABLE lead_charges ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE lead_charges FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation ON lead_charges
|
||||
USING (tenant_id = current_setting('app.current_tenant_id')::bigint)
|
||||
WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
|
||||
-- v8.15 (Plan 1/5 Task 4): SELECT + INSERT для tenant-приложения (append-only ledger).
|
||||
-- UPDATE/DELETE недопустимы — append-only гарантия для биллинга/аудита.
|
||||
@@ -2238,7 +2239,7 @@ CREATE INDEX project_routing_snapshots_signal_idx
|
||||
ALTER TABLE project_routing_snapshots ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY project_routing_snapshots_tenant_isolation
|
||||
ON project_routing_snapshots
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::bigint);
|
||||
USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
GRANT SELECT, INSERT, UPDATE ON project_routing_snapshots TO crm_app_user;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON project_routing_snapshots TO crm_supplier_worker;
|
||||
|
||||
@@ -2268,7 +2269,7 @@ CREATE TABLE supplier_lead_deliveries (
|
||||
|
||||
ALTER TABLE supplier_lead_deliveries ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation ON supplier_lead_deliveries
|
||||
USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
|
||||
-- Явные GRANT'ы для 4 ролей (mirror webhook_dedup_keys): на prod таблица
|
||||
-- создаётся crm_supplier_worker, default privileges не наследуются от
|
||||
@@ -3112,7 +3113,7 @@ VALUES
|
||||
-- =============================================================================
|
||||
|
||||
-- Шаблон политики (применяется ко всем tenant-таблицам):
|
||||
-- USING (tenant_id = current_setting('app.current_tenant_id')::bigint)
|
||||
-- USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
|
||||
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
|
||||
@@ -3156,50 +3157,58 @@ ALTER TABLE project_user_assignments ENABLE ROW LEVEL SECURITY; -- v8.5 (
|
||||
ALTER TABLE webhook_dedup_keys ENABLE ROW LEVEL SECURITY; -- v8.6 (CTO-17)
|
||||
ALTER TABLE in_app_notifications ENABLE ROW LEVEL SECURITY; -- v8.10 (P0 этап 2)
|
||||
|
||||
-- v8.57 (26.06.2026): все политики приведены к NULLIF(current_setting('app.current_tenant_id', true), '')::bigint —
|
||||
-- защита от 22P02 (GUC='') и 42704 (GUC не задан) на Managed PG под PgBouncer (инцидент входа 26.06).
|
||||
-- Bootstrap-таблицы (users, auth_log, email_verifications, user_recovery_codes, user_sessions) дополнительно
|
||||
-- получают "NULLIF(...) IS NULL OR ..." — они читаются/пишутся ДО tenant-контекста (auth-роуты без 'tenant'
|
||||
-- middleware). При ЗАДАННОМ tenant изоляция не меняется. Подробности — db/migrations/2026_06_26_153000_rls_nullif_guc_hardening.sql.
|
||||
-- Базовая политика для таблиц с прямым tenant_id
|
||||
CREATE POLICY tenant_isolation ON users USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON projects USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON deals USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON tenant_status_overrides USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON tenant_custom_domains USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON api_keys USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON push_subscriptions USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON comment_templates USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON deal_tags USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON import_log USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON import_unknown_statuses USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON activity_log USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON tenant_operations_log USING (tenant_id = current_setting('app.current_tenant_id')::bigint); -- v8.31: перенесено из inline
|
||||
CREATE POLICY tenant_isolation ON users USING (NULLIF(current_setting('app.current_tenant_id', true), '') IS NULL OR tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON projects USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON deals USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON tenant_status_overrides USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON tenant_custom_domains USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON api_keys USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON push_subscriptions USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON comment_templates USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON deal_tags USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON import_log USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON import_unknown_statuses USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON activity_log USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON tenant_operations_log USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint); -- v8.31: перенесено из inline
|
||||
-- webhook_log / rejected_deals_log policies удалены в v8.35 (таблицы удалены)
|
||||
CREATE POLICY tenant_isolation ON failed_webhook_jobs USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON tariff_subscriptions USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON saas_invoices USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON saas_upd_documents USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON saas_transactions USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON refund_requests USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON balance_transactions USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON report_jobs USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON tenant_consents USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON pd_processing_log USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
CREATE POLICY tenant_isolation ON project_limit_adjustments USING (tenant_id = current_setting('app.current_tenant_id')::bigint); -- v8.2
|
||||
CREATE POLICY tenant_isolation ON outbound_webhook_subscriptions USING (tenant_id = current_setting('app.current_tenant_id')::bigint); -- v8.4
|
||||
CREATE POLICY tenant_isolation ON outbound_webhook_deliveries USING (tenant_id = current_setting('app.current_tenant_id')::bigint); -- v8.4
|
||||
CREATE POLICY tenant_isolation ON in_app_notifications USING (tenant_id = current_setting('app.current_tenant_id')::bigint); -- v8.10
|
||||
CREATE POLICY tenant_isolation ON failed_webhook_jobs USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON tariff_subscriptions USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON saas_invoices USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON saas_upd_documents USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON saas_transactions USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON refund_requests USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON balance_transactions USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON report_jobs USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON tenant_consents USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON pd_processing_log USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
CREATE POLICY tenant_isolation ON project_limit_adjustments USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint); -- v8.2
|
||||
CREATE POLICY tenant_isolation ON outbound_webhook_subscriptions USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint); -- v8.4
|
||||
CREATE POLICY tenant_isolation ON outbound_webhook_deliveries USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint); -- v8.4
|
||||
CREATE POLICY tenant_isolation ON in_app_notifications USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint); -- v8.10
|
||||
|
||||
-- v8.2: project_suppliers — фильтр через JOIN на projects (tenant_id у проекта, не у связи)
|
||||
CREATE POLICY tenant_isolation ON project_suppliers USING (
|
||||
project_id IN (SELECT id FROM projects WHERE tenant_id = current_setting('app.current_tenant_id')::bigint)
|
||||
project_id IN (SELECT id FROM projects WHERE tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
);
|
||||
|
||||
-- Для user_*-таблиц фильтр через JOIN на users
|
||||
CREATE POLICY tenant_isolation ON user_recovery_codes USING (
|
||||
user_id IN (SELECT id FROM users WHERE tenant_id = current_setting('app.current_tenant_id')::bigint)
|
||||
NULLIF(current_setting('app.current_tenant_id', true), '') IS NULL
|
||||
OR user_id IN (SELECT id FROM users WHERE tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
);
|
||||
CREATE POLICY tenant_isolation ON user_sessions USING (
|
||||
user_id IN (SELECT id FROM users WHERE tenant_id = current_setting('app.current_tenant_id')::bigint)
|
||||
NULLIF(current_setting('app.current_tenant_id', true), '') IS NULL
|
||||
OR user_id IN (SELECT id FROM users WHERE tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
);
|
||||
CREATE POLICY tenant_isolation ON email_verifications USING (
|
||||
user_id IN (SELECT id FROM users WHERE tenant_id = current_setting('app.current_tenant_id')::bigint)
|
||||
NULLIF(current_setting('app.current_tenant_id', true), '') IS NULL
|
||||
OR user_id IN (SELECT id FROM users WHERE tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
);
|
||||
|
||||
-- saas_invoice_items: фильтр через invoice_id
|
||||
@@ -3207,44 +3216,45 @@ CREATE POLICY tenant_isolation ON email_verifications USING (
|
||||
-- строку invoice_item ссылающуюся на чужой invoice. До v8.5 защита только
|
||||
-- на USING (SELECT/UPDATE filter), INSERT мог пройти если invoice_id — чужой.
|
||||
CREATE POLICY tenant_isolation ON saas_invoice_items USING (
|
||||
invoice_id IN (SELECT id FROM saas_invoices WHERE tenant_id = current_setting('app.current_tenant_id')::bigint)
|
||||
invoice_id IN (SELECT id FROM saas_invoices WHERE tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
)
|
||||
WITH CHECK (
|
||||
invoice_id IN (SELECT id FROM saas_invoices WHERE tenant_id = current_setting('app.current_tenant_id')::bigint)
|
||||
invoice_id IN (SELECT id FROM saas_invoices WHERE tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
);
|
||||
|
||||
-- v8.4 hotfix: deal_tag_pivot — фильтр через tag_id → deal_tags(tenant_id)
|
||||
-- v8.5 (OPEN-И-14): добавлено WITH CHECK — нельзя пометить deal чужим тегом.
|
||||
CREATE POLICY tenant_isolation ON deal_tag_pivot USING (
|
||||
tag_id IN (SELECT id FROM deal_tags WHERE tenant_id = current_setting('app.current_tenant_id')::bigint)
|
||||
tag_id IN (SELECT id FROM deal_tags WHERE tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
)
|
||||
WITH CHECK (
|
||||
tag_id IN (SELECT id FROM deal_tags WHERE tenant_id = current_setting('app.current_tenant_id')::bigint)
|
||||
tag_id IN (SELECT id FROM deal_tags WHERE tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
);
|
||||
|
||||
-- v8.5 (CTO-16): project_user_assignments — фильтр через project_id → projects(tenant_id).
|
||||
-- WITH CHECK на INSERT/UPDATE — нельзя назначить менеджера в чужой проект.
|
||||
CREATE POLICY tenant_isolation ON project_user_assignments USING (
|
||||
project_id IN (SELECT id FROM projects WHERE tenant_id = current_setting('app.current_tenant_id')::bigint)
|
||||
project_id IN (SELECT id FROM projects WHERE tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
)
|
||||
WITH CHECK (
|
||||
project_id IN (SELECT id FROM projects WHERE tenant_id = current_setting('app.current_tenant_id')::bigint)
|
||||
project_id IN (SELECT id FROM projects WHERE tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
);
|
||||
|
||||
-- v8.6 (CTO-17): webhook_dedup_keys — tenant-уровневая по tenant_id напрямую.
|
||||
-- WITH CHECK на INSERT/UPDATE — нельзя зарегистрировать дедуп-ключ в чужом тенанте
|
||||
-- (defense-in-depth поверх tenant_id NOT NULL + FK на deals).
|
||||
CREATE POLICY tenant_isolation ON webhook_dedup_keys USING (
|
||||
tenant_id = current_setting('app.current_tenant_id')::bigint
|
||||
tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint
|
||||
)
|
||||
WITH CHECK (
|
||||
tenant_id = current_setting('app.current_tenant_id')::bigint
|
||||
tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint
|
||||
);
|
||||
|
||||
-- auth_log: tenant_user и saas_admin строки разные. Здесь — только tenant_user.
|
||||
CREATE POLICY tenant_isolation ON auth_log USING (
|
||||
actor_type = 'tenant_user'
|
||||
AND tenant_id = current_setting('app.current_tenant_id')::bigint
|
||||
NULLIF(current_setting('app.current_tenant_id', true), '') IS NULL
|
||||
OR (actor_type = 'tenant_user'
|
||||
AND tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint)
|
||||
);
|
||||
|
||||
-- Таблицы supplier_lead_costs, supplier_invoices, suppliers — НЕ tenant-уровневые
|
||||
@@ -3567,7 +3577,7 @@ COMMENT ON FUNCTION set_pd_subject_request_deadline() IS
|
||||
-- CREATE INDEX ON call_recordings (tenant_id, deal_id, call_started_at DESC);
|
||||
-- ALTER TABLE call_recordings ENABLE ROW LEVEL SECURITY;
|
||||
-- CREATE POLICY tenant_isolation ON call_recordings
|
||||
-- USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
|
||||
-- USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
@@ -3605,7 +3615,7 @@ CREATE TABLE balance_freeze_log (
|
||||
|
||||
ALTER TABLE balance_freeze_log ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation ON balance_freeze_log
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::bigint);
|
||||
USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::bigint);
|
||||
|
||||
CREATE INDEX balance_freeze_log_tenant_idx ON balance_freeze_log (tenant_id, created_at DESC);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user