feat(db): RLS 策略迁移 — 80 张 tenant_id 表启用行级安全
- 所有含 tenant_id 的表(基础 34 + 健康 28 + 其他 18)启用 RLS - 策略:未设置 app.current_tenant_id 时允许全部,设置后按 tenant_id 过滤 - down 方法完整回退(DROP POLICY + DISABLE ROW LEVEL SECURITY)
This commit is contained in:
@@ -85,6 +85,7 @@ mod m20260427_000082_seed_ai_prompts;
|
|||||||
mod m20260427_000083_create_follow_up_template;
|
mod m20260427_000083_create_follow_up_template;
|
||||||
mod m20260427_000084_domain_events_cleanup;
|
mod m20260427_000084_domain_events_cleanup;
|
||||||
mod m20260427_000085_processed_events;
|
mod m20260427_000085_processed_events;
|
||||||
|
mod m20260427_000086_enable_rls_all_tables;
|
||||||
|
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
@@ -177,6 +178,7 @@ impl MigratorTrait for Migrator {
|
|||||||
Box::new(m20260427_000083_create_follow_up_template::Migration),
|
Box::new(m20260427_000083_create_follow_up_template::Migration),
|
||||||
Box::new(m20260427_000084_domain_events_cleanup::Migration),
|
Box::new(m20260427_000084_domain_events_cleanup::Migration),
|
||||||
Box::new(m20260427_000085_processed_events::Migration),
|
Box::new(m20260427_000085_processed_events::Migration),
|
||||||
|
Box::new(m20260427_000086_enable_rls_all_tables::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
/// 所有含 tenant_id 的表 — RLS 策略统一启用
|
||||||
|
const TENANT_TABLES: &[&str] = &[
|
||||||
|
// ── 基础层 (ERP Core) ──────────────────────────
|
||||||
|
"users",
|
||||||
|
"user_credentials",
|
||||||
|
"user_tokens",
|
||||||
|
"roles",
|
||||||
|
"permissions",
|
||||||
|
"role_permissions",
|
||||||
|
"user_roles",
|
||||||
|
"organizations",
|
||||||
|
"departments",
|
||||||
|
"positions",
|
||||||
|
"dictionaries",
|
||||||
|
"dictionary_items",
|
||||||
|
"menus",
|
||||||
|
"menu_roles",
|
||||||
|
"settings",
|
||||||
|
"numbering_rules",
|
||||||
|
"process_definitions",
|
||||||
|
"process_instances",
|
||||||
|
"tokens",
|
||||||
|
"tasks",
|
||||||
|
"process_variables",
|
||||||
|
"message_templates",
|
||||||
|
"messages",
|
||||||
|
"message_subscriptions",
|
||||||
|
"audit_logs",
|
||||||
|
"domain_events",
|
||||||
|
"plugins",
|
||||||
|
"plugin_entities",
|
||||||
|
"plugin_entity_columns",
|
||||||
|
"user_departments",
|
||||||
|
"plugin_user_views",
|
||||||
|
"plugin_market_reviews",
|
||||||
|
"tenant_crypto_keys",
|
||||||
|
"domain_events_archive",
|
||||||
|
// ── 健康层 (erp-health) ────────────────────────
|
||||||
|
"patient",
|
||||||
|
"patient_family_member",
|
||||||
|
"patient_tag",
|
||||||
|
"patient_tag_relation",
|
||||||
|
"doctor_profile",
|
||||||
|
"patient_doctor_relation",
|
||||||
|
"health_record",
|
||||||
|
"vital_signs",
|
||||||
|
"lab_report",
|
||||||
|
"health_trend",
|
||||||
|
"appointment",
|
||||||
|
"doctor_schedule",
|
||||||
|
"follow_up_task",
|
||||||
|
"follow_up_record",
|
||||||
|
"consultation_session",
|
||||||
|
"consultation_message",
|
||||||
|
"dialysis_record",
|
||||||
|
"daily_monitoring",
|
||||||
|
"diagnosis",
|
||||||
|
"critical_value_threshold",
|
||||||
|
"consent",
|
||||||
|
"device_readings",
|
||||||
|
"vital_signs_hourly",
|
||||||
|
"patient_devices",
|
||||||
|
"alert_rules",
|
||||||
|
"alerts",
|
||||||
|
"medication_record",
|
||||||
|
"dialysis_prescription",
|
||||||
|
// ── AI + 积分 + 内容 + 微信 ────────────────────
|
||||||
|
"ai_prompts",
|
||||||
|
"ai_analysis_results",
|
||||||
|
"ai_usage_logs",
|
||||||
|
"points_account",
|
||||||
|
"points_rule",
|
||||||
|
"points_transaction",
|
||||||
|
"points_product",
|
||||||
|
"points_order",
|
||||||
|
"points_checkin",
|
||||||
|
"offline_event",
|
||||||
|
"offline_event_registration",
|
||||||
|
"wechat_users",
|
||||||
|
"article",
|
||||||
|
"article_category",
|
||||||
|
"article_tag",
|
||||||
|
"article_revision",
|
||||||
|
"follow_up_template",
|
||||||
|
"follow_up_template_step",
|
||||||
|
];
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
let conn = manager.get_connection();
|
||||||
|
|
||||||
|
// 创建自定义配置参数(仅用于文档,PostgreSQL 自动支持 current_setting)
|
||||||
|
// 策略设计:
|
||||||
|
// - current_setting('app.current_tenant_id', true) = '' → 未设置,允许所有(兼容迁移/后台任务)
|
||||||
|
// - 设置为 UUID → 仅允许匹配 tenant_id 的行
|
||||||
|
|
||||||
|
for table in TENANT_TABLES {
|
||||||
|
// 启用 RLS
|
||||||
|
conn.execute_unprepared(&format!(
|
||||||
|
"ALTER TABLE {table} ENABLE ROW LEVEL SECURITY"
|
||||||
|
)).await?;
|
||||||
|
|
||||||
|
// 移除旧策略(如果存在,支持重复执行迁移)
|
||||||
|
conn.execute_unprepared(&format!(
|
||||||
|
"DROP POLICY IF EXISTS tenant_isolation ON {table}"
|
||||||
|
)).await?;
|
||||||
|
|
||||||
|
// 租户隔离策略 — 未设置参数时允许所有,设置后按 tenant_id 过滤
|
||||||
|
conn.execute_unprepared(&format!(
|
||||||
|
"CREATE POLICY tenant_isolation ON {table} USING (
|
||||||
|
current_setting('app.current_tenant_id', true) = ''
|
||||||
|
OR tenant_id = current_setting('app.current_tenant_id', true)::uuid
|
||||||
|
)"
|
||||||
|
)).await?;
|
||||||
|
|
||||||
|
// 超级用户绕过策略(数据库 owner 直接绕过 RLS)
|
||||||
|
// PostgreSQL 默认表 owner 绕过 RLS,无需额外策略
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
let conn = manager.get_connection();
|
||||||
|
|
||||||
|
for table in TENANT_TABLES {
|
||||||
|
conn.execute_unprepared(&format!(
|
||||||
|
"DROP POLICY IF EXISTS tenant_isolation ON {table}"
|
||||||
|
)).await?;
|
||||||
|
|
||||||
|
conn.execute_unprepared(&format!(
|
||||||
|
"ALTER TABLE {table} DISABLE ROW LEVEL SECURITY"
|
||||||
|
)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user