rust_store_core/
access.rs1use serde_json::{json, Map, Value};
14
15use crate::permission::Context;
16use crate::schema::{AccessKeyDef, Schema};
17
18pub const ERR_ACCESS_PREFIX: &str = "ERR_PERMISSION:ACCESS:";
20
21pub fn is_bypassed(def: &AccessKeyDef, ctx: &Context) -> bool {
23 if ctx.internal {
24 return true;
25 }
26 let mut roles: Vec<&str> = Vec::new();
27 if let Some(rs) = &ctx.roles {
28 roles.extend(rs.iter().map(String::as_str));
29 }
30 if let Some(r) = &ctx.role {
31 roles.push(r.as_str());
32 }
33 roles
34 .iter()
35 .any(|r| def.bypass_roles.iter().any(|b| b == r))
36}
37
38pub fn resolve_list<'a>(
40 ctx: &'a Context,
41 ctx_key: &str,
42 field: &str,
43) -> Result<&'a Vec<Value>, String> {
44 if let Some(err) = ctx.access_list_errors.get(ctx_key) {
45 return Err(format!(
46 "{ERR_ACCESS_PREFIX}ctx 键 \"{ctx_key}\" 的{err}(字段 \"{field}\" 的行级权限清单非法)"
47 ));
48 }
49 match ctx.access_lists.get(ctx_key) {
50 Some(Value::Array(items)) => Ok(items),
51 Some(_) => Err(format!(
52 "{ERR_ACCESS_PREFIX}ctx 键 \"{ctx_key}\" 的清单必须为数组"
53 )),
54 None => Err(format!(
55 "{ERR_ACCESS_PREFIX}ctx 缺少字段 \"{field}\" 的行级权限清单键 \"{ctx_key}\"\
56 (角色未命中 bypassRoles,拒绝静默放行)"
57 )),
58 }
59}
60
61pub fn row_condition(
67 schema: &Schema,
68 ctx: Option<&Context>,
69 action: &str,
70) -> Result<Option<Value>, String> {
71 let Some(c) = ctx else {
72 return Ok(None);
73 };
74 if !matches!(action, "read" | "update" | "remove") {
75 return Ok(None);
76 }
77 let mut conds: Vec<Value> = Vec::new();
78 for def in &schema.access_keys {
79 if is_bypassed(def, c) {
80 continue;
81 }
82 let key = if action == "read" {
83 Some(def.read.as_str())
84 } else {
85 def.write.as_deref()
86 };
87 let Some(key) = key else {
88 continue;
89 };
90 let list = resolve_list(c, key, &def.field)?;
91 let mut obj = serde_json::Map::new();
92 obj.insert(def.field.clone(), json!({ "$in": list.clone() }));
93 conds.push(Value::Object(obj));
94 }
95 Ok(match conds.len() {
96 0 => None,
97 1 => Some(conds.remove(0)),
98 _ => Some(json!({ "$and": conds })),
99 })
100}
101
102pub fn write_restricted(schema: &Schema, ctx: Option<&Context>) -> Result<bool, String> {
110 let Some(c) = ctx.filter(|c| !c.internal) else {
111 return Ok(false);
112 };
113 Ok(!active_write_keys(schema, c)?.is_empty())
114}
115
116fn active_write_keys<'a>(
118 schema: &'a Schema,
119 c: &'a Context,
120) -> Result<Vec<(&'a AccessKeyDef, &'a str, &'a Vec<Value>)>, String> {
121 let mut out = Vec::new();
122 for def in &schema.access_keys {
123 if is_bypassed(def, c) {
124 continue;
125 }
126 let Some(key) = def.write.as_deref() else {
127 continue;
128 };
129 let list = resolve_list(c, key, &def.field)?;
130 out.push((def, key, list));
131 }
132 Ok(out)
133}
134
135fn is_array_field(schema: &Schema, field: &str) -> bool {
136 schema
137 .fields
138 .get(field)
139 .map(|f| f.field_type == "array")
140 .unwrap_or(false)
141}
142
143fn empty_list_err(key: &str, field: &str) -> String {
144 format!("{ERR_ACCESS_PREFIX}清单 \"{key}\" 为空,拒绝写入字段 \"{field}\"")
145}
146
147pub fn validate_write_values(
152 schema: &Schema,
153 ctx: Option<&Context>,
154 data: &Map<String, Value>,
155) -> Result<(), String> {
156 let Some(c) = ctx.filter(|c| !c.internal) else {
157 return Ok(());
158 };
159 for (def, key, list) in active_write_keys(schema, c)? {
160 let field = def.field.as_str();
161 if let Some(path) = data.keys().find(|k| k.starts_with(&format!("{field}."))) {
162 return Err(format!(
163 "{ERR_ACCESS_PREFIX}不允许通过点号路径 \"{path}\" 改写受 accessKeys 管控的字段 \"{field}\""
164 ));
165 }
166 let Some(target) = data.get(field) else {
167 continue;
168 };
169 if list.is_empty() {
170 return Err(empty_list_err(key, field));
171 }
172 if is_array_field(schema, field) {
173 let Some(items) = target.as_array() else {
174 return Err(format!(
175 "{ERR_ACCESS_PREFIX}字段 \"{field}\" 为数组,目标值必须为数组(实际 {target})"
176 ));
177 };
178 for item in items {
179 if !list.contains(item) {
180 return Err(format!(
181 "{ERR_ACCESS_PREFIX}字段 \"{field}\" 的目标数组含清单 \"{key}\" 之外的元素 {item}"
182 ));
183 }
184 }
185 } else if !list.contains(target) {
186 return Err(format!(
187 "{ERR_ACCESS_PREFIX}字段 \"{field}\" 的目标值 {target} 不在清单 \"{key}\" 中"
188 ));
189 }
190 }
191 Ok(())
192}
193
194pub fn validate_doc_values(
196 schema: &Schema,
197 ctx: Option<&Context>,
198 doc: &Value,
199) -> Result<(), String> {
200 let Some(c) = ctx.filter(|c| !c.internal) else {
201 return Ok(());
202 };
203 for (def, key, list) in active_write_keys(schema, c)? {
204 let field = def.field.as_str();
205 if list.is_empty() {
206 return Err(empty_list_err(key, field));
207 }
208 if is_array_field(schema, field) {
209 match doc.get(field) {
210 None | Some(Value::Null) => {}
211 Some(Value::Array(items)) => {
212 for item in items {
213 if !list.contains(item) {
214 return Err(format!(
215 "{ERR_ACCESS_PREFIX}文档字段 \"{field}\" 含清单 \"{key}\" 之外的元素 {item}"
216 ));
217 }
218 }
219 }
220 Some(other) => {
221 return Err(format!(
222 "{ERR_ACCESS_PREFIX}文档字段 \"{field}\" 非数组,无法做子集判定(实际 {other})"
223 ))
224 }
225 }
226 } else {
227 match doc.get(field) {
228 None | Some(Value::Null) => {
229 return Err(format!(
230 "{ERR_ACCESS_PREFIX}文档字段 \"{field}\" 缺失,不在清单 \"{key}\" 中"
231 ))
232 }
233 Some(v) if !list.contains(v) => {
234 return Err(format!(
235 "{ERR_ACCESS_PREFIX}文档字段 \"{field}\" 的值 {v} 不在清单 \"{key}\" 中"
236 ))
237 }
238 Some(_) => {}
239 }
240 }
241 }
242 Ok(())
243}
244
245pub fn validate_raw_operators(
248 schema: &Schema,
249 ctx: Option<&Context>,
250 update: &Value,
251) -> Result<(), String> {
252 let Some(c) = ctx.filter(|c| !c.internal) else {
253 return Ok(());
254 };
255 let pairs = active_write_keys(schema, c)?;
256 if pairs.is_empty() {
257 return Ok(());
258 }
259 let Some(obj) = update.as_object() else {
260 return Ok(());
261 };
262 for (op, val) in obj {
263 if !op.starts_with('$') || op == "$set" {
264 continue;
265 }
266 let Some(fields) = val.as_object() else {
267 continue;
268 };
269 for name in fields.keys() {
270 let root = name.split('.').next().unwrap_or(name);
271 if let Some((def, key, _)) = pairs.iter().find(|(d, _, _)| d.field == root) {
272 if def.write.as_deref() == Some(*key) {
273 return Err(format!(
274 "{ERR_ACCESS_PREFIX}操作符 {op} 不允许改写受 accessKeys 管控的字段 \"{root}\""
275 ));
276 }
277 }
278 }
279 }
280 Ok(())
281}
282
283pub fn probe_fields(schema: &Schema, ctx: Option<&Context>) -> Result<Vec<String>, String> {
285 let Some(c) = ctx.filter(|c| !c.internal) else {
286 return Ok(Vec::new());
287 };
288 Ok(active_write_keys(schema, c)?
289 .into_iter()
290 .map(|(def, _, _)| def.field.clone())
291 .collect())
292}