use serde_json::{json, Map, Value};
use crate::permission::Context;
use crate::schema::{AccessKeyDef, Schema};
pub const ERR_ACCESS_PREFIX: &str = "ERR_PERMISSION:ACCESS:";
pub fn is_bypassed(def: &AccessKeyDef, ctx: &Context) -> bool {
if ctx.internal {
return true;
}
let mut roles: Vec<&str> = Vec::new();
if let Some(rs) = &ctx.roles {
roles.extend(rs.iter().map(String::as_str));
}
if let Some(r) = &ctx.role {
roles.push(r.as_str());
}
roles
.iter()
.any(|r| def.bypass_roles.iter().any(|b| b == r))
}
pub fn resolve_list<'a>(
ctx: &'a Context,
ctx_key: &str,
field: &str,
) -> Result<&'a Vec<Value>, String> {
if let Some(err) = ctx.access_list_errors.get(ctx_key) {
return Err(format!(
"{ERR_ACCESS_PREFIX}ctx 键 \"{ctx_key}\" 的{err}(字段 \"{field}\" 的行级权限清单非法)"
));
}
match ctx.access_lists.get(ctx_key) {
Some(Value::Array(items)) => Ok(items),
Some(_) => Err(format!(
"{ERR_ACCESS_PREFIX}ctx 键 \"{ctx_key}\" 的清单必须为数组"
)),
None => Err(format!(
"{ERR_ACCESS_PREFIX}ctx 缺少字段 \"{field}\" 的行级权限清单键 \"{ctx_key}\"\
(角色未命中 bypassRoles,拒绝静默放行)"
)),
}
}
pub fn row_condition(
schema: &Schema,
ctx: Option<&Context>,
action: &str,
) -> Result<Option<Value>, String> {
let Some(c) = ctx else {
return Ok(None);
};
if !matches!(action, "read" | "update" | "remove") {
return Ok(None);
}
let mut conds: Vec<Value> = Vec::new();
for def in &schema.access_keys {
if is_bypassed(def, c) {
continue;
}
let key = if action == "read" {
Some(def.read.as_str())
} else {
def.write.as_deref()
};
let Some(key) = key else {
continue;
};
let list = resolve_list(c, key, &def.field)?;
let mut obj = serde_json::Map::new();
obj.insert(def.field.clone(), json!({ "$in": list.clone() }));
conds.push(Value::Object(obj));
}
Ok(match conds.len() {
0 => None,
1 => Some(conds.remove(0)),
_ => Some(json!({ "$and": conds })),
})
}
pub fn write_restricted(schema: &Schema, ctx: Option<&Context>) -> Result<bool, String> {
let Some(c) = ctx.filter(|c| !c.internal) else {
return Ok(false);
};
Ok(!active_write_keys(schema, c)?.is_empty())
}
type ActiveWriteKey<'a> = (&'a AccessKeyDef, &'a str, &'a Vec<Value>);
fn active_write_keys<'a>(
schema: &'a Schema,
c: &'a Context,
) -> Result<Vec<ActiveWriteKey<'a>>, String> {
let mut out = Vec::new();
for def in &schema.access_keys {
if is_bypassed(def, c) {
continue;
}
let Some(key) = def.write.as_deref() else {
continue;
};
let list = resolve_list(c, key, &def.field)?;
out.push((def, key, list));
}
Ok(out)
}
fn is_array_field(schema: &Schema, field: &str) -> bool {
schema
.fields
.get(field)
.map(|f| f.field_type == "array")
.unwrap_or(false)
}
fn empty_list_err(key: &str, field: &str) -> String {
format!("{ERR_ACCESS_PREFIX}清单 \"{key}\" 为空,拒绝写入字段 \"{field}\"")
}
pub fn validate_write_values(
schema: &Schema,
ctx: Option<&Context>,
data: &Map<String, Value>,
) -> Result<(), String> {
let Some(c) = ctx.filter(|c| !c.internal) else {
return Ok(());
};
for (def, key, list) in active_write_keys(schema, c)? {
let field = def.field.as_str();
if let Some(path) = data.keys().find(|k| k.starts_with(&format!("{field}."))) {
return Err(format!(
"{ERR_ACCESS_PREFIX}不允许通过点号路径 \"{path}\" 改写受 accessKeys 管控的字段 \"{field}\""
));
}
let Some(target) = data.get(field) else {
continue;
};
if list.is_empty() {
return Err(empty_list_err(key, field));
}
if is_array_field(schema, field) {
let Some(items) = target.as_array() else {
return Err(format!(
"{ERR_ACCESS_PREFIX}字段 \"{field}\" 为数组,目标值必须为数组(实际 {target})"
));
};
for item in items {
if !list.contains(item) {
return Err(format!(
"{ERR_ACCESS_PREFIX}字段 \"{field}\" 的目标数组含清单 \"{key}\" 之外的元素 {item}"
));
}
}
} else if !list.contains(target) {
return Err(format!(
"{ERR_ACCESS_PREFIX}字段 \"{field}\" 的目标值 {target} 不在清单 \"{key}\" 中"
));
}
}
Ok(())
}
pub fn validate_doc_values(
schema: &Schema,
ctx: Option<&Context>,
doc: &Value,
) -> Result<(), String> {
let Some(c) = ctx.filter(|c| !c.internal) else {
return Ok(());
};
for (def, key, list) in active_write_keys(schema, c)? {
let field = def.field.as_str();
if list.is_empty() {
return Err(empty_list_err(key, field));
}
if is_array_field(schema, field) {
match doc.get(field) {
None | Some(Value::Null) => {}
Some(Value::Array(items)) => {
for item in items {
if !list.contains(item) {
return Err(format!(
"{ERR_ACCESS_PREFIX}文档字段 \"{field}\" 含清单 \"{key}\" 之外的元素 {item}"
));
}
}
}
Some(other) => {
return Err(format!(
"{ERR_ACCESS_PREFIX}文档字段 \"{field}\" 非数组,无法做子集判定(实际 {other})"
))
}
}
} else {
match doc.get(field) {
None | Some(Value::Null) => {
return Err(format!(
"{ERR_ACCESS_PREFIX}文档字段 \"{field}\" 缺失,不在清单 \"{key}\" 中"
))
}
Some(v) if !list.contains(v) => {
return Err(format!(
"{ERR_ACCESS_PREFIX}文档字段 \"{field}\" 的值 {v} 不在清单 \"{key}\" 中"
))
}
Some(_) => {}
}
}
}
Ok(())
}
pub fn validate_raw_operators(
schema: &Schema,
ctx: Option<&Context>,
update: &Value,
) -> Result<(), String> {
let Some(c) = ctx.filter(|c| !c.internal) else {
return Ok(());
};
let pairs = active_write_keys(schema, c)?;
if pairs.is_empty() {
return Ok(());
}
let Some(obj) = update.as_object() else {
return Ok(());
};
for (op, val) in obj {
if !op.starts_with('$') || op == "$set" {
continue;
}
let Some(fields) = val.as_object() else {
continue;
};
for name in fields.keys() {
let root = name.split('.').next().unwrap_or(name);
if let Some((def, key, _)) = pairs.iter().find(|(d, _, _)| d.field == root) {
if def.write.as_deref() == Some(*key) {
return Err(format!(
"{ERR_ACCESS_PREFIX}操作符 {op} 不允许改写受 accessKeys 管控的字段 \"{root}\""
));
}
}
}
}
Ok(())
}
pub fn probe_fields(schema: &Schema, ctx: Option<&Context>) -> Result<Vec<String>, String> {
let Some(c) = ctx.filter(|c| !c.internal) else {
return Ok(Vec::new());
};
Ok(active_write_keys(schema, c)?
.into_iter()
.map(|(def, _, _)| def.field.clone())
.collect())
}