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_000084_domain_events_cleanup;
|
||||
mod m20260427_000085_processed_events;
|
||||
mod m20260427_000086_enable_rls_all_tables;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -177,6 +178,7 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20260427_000083_create_follow_up_template::Migration),
|
||||
Box::new(m20260427_000084_domain_events_cleanup::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