#![cfg(feature = "multi-tenant-enhanced")]
use sz_orm_core::dialect::get_dialect;
use sz_orm_core::tenant_context::{IsolationStrategy, TenantContext};
use sz_orm_core::{DbType, Model, QueryBuilder, Value};
#[derive(Debug, Clone, Default)]
struct Order {
id: i64,
}
impl Model for Order {
type PrimaryKey = i64;
fn table_name() -> &'static str {
"orders"
}
fn pk(&self) -> Self::PrimaryKey {
self.id
}
fn set_pk(&mut self, pk: Self::PrimaryKey) {
self.id = pk;
}
fn tenant_field() -> Option<&'static str> {
Some("tenant_id")
}
}
fn builder() -> QueryBuilder<Order> {
QueryBuilder::<Order>::new(get_dialect(DbType::MySQL).unwrap())
}
#[test]
fn attack_tenant_id_never_inlined_as_literal() {
let qb = builder()
.table("orders")
.with_tenant_id(42)
.where_eq("status", Value::String("active".to_string()));
let (sql, params) = qb.build_select_with_params();
assert!(
!sql.contains("42"),
"tenant_id 必须以参数传递,不得内联字面量: {sql}"
);
assert!(
sql.matches('?').count() >= 1,
"tenant_id 条件应有参数占位符: {sql}"
);
assert!(
params.iter().any(|v| v.as_i64() == Some(42)),
"params 中应包含 tenant_id=42 的参数值"
);
}
#[tokio::test]
async fn attack_cross_tenant_table_access_attempt() {
let ctx = TenantContext::new(42, IsolationStrategy::SchemaIsolation);
let result = ctx
.scope(async {
let qb = builder().table("orders").with_tenant_id(42);
let (sql, _) = qb.build_select_with_params();
sql
})
.await;
assert!(
result.contains("tenant_42_orders"),
"Schema 隔离下表名应重写为 tenant_42_orders: {result}"
);
let direct = builder().table("tenant_99_orders").with_tenant_id(42);
let (sql, _) = direct.build_select_with_params();
assert!(
sql.contains("tenant_99_orders"),
"已知边界:直接指定表名可绕过 Schema 重写(调用方须遵守 table() 入口约定)"
);
}
#[tokio::test]
async fn attack_missing_context_no_tenant_filter() {
let qb = builder()
.table("orders")
.where_eq("status", Value::String("active".to_string()));
let (sql, _) = qb.build_select_with_params();
assert!(
!sql.contains("tenant"),
"无上下文时不得隐式注入租户条件(已知边界): {sql}"
);
}
#[test]
fn attack_schema_router_edge_values() {
for tid in [0i64, -1, i64::MAX, i64::MIN] {
let name = sz_orm_core::tenant_context::SchemaIsolationRouter::rewrite_table("orders", tid);
assert!(
name.starts_with("tenant_") && name.ends_with("_orders"),
"表名格式异常: {name} (tenant_id={tid})"
);
assert!(
!name.contains("..") && !name.contains('/'),
"表名不得含路径分隔符(注入面): {name}"
);
}
}