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
116type ActiveWriteKey<'a> = (&'a AccessKeyDef, &'a str, &'a Vec<Value>);
118
119fn active_write_keys<'a>(
121 schema: &'a Schema,
122 c: &'a Context,
123) -> Result<Vec<ActiveWriteKey<'a>>, String> {
124 let mut out = Vec::new();
125 for def in &schema.access_keys {
126 if is_bypassed(def, c) {
127 continue;
128 }
129 let Some(key) = def.write.as_deref() else {
130 continue;
131 };
132 let list = resolve_list(c, key, &def.field)?;
133 out.push((def, key, list));
134 }
135 Ok(out)
136}
137
138fn is_array_field(schema: &Schema, field: &str) -> bool {
139 schema
140 .fields
141 .get(field)
142 .map(|f| f.field_type == "array")
143 .unwrap_or(false)
144}
145
146fn empty_list_err(key: &str, field: &str) -> String {
147 format!("{ERR_ACCESS_PREFIX}清单 \"{key}\" 为空,拒绝写入字段 \"{field}\"")
148}
149
150pub fn validate_write_values(
155 schema: &Schema,
156 ctx: Option<&Context>,
157 data: &Map<String, Value>,
158) -> Result<(), String> {
159 let Some(c) = ctx.filter(|c| !c.internal) else {
160 return Ok(());
161 };
162 for (def, key, list) in active_write_keys(schema, c)? {
163 let field = def.field.as_str();
164 if let Some(path) = data.keys().find(|k| k.starts_with(&format!("{field}."))) {
165 return Err(format!(
166 "{ERR_ACCESS_PREFIX}不允许通过点号路径 \"{path}\" 改写受 accessKeys 管控的字段 \"{field}\""
167 ));
168 }
169 let Some(target) = data.get(field) else {
170 continue;
171 };
172 if list.is_empty() {
173 return Err(empty_list_err(key, field));
174 }
175 if is_array_field(schema, field) {
176 let Some(items) = target.as_array() else {
177 return Err(format!(
178 "{ERR_ACCESS_PREFIX}字段 \"{field}\" 为数组,目标值必须为数组(实际 {target})"
179 ));
180 };
181 for item in items {
182 if !list.contains(item) {
183 return Err(format!(
184 "{ERR_ACCESS_PREFIX}字段 \"{field}\" 的目标数组含清单 \"{key}\" 之外的元素 {item}"
185 ));
186 }
187 }
188 } else if !list.contains(target) {
189 return Err(format!(
190 "{ERR_ACCESS_PREFIX}字段 \"{field}\" 的目标值 {target} 不在清单 \"{key}\" 中"
191 ));
192 }
193 }
194 Ok(())
195}
196
197pub fn validate_doc_values(
199 schema: &Schema,
200 ctx: Option<&Context>,
201 doc: &Value,
202) -> Result<(), String> {
203 let Some(c) = ctx.filter(|c| !c.internal) else {
204 return Ok(());
205 };
206 for (def, key, list) in active_write_keys(schema, c)? {
207 let field = def.field.as_str();
208 if list.is_empty() {
209 return Err(empty_list_err(key, field));
210 }
211 if is_array_field(schema, field) {
212 match doc.get(field) {
213 None | Some(Value::Null) => {}
214 Some(Value::Array(items)) => {
215 for item in items {
216 if !list.contains(item) {
217 return Err(format!(
218 "{ERR_ACCESS_PREFIX}文档字段 \"{field}\" 含清单 \"{key}\" 之外的元素 {item}"
219 ));
220 }
221 }
222 }
223 Some(other) => {
224 return Err(format!(
225 "{ERR_ACCESS_PREFIX}文档字段 \"{field}\" 非数组,无法做子集判定(实际 {other})"
226 ))
227 }
228 }
229 } else {
230 match doc.get(field) {
231 None | Some(Value::Null) => {
232 return Err(format!(
233 "{ERR_ACCESS_PREFIX}文档字段 \"{field}\" 缺失,不在清单 \"{key}\" 中"
234 ))
235 }
236 Some(v) if !list.contains(v) => {
237 return Err(format!(
238 "{ERR_ACCESS_PREFIX}文档字段 \"{field}\" 的值 {v} 不在清单 \"{key}\" 中"
239 ))
240 }
241 Some(_) => {}
242 }
243 }
244 }
245 Ok(())
246}
247
248pub fn validate_raw_operators(
251 schema: &Schema,
252 ctx: Option<&Context>,
253 update: &Value,
254) -> Result<(), String> {
255 let Some(c) = ctx.filter(|c| !c.internal) else {
256 return Ok(());
257 };
258 let pairs = active_write_keys(schema, c)?;
259 if pairs.is_empty() {
260 return Ok(());
261 }
262 let Some(obj) = update.as_object() else {
263 return Ok(());
264 };
265 for (op, val) in obj {
266 if !op.starts_with('$') || op == "$set" {
267 continue;
268 }
269 let Some(fields) = val.as_object() else {
270 continue;
271 };
272 for name in fields.keys() {
273 let root = name.split('.').next().unwrap_or(name);
274 if let Some((def, key, _)) = pairs.iter().find(|(d, _, _)| d.field == root) {
275 if def.write.as_deref() == Some(*key) {
276 return Err(format!(
277 "{ERR_ACCESS_PREFIX}操作符 {op} 不允许改写受 accessKeys 管控的字段 \"{root}\""
278 ));
279 }
280 }
281 }
282 }
283 Ok(())
284}
285
286pub fn probe_fields(schema: &Schema, ctx: Option<&Context>) -> Result<Vec<String>, String> {
288 let Some(c) = ctx.filter(|c| !c.internal) else {
289 return Ok(Vec::new());
290 };
291 Ok(active_write_keys(schema, c)?
292 .into_iter()
293 .map(|(def, _, _)| def.field.clone())
294 .collect())
295}