mirror of
https://github.com/RaisFast/raisfast.git
synced 2026-09-23 16:02:25 +00:00
8.8 KiB
8.8 KiB
db_execute 插件扩展设计
2026-04-16 · 为插件系统增加数据库写操作能力,解锁电商/工作流等业务场景。
1. 目标
在现有 Host.dbQuery(sql) (只读) 基础上,新增 Host.dbExecute(sql) (写入),让插件可以执行 INSERT/UPDATE/DELETE。
不做的事:
- 不支持 DDL(CREATE/DROP/ALTER/TRUNCATE)
- 不支持事务(BEGIN/COMMIT/ROLLBACK)
- 不支持批量语句(只执行第一条)
2. API 设计
插件端调用
// JS 插件
const result = Host.dbExecute("UPDATE products SET stock = stock - 1 WHERE id = 'abc'");
// result: '{"rows_affected":1}' 或 '{"error":"no write permission for table: products"}'
-- Lua 插件
local result = Host.dbExecute("INSERT INTO orders (id, status) VALUES ('ord-1', 'pending')")
-- result: '{"rows_affected":1}' 或 '{"error":"..."}'
// WASM 插件(预期签名)
fn host_db_execute(ptr: i32, len: i32) -> i32;
返回格式
// 成功
{"rows_affected": 3}
// 失败
{"error": "only INSERT/UPDATE/DELETE are allowed"}
{"error": "no write permission for table: orders"}
{"error": "DDL operations are not allowed"}
3. 权限模型
manifest.toml 声明
[permissions]
database = [
"read:products", # 只读 products 表
"write:orders", # 只写 orders 表
"products", # 读写 products 表(等同于 read + write)
"read:order_items",
"write:order_items",
]
权限规则(复用已有实现)
| 声明 | db_query (读) |
db_execute (写) |
|---|---|---|
"*" |
✅ 所有表 | ✅ 所有表 |
"products" (裸表名) |
✅ | ✅ |
"read:products" |
✅ | ❌ |
"write:products" |
❌ | ✅ |
已有的 is_table_writable() (src/plugins/permissions.rs:84-93) 已覆盖以上逻辑。
4. 安全防护(4 层)
Layer 1: SQL 类型白名单
fn is_write_query(sql: &str) -> bool {
let trimmed = sql.trim().to_uppercase();
trimmed.starts_with("INSERT")
|| trimmed.starts_with("UPDATE")
|| trimmed.starts_with("DELETE")
}
只允许 INSERT / UPDATE / DELETE,拒绝 SELECT / DDL / 其他。
Layer 2: DDL 黑名单
fn is_ddl_query(sql: &str) -> bool {
let trimmed = sql.trim().to_uppercase();
trimmed.starts_with("CREATE")
|| trimmed.starts_with("DROP")
|| trimmed.starts_with("ALTER")
|| trimmed.starts_with("TRUNCATE")
|| trimmed.starts_with("ATTACH")
|| trimmed.starts_with("DETACH")
|| trimmed.starts_with("PRAGMA")
|| trimmed.starts_with("REINDEX")
|| trimmed.starts_with("ANALYZE")
|| trimmed.starts_with("VACUUM")
}
即使绕过 Layer 1,DDL 也会被拦截。
Layer 3: 表级权限 RBAC
let table = extract_write_table_name(sql);
if !PermissionChecker::is_table_writable(&self.permissions, &table) {
return r#"{"error":"no write permission for table: ..."}"#.to_string();
}
Layer 4: 系统表保护
const PROTECTED_TABLES: &[&str] = &[
"users", "roles", "permissions",
"extensions", "audit_log",
"plugin_storage", "options",
"rbac_roles", "rbac_permissions", "rbac_role_permissions",
"tenants",
];
fn is_protected_table(table: &str) -> bool {
PROTECTED_TABLES.contains(&table.to_lowercase().as_str())
}
即使声明 "write:users" 或 "*",系统核心表也拒绝写入。
5. 写 SQL 表名提取
现有 extract_table_name 只解析 SELECT ... FROM table。新增 extract_write_table_name:
fn extract_write_table_name(sql: &str) -> Option<String> {
let upper = sql.trim().to_uppercase();
if upper.starts_with("INSERT") {
// INSERT INTO table_name ...
extract_after_keyword(&upper, "INTO")
} else if upper.starts_with("UPDATE") {
// UPDATE table_name SET ...
extract_first_identifier_after(&upper, "UPDATE")
} else if upper.starts_with("DELETE") {
// DELETE FROM table_name ...
extract_after_keyword(&upper, "FROM")
} else {
None
}
}
6. 实现变更清单
src/plugins/permissions.rs
| 操作 | 说明 |
|---|---|
新增 is_write_query(sql) |
检查是否 INSERT/UPDATE/DELETE |
新增 is_ddl_query(sql) |
DDL 黑名单检查 |
新增 extract_write_table_name(sql) |
提取写操作的目标表名 |
新增 is_protected_table(table) |
系统表保护 |
已有 is_table_writable(permissions, table) |
无需修改 |
src/plugins/host_common.rs
| 操作 | 说明 |
|---|---|
新增 HostContext::db_execute(&self, sql: &str) -> String |
核心实现:4 层安全检查 + 执行 + 返回 JSON |
src/plugins/js_host.rs
| 操作 | 说明 |
|---|---|
新增 dbExecute 函数注册 |
Host.dbExecute(sql: string) -> string |
src/plugins/lua_host.rs
| 操作 | 说明 |
|---|---|
新增 dbExecute 函数注册 |
Host.dbExecute(sql) -> string |
src/plugins/host.rs
| 操作 | 说明 |
|---|---|
新增 host_db_execute 函数 |
WASM ABI:(ptr, len) -> ptr |
src/plugins/mod.rs
| 操作 | 说明 |
|---|---|
| 无修改 | rows_to_json 不适用于写操作,新增 execute_result_to_json |
7. db_execute 核心实现
// host_common.rs
impl HostContext {
pub fn db_execute(&self, sql: &str) -> String {
// Layer 1: 只允许 INSERT/UPDATE/DELETE
if !PermissionChecker::is_write_query(sql) {
return r#"{"error":"only INSERT/UPDATE/DELETE are allowed"}"#.to_string();
}
// Layer 2: 拒绝 DDL(双重保险)
if PermissionChecker::is_ddl_query(sql) {
return r#"{"error":"DDL operations are not allowed"}"#.to_string();
}
// Layer 3: 表级权限
let table = match PermissionChecker::extract_write_table_name(sql) {
Some(t) => t,
None => return r#"{"error":"cannot parse table name from SQL"}"#.to_string(),
};
if PermissionChecker::is_protected_table(&table) {
return format!(r#"{{"error":"table '{table}' is protected and cannot be modified by plugins"}}"#);
}
if !PermissionChecker::is_table_writable(&self.permissions, &table) {
return format!(r#"{{"error":"no write permission for table: {table}"}}"#);
}
// 执行
let Some(pool) = &self.pool else {
return r#"{"error":"no database access"}"#.to_string();
};
let handle = tokio::runtime::Handle::current();
let sql = crate::db::dialect::translate(sql).into_owned();
tokio::task::block_in_place(|| {
match handle.block_on(async {
let result = sqlx::query(&sql).execute(pool).await?;
Ok::<_, sqlx::Error>(result)
}) {
Ok(r) => format!(r#"{{"rows_affected":{}}}"#, r.rows_affected()),
Err(e) => format!(r#"{{"error":"{e}"}}"#),
}
})
}
}
8. 测试计划
单元测试 (permissions.rs)
| 测试 | 输入 | 预期 |
|---|---|---|
is_write_query("INSERT INTO ...") |
INSERT | true |
is_write_query("UPDATE ...") |
UPDATE | true |
is_write_query("DELETE FROM ...") |
DELETE | true |
is_write_query("SELECT ...") |
SELECT | false |
is_write_query("DROP TABLE ...") |
DDL | false |
is_ddl_query("CREATE TABLE ...") |
CREATE | true |
is_ddl_query("ALTER TABLE ...") |
ALTER | true |
is_ddl_query("INSERT INTO ...") |
INSERT | false |
extract_write_table_name("INSERT INTO orders ...") |
INSERT | Some("orders") |
extract_write_table_name("UPDATE products SET ...") |
UPDATE | Some("products") |
extract_write_table_name("DELETE FROM cart_items ...") |
DELETE | Some("cart_items") |
extract_write_table_name("SELECT * FROM orders") |
SELECT | None |
is_protected_table("users") |
users | true |
is_protected_table("orders") |
orders | false |
is_table_writable(["write:orders"], "orders") |
— | true |
is_table_writable(["read:orders"], "orders") |
— | false |
is_table_writable(["orders"], "orders") |
— | true |
集成测试
| 测试 | 说明 |
|---|---|
| 插件写入有权限的表 | 成功,返回 rows_affected |
| 插件写入无权限的表 | 返回 error |
| 插件写入系统表 | 返回 protected error |
| 插件执行 SELECT | 返回 "only INSERT/UPDATE/DELETE allowed" |
| 插件执行 DDL | 返回 "DDL not allowed" |
9. 工作量
| 文件 | 操作 | 时间 |
|---|---|---|
permissions.rs |
新增 4 个函数 + 15 个单元测试 | 2h |
host_common.rs |
新增 db_execute 方法 |
1h |
js_host.rs |
注册 dbExecute |
30min |
lua_host.rs |
注册 dbExecute |
30min |
host.rs |
注册 host_db_execute |
30min |
| 集成测试 | API 端到端测试 | 1h |
| 合计 | ~5-6h |