use std::rc::Rc;
use indexmap::IndexMap;
use super::{
Budget, Context, Decision, Documents, Filter, FilterOp, Method, Request, Trace, Value,
constraint::{Comparator, Simple},
denied, no_match, request_value, statements,
value::{self, Val},
};
use crate::{
Ruleset, ast,
paths::{Part, covering_parts},
};
const MAX_WAYS: usize = 1_000;
type Fields = Vec<(Vec<String>, Vec<(FilterOp, Value)>)>;
enum Way {
Document(Vec<String>),
Fields(Fields),
}
enum Atom {
Field(Vec<String>, FilterOp, Value),
Name(Vec<String>),
}
pub(super) fn authorize(
ruleset: &Ruleset,
service: &ast::Service,
request: &Request,
documents: &dyn Documents,
budget: &mut Budget,
trace: &mut Trace,
) -> Decision {
let query = request.query.clone().unwrap_or_default();
let Some(ways) = ways(query.filter.as_ref()) else {
return Decision::Deny("\nThe query's filter has too many disjunctions.".to_owned());
};
for way in ways {
let outcome = match way {
Way::Document(path) => {
let parts: Vec<Part<'_>> = path.iter().map(|s| Part::Segment(s)).collect();
let resource = documents
.get(&path)
.map_or(Val::Null, |d| value::resource(&d));
decide(
ruleset,
service,
request,
Some(&path),
&parts,
resource,
documents,
budget,
trace,
)
}
Way::Fields(fields) => {
let mut parts: Vec<Part<'_>> =
request.path.iter().map(|s| Part::Segment(s)).collect();
if let Some(id) = &query.group {
parts.push(Part::UnknownRun);
if !id.is_empty() {
parts.push(Part::Segment(id));
}
}
parts.push(Part::UnknownId);
let mut resource = IndexMap::new();
resource.insert("data".to_owned(), data(&fields));
let resource = Val::Map(Rc::new(resource));
decide(
ruleset, service, request, None, &parts, resource, documents, budget, trace,
)
}
};
match outcome {
Ok(()) => {}
Err(None) => return no_match(),
Err(Some(entries)) => return denied(&entries),
}
}
Decision::Allow
}
#[allow(clippy::too_many_arguments)]
fn decide(
ruleset: &Ruleset,
service: &ast::Service,
request: &Request,
document: Option<&Vec<String>>,
parts: &[Part<'_>],
resource: Val,
documents: &dyn Documents,
budget: &mut Budget,
trace: &mut Trace,
) -> Result<(), Option<Vec<String>>> {
let coverings = covering_parts(ruleset.version, service, parts);
let applying = statements(&coverings, Method::List);
let mut request_value = request_value(request, Method::List);
let path = match document {
Some(path) => Val::path(path.clone()),
None if parts.contains(&Part::UnknownRun) => Val::Unknown,
None => Val::path(request.path.clone()),
};
if let Val::Map(map) = &mut request_value {
Rc::make_mut(map).insert("path".to_owned(), path);
}
if applying.is_empty() {
trace.evaluating(Method::List, &request_value, &resource);
return Err(None);
}
let context = Context {
ruleset,
service,
request: request_value,
resource,
target: document.unwrap_or(&request.path),
documents,
};
context
.decide(&applying, Method::List, budget, trace)
.map_err(Some)
}
fn ways(filter: Option<&Filter>) -> Option<Vec<Way>> {
let conjunctions = match filter {
Some(filter) => dnf(filter)?,
None => vec![Vec::new()],
};
let mut ways: Vec<Way> = conjunctions.into_iter().map(way).collect();
if ways.is_empty() {
ways.push(Way::Fields(Vec::new()));
}
Some(ways)
}
fn way(atoms: Vec<Atom>) -> Way {
let mut fields: Fields = Vec::new();
for atom in atoms {
match atom {
Atom::Name(path) => return Way::Document(path),
Atom::Field(field, op, value) => match fields.iter_mut().find(|(f, _)| *f == field) {
Some((_, filters)) => filters.push((op, value)),
None => fields.push((field, vec![(op, value)])),
},
}
}
Way::Fields(fields)
}
fn dnf(filter: &Filter) -> Option<Vec<Vec<Atom>>> {
let out = match filter {
Filter::And(filters) => {
let mut product: Vec<Vec<Atom>> = vec![Vec::new()];
for filter in filters {
let terms = dnf(filter)?;
if product.len().saturating_mul(terms.len()) > MAX_WAYS {
return None;
}
product = product
.iter()
.flat_map(|left| {
terms.iter().map(move |right| {
left.iter().chain(right).map(Atom::copy).collect::<Vec<_>>()
})
})
.collect();
}
product
}
Filter::Or(filters) => {
let mut sum = Vec::new();
for filter in filters {
sum.extend(dnf(filter)?);
if sum.len() > MAX_WAYS {
return None;
}
}
sum
}
Filter::Field { field, op, value } => match (op, value) {
(FilterOp::In, Value::List(values)) => values
.iter()
.map(|v| vec![Atom::Field(field.clone(), FilterOp::Eq, v.clone())])
.collect(),
(FilterOp::ArrayContainsAny, Value::List(values)) => values
.iter()
.map(|v| {
vec![Atom::Field(
field.clone(),
FilterOp::ArrayContains,
v.clone(),
)]
})
.collect(),
_ => vec![vec![Atom::Field(field.clone(), *op, value.clone())]],
},
Filter::Name { op, value } => match (op, value) {
(FilterOp::Eq, Value::Path(path)) => vec![vec![Atom::Name(path.clone())]],
(FilterOp::In, Value::List(names)) => names
.iter()
.filter_map(|name| match name {
Value::Path(path) => Some(vec![Atom::Name(path.clone())]),
_ => None,
})
.collect(),
_ => vec![Vec::new()],
},
};
(out.len() <= MAX_WAYS).then_some(out)
}
impl Atom {
fn copy(&self) -> Self {
match self {
Self::Field(field, op, value) => Self::Field(field.clone(), *op, value.clone()),
Self::Name(path) => Self::Name(path.clone()),
}
}
}
fn data(fields: &Fields) -> Val {
let mut root = IndexMap::new();
for (field, filters) in fields {
insert(&mut root, field, known(filters));
}
freeze(root)
}
fn known(filters: &[(FilterOp, Value)]) -> Val {
if let [(op, value)] = filters {
match op {
FilterOp::Eq => return Val::from(value),
FilterOp::IsNull => return Val::Null,
FilterOp::IsNan => return Val::Float(f64::NAN),
_ => {}
}
}
let mut simple = Vec::with_capacity(filters.len());
for (op, value) in filters {
let one = |comparator, value: &Value| Simple {
comparator,
value: Val::from(value),
};
match op {
FilterOp::Eq => simple.push(one(Comparator::Eq, value)),
FilterOp::Ne => simple.push(one(Comparator::Neq, value)),
FilterOp::Lt => simple.push(one(Comparator::Lt, value)),
FilterOp::Le => simple.push(one(Comparator::Lte, value)),
FilterOp::Gt => simple.push(one(Comparator::Gt, value)),
FilterOp::Ge => simple.push(one(Comparator::Gte, value)),
FilterOp::ArrayContains => simple.push(one(Comparator::ListContains, value)),
FilterOp::NotIn => {
if let Value::List(values) = value {
simple.extend(values.iter().map(|v| one(Comparator::Neq, v)));
}
}
FilterOp::IsNull => simple.push(one(Comparator::Eq, &Value::Null)),
FilterOp::IsNotNull => simple.push(one(Comparator::Neq, &Value::Null)),
FilterOp::IsNan => simple.push(one(Comparator::Eq, &Value::Float(f64::NAN))),
FilterOp::IsNotNan => simple.push(one(Comparator::Neq, &Value::Float(f64::NAN))),
FilterOp::In | FilterOp::ArrayContainsAny => {}
}
}
Val::Constraint(Rc::from(simple))
}
enum Node {
Value(Val),
Map(IndexMap<String, Node>),
}
fn insert(map: &mut IndexMap<String, Node>, field: &[String], value: Val) {
let Some((first, rest)) = field.split_first() else {
return;
};
if rest.is_empty() {
map.insert(first.clone(), Node::Value(value));
return;
}
let node = map
.entry(first.clone())
.or_insert_with(|| Node::Map(IndexMap::new()));
if let Node::Value(_) = node {
*node = Node::Map(IndexMap::new());
}
if let Node::Map(inner) = node {
insert(inner, rest, value);
}
}
fn freeze(map: IndexMap<String, Node>) -> Val {
Val::Map(Rc::new(
map.into_iter()
.map(|(key, node)| {
let value = match node {
Node::Value(v) => v,
Node::Map(inner) => freeze(inner),
};
(key, value)
})
.collect(),
))
}