From b0a92faf4c3de9f1aaf5027c81219110d283e5a7 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Fri, 29 Dec 2023 11:01:58 +0100 Subject: [PATCH 01/18] WIP closures - recursive closure evaluation - no support for serialization nor parsing --- biscuit-auth/src/datalog/expression.rs | 133 +++++++++++++++++++++++-- biscuit-auth/src/format/convert.rs | 2 + biscuit-auth/src/token/builder.rs | 2 + 3 files changed, 128 insertions(+), 9 deletions(-) diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index 8a5dd13a..0a373537 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -15,6 +15,8 @@ pub enum Op { Value(Term), Unary(Unary), Binary(Binary), + Suspend, + Unsuspend, } /// Unary operation code @@ -83,6 +85,33 @@ pub enum Binary { } impl Binary { + fn evaluate_with_closure( + &self, + left: Term, + right: Vec, + values: &HashMap, + symbols: &mut TemporarySymbolTable, + ) -> Result { + match (self, left) { + (Binary::Or, Term::Bool(true)) => Ok(Term::Bool(true)), + (Binary::Or, Term::Bool(false)) => { + let e = Expression { ops: right }; + match e.evaluate(values, symbols)? { + Term::Bool(b) => Ok(Term::Bool(b)), + _ => Err(error::Expression::InvalidType), + } + } + (Binary::And, Term::Bool(false)) => Ok(Term::Bool(false)), + (Binary::And, Term::Bool(true)) => { + let e = Expression { ops: right }; + match e.evaluate(values, symbols)? { + Term::Bool(b) => Ok(Term::Bool(b)), + _ => Err(error::Expression::InvalidType), + } + } + (_, _) => Err(error::Expression::InvalidType), + } + } fn evaluate( &self, left: Term, @@ -242,35 +271,72 @@ impl Binary { } } +#[derive(Clone, Debug)] +enum StackElem { + Closure(Vec), + Term(Term), +} + impl Expression { pub fn evaluate( &self, values: &HashMap, symbols: &mut TemporarySymbolTable, ) -> Result { - let mut stack: Vec = Vec::new(); + let mut stack: Vec = Vec::new(); + let mut depth = 0usize; for op in self.ops.iter() { - //println!("op: {:?}\t| stack: {:?}", op, stack); + println!("op: {:?}\t| stack: {:?}", op, stack); + if depth == 1 && op == &Op::Unsuspend { + match stack.last_mut() { + Some(StackElem::Closure(_)) => { + depth = 0; + continue; + } + _ => return Err(error::Expression::InvalidStack), + } + } else if depth > 0 { + match stack.last_mut() { + Some(StackElem::Closure(ops)) => { + ops.push(op.clone()); + if op == &Op::Suspend { + depth += 1; + } else if op == &Op::Unsuspend { + depth -= 1; + } + continue; + } + _ => return Err(error::Expression::InvalidStack), + } + } match op { Op::Value(Term::Variable(i)) => match values.get(i) { - Some(term) => stack.push(term.clone()), + Some(term) => stack.push(StackElem::Term(term.clone())), None => { //println!("unknown variable {}", i); return Err(error::Expression::UnknownVariable(*i)); } }, - Op::Value(term) => stack.push(term.clone()), + Op::Value(term) => stack.push(StackElem::Term(term.clone())), Op::Unary(unary) => match stack.pop() { - None => { + Some(StackElem::Term(term)) => { + stack.push(StackElem::Term(unary.evaluate(term, symbols)?)) + } + _ => { //println!("expected a value on the stack"); return Err(error::Expression::InvalidStack); } - Some(term) => stack.push(unary.evaluate(term, symbols)?), }, Op::Binary(binary) => match (stack.pop(), stack.pop()) { - (Some(right_term), Some(left_term)) => { - stack.push(binary.evaluate(left_term, right_term, symbols)?) + (Some(StackElem::Term(right_term)), Some(StackElem::Term(left_term))) => stack + .push(StackElem::Term( + binary.evaluate(left_term, right_term, symbols)?, + )), + (Some(StackElem::Closure(right_ops)), Some(StackElem::Term(left_term))) => { + stack.push(StackElem::Term( + binary.evaluate_with_closure(left_term, right_ops, values, symbols)?, + )) } _ => { @@ -278,11 +344,20 @@ impl Expression { return Err(error::Expression::InvalidStack); } }, + Op::Suspend => { + stack.push(StackElem::Closure(vec![])); + depth = 1; + } + Op::Unsuspend => {} } } + println!("stack: {stack:?}"); if stack.len() == 1 { - Ok(stack.remove(0)) + match stack.remove(0) { + StackElem::Term(t) => Ok(t), + _ => Err(error::Expression::InvalidStack), + } } else { Err(error::Expression::InvalidStack) } @@ -303,6 +378,8 @@ impl Expression { (Some(right), Some(left)) => stack.push(binary.print(left, right, symbols)), _ => return None, }, + Op::Suspend => {} + Op::Unsuspend => {} } } @@ -469,4 +546,42 @@ mod tests { assert_eq!(e3.print(&symbols).unwrap(), "1 + 2 < 3"); //panic!(); } + + #[test] + fn suspend() { + let symbols = SymbolTable::new(); + let mut symbols = TemporarySymbolTable::new(&symbols); + + let ops1 = vec![ + Op::Value(Term::Bool(true)), + Op::Suspend, + Op::Value(Term::Bool(false)), + Op::Suspend, + Op::Value(Term::Bool(false)), + Op::Unsuspend, + Op::Binary(Binary::Or), + Op::Unsuspend, + Op::Binary(Binary::Or), + ]; + let e1 = Expression { ops: ops1 }; + + let res1 = e1.evaluate(&HashMap::new(), &mut symbols).unwrap(); + assert_eq!(res1, Term::Bool(true)); + + let ops2 = vec![ + Op::Value(Term::Bool(false)), + Op::Suspend, + Op::Value(Term::Bool(false)), + Op::Suspend, + Op::Value(Term::Bool(true)), + Op::Unsuspend, + Op::Binary(Binary::Or), + Op::Unsuspend, + Op::Binary(Binary::Or), + ]; + let e2 = Expression { ops: ops2 }; + + let res2 = e2.evaluate(&HashMap::new(), &mut symbols).unwrap(); + assert_eq!(res2, Term::Bool(true)); + } } diff --git a/biscuit-auth/src/format/convert.rs b/biscuit-auth/src/format/convert.rs index a049ca93..e520e203 100644 --- a/biscuit-auth/src/format/convert.rs +++ b/biscuit-auth/src/format/convert.rs @@ -619,6 +619,8 @@ pub mod v2 { } as i32, }) } + Op::Suspend => todo!(), + Op::Unsuspend => todo!(), }; schema::Op { diff --git a/biscuit-auth/src/token/builder.rs b/biscuit-auth/src/token/builder.rs index 3335bd1c..50c7fa52 100644 --- a/biscuit-auth/src/token/builder.rs +++ b/biscuit-auth/src/token/builder.rs @@ -920,6 +920,8 @@ impl Convert for Op { datalog::Op::Value(t) => Op::Value(Term::convert_from(t, symbols)?), datalog::Op::Unary(u) => Op::Unary(u.clone()), datalog::Op::Binary(b) => Op::Binary(b.clone()), + datalog::Op::Suspend => todo!(), + datalog::Op::Unsuspend => todo!(), }) } } From 3486159eccbac40617a238f0d1e882d1fd9da093 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Sat, 30 Dec 2023 14:44:00 +0100 Subject: [PATCH 02/18] closures: remove recursion instead of calling `Expression.evaluate` recursively, push the closure to the ops vector --- biscuit-auth/src/datalog/expression.rs | 74 ++++++++++++++------------ 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index 0a373537..799a8557 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -88,26 +88,26 @@ impl Binary { fn evaluate_with_closure( &self, left: Term, - right: Vec, - values: &HashMap, - symbols: &mut TemporarySymbolTable, + mut right: Vec, + ops: &mut Vec, ) -> Result { match (self, left) { (Binary::Or, Term::Bool(true)) => Ok(Term::Bool(true)), (Binary::Or, Term::Bool(false)) => { - let e = Expression { ops: right }; - match e.evaluate(values, symbols)? { - Term::Bool(b) => Ok(Term::Bool(b)), - _ => Err(error::Expression::InvalidType), + ops.push(Op::Binary(Binary::Or)); + right.reverse(); + for op in right { + ops.push(op); } + Ok(Term::Bool(false)) } (Binary::And, Term::Bool(false)) => Ok(Term::Bool(false)), (Binary::And, Term::Bool(true)) => { - let e = Expression { ops: right }; - match e.evaluate(values, symbols)? { - Term::Bool(b) => Ok(Term::Bool(b)), - _ => Err(error::Expression::InvalidType), + ops.push(Op::Binary(Binary::And)); + for op in right { + ops.push(op); } + Ok(Term::Bool(true)) } (_, _) => Err(error::Expression::InvalidType), } @@ -286,9 +286,13 @@ impl Expression { let mut stack: Vec = Vec::new(); let mut depth = 0usize; - for op in self.ops.iter() { + let mut ops = self.ops.clone(); + ops.reverse(); + + while let Some(op) = ops.pop() { + println!("ops: {ops:?}"); println!("op: {:?}\t| stack: {:?}", op, stack); - if depth == 1 && op == &Op::Unsuspend { + if depth == 1 && op == Op::Unsuspend { match stack.last_mut() { Some(StackElem::Closure(_)) => { depth = 0; @@ -300,9 +304,9 @@ impl Expression { match stack.last_mut() { Some(StackElem::Closure(ops)) => { ops.push(op.clone()); - if op == &Op::Suspend { + if op == Op::Suspend { depth += 1; - } else if op == &Op::Unsuspend { + } else if op == Op::Unsuspend { depth -= 1; } continue; @@ -311,11 +315,11 @@ impl Expression { } } match op { - Op::Value(Term::Variable(i)) => match values.get(i) { + Op::Value(Term::Variable(i)) => match values.get(&i) { Some(term) => stack.push(StackElem::Term(term.clone())), None => { //println!("unknown variable {}", i); - return Err(error::Expression::UnknownVariable(*i)); + return Err(error::Expression::UnknownVariable(i)); } }, Op::Value(term) => stack.push(StackElem::Term(term.clone())), @@ -335,7 +339,7 @@ impl Expression { )), (Some(StackElem::Closure(right_ops)), Some(StackElem::Term(left_term))) => { stack.push(StackElem::Term( - binary.evaluate_with_closure(left_term, right_ops, values, symbols)?, + binary.evaluate_with_closure(left_term, right_ops, &mut ops)?, )) } @@ -552,30 +556,30 @@ mod tests { let symbols = SymbolTable::new(); let mut symbols = TemporarySymbolTable::new(&symbols); - let ops1 = vec![ - Op::Value(Term::Bool(true)), - Op::Suspend, - Op::Value(Term::Bool(false)), - Op::Suspend, - Op::Value(Term::Bool(false)), - Op::Unsuspend, - Op::Binary(Binary::Or), - Op::Unsuspend, - Op::Binary(Binary::Or), - ]; - let e1 = Expression { ops: ops1 }; - - let res1 = e1.evaluate(&HashMap::new(), &mut symbols).unwrap(); - assert_eq!(res1, Term::Bool(true)); + // let ops1 = vec![ + // Op::Value(Term::Bool(true)), + // Op::Suspend, + // Op::Value(Term::Bool(false)), + // Op::Suspend, + // Op::Value(Term::Bool(false)), + // Op::Unsuspend, + // Op::Binary(Binary::Or), + // Op::Unsuspend, + // Op::Binary(Binary::Or), + // ]; + // let e1 = Expression { ops: ops1 }; + + // let res1 = e1.evaluate(&HashMap::new(), &mut symbols).unwrap(); + // assert_eq!(res1, Term::Bool(true)); let ops2 = vec![ Op::Value(Term::Bool(false)), Op::Suspend, - Op::Value(Term::Bool(false)), + Op::Value(Term::Bool(true)), Op::Suspend, Op::Value(Term::Bool(true)), Op::Unsuspend, - Op::Binary(Binary::Or), + Op::Binary(Binary::And), Op::Unsuspend, Op::Binary(Binary::Or), ]; From ccc8612e9af8dfec624bad73bf64b626e4170961 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Mon, 1 Jan 2024 16:32:28 +0100 Subject: [PATCH 03/18] wip support for Binary::Any (recursive calls) no support for nested closures and proper parameter substitution yet --- biscuit-auth/src/datalog/expression.rs | 52 ++++++++++++++++++++++++-- biscuit-auth/src/format/convert.rs | 2 + biscuit-auth/src/token/builder.rs | 1 + 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index 799a8557..fd535497 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -17,6 +17,7 @@ pub enum Op { Binary(Binary), Suspend, Unsuspend, + Param(u8), } /// Unary operation code @@ -82,6 +83,7 @@ pub enum Binary { BitwiseOr, BitwiseXor, NotEqual, + Any, } impl Binary { @@ -90,6 +92,8 @@ impl Binary { left: Term, mut right: Vec, ops: &mut Vec, + values: &HashMap, + symbols: &mut TemporarySymbolTable, ) -> Result { match (self, left) { (Binary::Or, Term::Bool(true)) => Ok(Term::Bool(true)), @@ -109,6 +113,25 @@ impl Binary { } Ok(Term::Bool(true)) } + (Binary::Any, Term::Set(set_values)) => { + for value in set_values.iter() { + let ops = right + .clone() + .iter() + .map(|op| match op { + Op::Param(0) => Op::Value(value.clone()), + _ => op.clone(), + }) + .collect::>(); + let e = Expression { ops }; + match e.evaluate(values, symbols)? { + Term::Bool(false) => {} + Term::Bool(true) => return Ok(Term::Bool(true)), + _ => return Err(error::Expression::InvalidType), + }; + } + Ok(Term::Bool(false)) + } (_, _) => Err(error::Expression::InvalidType), } } @@ -267,6 +290,7 @@ impl Binary { Binary::BitwiseAnd => format!("{} & {}", left, right), Binary::BitwiseOr => format!("{} | {}", left, right), Binary::BitwiseXor => format!("{} ^ {}", left, right), + Binary::Any => todo!(), } } } @@ -338,9 +362,9 @@ impl Expression { binary.evaluate(left_term, right_term, symbols)?, )), (Some(StackElem::Closure(right_ops)), Some(StackElem::Term(left_term))) => { - stack.push(StackElem::Term( - binary.evaluate_with_closure(left_term, right_ops, &mut ops)?, - )) + stack.push(StackElem::Term(binary.evaluate_with_closure( + left_term, right_ops, &mut ops, values, symbols, + )?)) } _ => { @@ -353,6 +377,7 @@ impl Expression { depth = 1; } Op::Unsuspend => {} + Op::Param(_) => todo!(), } } println!("stack: {stack:?}"); @@ -384,6 +409,7 @@ impl Expression { }, Op::Suspend => {} Op::Unsuspend => {} + Op::Param(_) => {} } } @@ -552,7 +578,7 @@ mod tests { } #[test] - fn suspend() { + fn laziness() { let symbols = SymbolTable::new(); let mut symbols = TemporarySymbolTable::new(&symbols); @@ -588,4 +614,22 @@ mod tests { let res2 = e2.evaluate(&HashMap::new(), &mut symbols).unwrap(); assert_eq!(res2, Term::Bool(true)); } + + #[test] + fn any() { + let symbols = SymbolTable::new(); + let mut symbols = TemporarySymbolTable::new(&symbols); + + let ops1 = vec![ + Op::Value(Term::Set([Term::Bool(false), Term::Bool(true)].into())), + Op::Suspend, + Op::Param(0), + Op::Unsuspend, + Op::Binary(Binary::Any), + ]; + let e1 = Expression { ops: ops1 }; + + let res1 = e1.evaluate(&HashMap::new(), &mut symbols).unwrap(); + assert_eq!(res1, Term::Bool(true)); + } } diff --git a/biscuit-auth/src/format/convert.rs b/biscuit-auth/src/format/convert.rs index e520e203..1ab3f0e4 100644 --- a/biscuit-auth/src/format/convert.rs +++ b/biscuit-auth/src/format/convert.rs @@ -616,11 +616,13 @@ pub mod v2 { Binary::BitwiseOr => Kind::BitwiseOr, Binary::BitwiseXor => Kind::BitwiseXor, Binary::NotEqual => Kind::NotEqual, + Binary::Any => todo!(), } as i32, }) } Op::Suspend => todo!(), Op::Unsuspend => todo!(), + Op::Param(_)=> todo!(), }; schema::Op { diff --git a/biscuit-auth/src/token/builder.rs b/biscuit-auth/src/token/builder.rs index 50c7fa52..54a334b5 100644 --- a/biscuit-auth/src/token/builder.rs +++ b/biscuit-auth/src/token/builder.rs @@ -922,6 +922,7 @@ impl Convert for Op { datalog::Op::Binary(b) => Op::Binary(b.clone()), datalog::Op::Suspend => todo!(), datalog::Op::Unsuspend => todo!(), + datalog::Op::Param(_) => todo!(), }) } } From 91c852afca84328940955bc276033cfd16d91d7f Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Tue, 2 Jan 2024 11:33:52 +0100 Subject: [PATCH 04/18] dedicated pre-aggregated "closure" op It also includes a list of parameter names for proper parameter evaluation --- biscuit-auth/src/datalog/expression.rs | 87 +++++++++----------------- biscuit-auth/src/format/convert.rs | 5 +- biscuit-auth/src/token/builder.rs | 3 +- 3 files changed, 34 insertions(+), 61 deletions(-) diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index fd535497..4e238a3c 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -15,9 +15,8 @@ pub enum Op { Value(Term), Unary(Unary), Binary(Binary), - Suspend, - Unsuspend, - Param(u8), + Param(String), + Closure(Vec, Vec), } /// Unary operation code @@ -91,13 +90,14 @@ impl Binary { &self, left: Term, mut right: Vec, + params: &[String], ops: &mut Vec, values: &HashMap, symbols: &mut TemporarySymbolTable, ) -> Result { - match (self, left) { - (Binary::Or, Term::Bool(true)) => Ok(Term::Bool(true)), - (Binary::Or, Term::Bool(false)) => { + match (self, left, params) { + (Binary::Or, Term::Bool(true), []) => Ok(Term::Bool(true)), + (Binary::Or, Term::Bool(false), []) => { ops.push(Op::Binary(Binary::Or)); right.reverse(); for op in right { @@ -105,21 +105,21 @@ impl Binary { } Ok(Term::Bool(false)) } - (Binary::And, Term::Bool(false)) => Ok(Term::Bool(false)), - (Binary::And, Term::Bool(true)) => { + (Binary::And, Term::Bool(false), []) => Ok(Term::Bool(false)), + (Binary::And, Term::Bool(true), []) => { ops.push(Op::Binary(Binary::And)); for op in right { ops.push(op); } Ok(Term::Bool(true)) } - (Binary::Any, Term::Set(set_values)) => { + (Binary::Any, Term::Set(set_values), [param_name]) => { for value in set_values.iter() { let ops = right .clone() .iter() .map(|op| match op { - Op::Param(0) => Op::Value(value.clone()), + Op::Param(p) if p == param_name => Op::Value(value.clone()), _ => op.clone(), }) .collect::>(); @@ -132,7 +132,7 @@ impl Binary { } Ok(Term::Bool(false)) } - (_, _) => Err(error::Expression::InvalidType), + (_, _, _) => Err(error::Expression::InvalidType), } } fn evaluate( @@ -297,7 +297,7 @@ impl Binary { #[derive(Clone, Debug)] enum StackElem { - Closure(Vec), + Closure(Vec, Vec), Term(Term), } @@ -308,7 +308,6 @@ impl Expression { symbols: &mut TemporarySymbolTable, ) -> Result { let mut stack: Vec = Vec::new(); - let mut depth = 0usize; let mut ops = self.ops.clone(); ops.reverse(); @@ -316,28 +315,7 @@ impl Expression { while let Some(op) = ops.pop() { println!("ops: {ops:?}"); println!("op: {:?}\t| stack: {:?}", op, stack); - if depth == 1 && op == Op::Unsuspend { - match stack.last_mut() { - Some(StackElem::Closure(_)) => { - depth = 0; - continue; - } - _ => return Err(error::Expression::InvalidStack), - } - } else if depth > 0 { - match stack.last_mut() { - Some(StackElem::Closure(ops)) => { - ops.push(op.clone()); - if op == Op::Suspend { - depth += 1; - } else if op == Op::Unsuspend { - depth -= 1; - } - continue; - } - _ => return Err(error::Expression::InvalidStack), - } - } + match op { Op::Value(Term::Variable(i)) => match values.get(&i) { Some(term) => stack.push(StackElem::Term(term.clone())), @@ -361,22 +339,21 @@ impl Expression { .push(StackElem::Term( binary.evaluate(left_term, right_term, symbols)?, )), - (Some(StackElem::Closure(right_ops)), Some(StackElem::Term(left_term))) => { - stack.push(StackElem::Term(binary.evaluate_with_closure( - left_term, right_ops, &mut ops, values, symbols, - )?)) - } + ( + Some(StackElem::Closure(params, right_ops)), + Some(StackElem::Term(left_term)), + ) => stack.push(StackElem::Term(binary.evaluate_with_closure( + left_term, right_ops, ¶ms, &mut ops, values, symbols, + )?)), _ => { //println!("expected two values on the stack"); return Err(error::Expression::InvalidStack); } }, - Op::Suspend => { - stack.push(StackElem::Closure(vec![])); - depth = 1; + Op::Closure(param, ops) => { + stack.push(StackElem::Closure(param, ops)); } - Op::Unsuspend => {} Op::Param(_) => todo!(), } } @@ -407,8 +384,7 @@ impl Expression { (Some(right), Some(left)) => stack.push(binary.print(left, right, symbols)), _ => return None, }, - Op::Suspend => {} - Op::Unsuspend => {} + Op::Closure(_, _) => stack.push("todo".to_owned()), Op::Param(_) => {} } } @@ -600,13 +576,14 @@ mod tests { let ops2 = vec![ Op::Value(Term::Bool(false)), - Op::Suspend, - Op::Value(Term::Bool(true)), - Op::Suspend, - Op::Value(Term::Bool(true)), - Op::Unsuspend, - Op::Binary(Binary::And), - Op::Unsuspend, + Op::Closure( + vec![], + vec![ + Op::Value(Term::Bool(true)), + Op::Closure(vec![], vec![Op::Value(Term::Bool(true))]), + Op::Binary(Binary::And), + ], + ), Op::Binary(Binary::Or), ]; let e2 = Expression { ops: ops2 }; @@ -622,9 +599,7 @@ mod tests { let ops1 = vec![ Op::Value(Term::Set([Term::Bool(false), Term::Bool(true)].into())), - Op::Suspend, - Op::Param(0), - Op::Unsuspend, + Op::Closure(vec!["0".to_owned()], vec![Op::Param("0".to_owned())]), Op::Binary(Binary::Any), ]; let e1 = Expression { ops: ops1 }; diff --git a/biscuit-auth/src/format/convert.rs b/biscuit-auth/src/format/convert.rs index 1ab3f0e4..a9b70e09 100644 --- a/biscuit-auth/src/format/convert.rs +++ b/biscuit-auth/src/format/convert.rs @@ -620,9 +620,8 @@ pub mod v2 { } as i32, }) } - Op::Suspend => todo!(), - Op::Unsuspend => todo!(), - Op::Param(_)=> todo!(), + Op::Closure(_, _) => todo!(), + Op::Param(_) => todo!(), }; schema::Op { diff --git a/biscuit-auth/src/token/builder.rs b/biscuit-auth/src/token/builder.rs index 54a334b5..6294efd2 100644 --- a/biscuit-auth/src/token/builder.rs +++ b/biscuit-auth/src/token/builder.rs @@ -920,8 +920,7 @@ impl Convert for Op { datalog::Op::Value(t) => Op::Value(Term::convert_from(t, symbols)?), datalog::Op::Unary(u) => Op::Unary(u.clone()), datalog::Op::Binary(b) => Op::Binary(b.clone()), - datalog::Op::Suspend => todo!(), - datalog::Op::Unsuspend => todo!(), + datalog::Op::Closure(_, _) => todo!(), datalog::Op::Param(_) => todo!(), }) } From fec7ef8276e01fa29766655152a8436faf82fd02 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Tue, 2 Jan 2024 15:00:02 +0100 Subject: [PATCH 05/18] closures: support for any/all, recursive ops, protobuf encoding --- biscuit-auth/src/datalog/expression.rs | 91 +++++++--- biscuit-auth/src/format/convert.rs | 226 +++++++++++++------------ biscuit-auth/src/format/schema.proto | 12 +- biscuit-auth/src/format/schema.rs | 15 +- biscuit-auth/src/token/builder.rs | 15 +- 5 files changed, 226 insertions(+), 133 deletions(-) diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index 4e238a3c..63e51787 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -15,8 +15,7 @@ pub enum Op { Value(Term), Unary(Unary), Binary(Binary), - Param(String), - Closure(Vec, Vec), + Closure(Vec, Vec), } /// Unary operation code @@ -82,6 +81,9 @@ pub enum Binary { BitwiseOr, BitwiseXor, NotEqual, + LazyAnd, + LazyOr, + All, Any, } @@ -90,14 +92,14 @@ impl Binary { &self, left: Term, mut right: Vec, - params: &[String], + params: &[u32], ops: &mut Vec, values: &HashMap, symbols: &mut TemporarySymbolTable, ) -> Result { match (self, left, params) { - (Binary::Or, Term::Bool(true), []) => Ok(Term::Bool(true)), - (Binary::Or, Term::Bool(false), []) => { + (Binary::LazyOr, Term::Bool(true), []) => Ok(Term::Bool(true)), + (Binary::LazyOr, Term::Bool(false), []) => { ops.push(Op::Binary(Binary::Or)); right.reverse(); for op in right { @@ -105,21 +107,40 @@ impl Binary { } Ok(Term::Bool(false)) } - (Binary::And, Term::Bool(false), []) => Ok(Term::Bool(false)), - (Binary::And, Term::Bool(true), []) => { + (Binary::LazyAnd, Term::Bool(false), []) => Ok(Term::Bool(false)), + (Binary::LazyAnd, Term::Bool(true), []) => { ops.push(Op::Binary(Binary::And)); for op in right { ops.push(op); } Ok(Term::Bool(true)) } - (Binary::Any, Term::Set(set_values), [param_name]) => { + (Binary::All, Term::Set(set_values), [param]) => { for value in set_values.iter() { let ops = right .clone() .iter() .map(|op| match op { - Op::Param(p) if p == param_name => Op::Value(value.clone()), + Op::Value(Term::Variable(v)) if v == param => Op::Value(value.clone()), + _ => op.clone(), + }) + .collect::>(); + let e = Expression { ops }; + match e.evaluate(values, symbols)? { + Term::Bool(true) => {} + Term::Bool(false) => return Ok(Term::Bool(false)), + _ => return Err(error::Expression::InvalidType), + }; + } + Ok(Term::Bool(false)) + } + (Binary::Any, Term::Set(set_values), [param]) => { + for value in set_values.iter() { + let ops = right + .clone() + .iter() + .map(|op| match op { + Op::Value(Term::Variable(v)) if v == param => Op::Value(value.clone()), _ => op.clone(), }) .collect::>(); @@ -290,14 +311,17 @@ impl Binary { Binary::BitwiseAnd => format!("{} & {}", left, right), Binary::BitwiseOr => format!("{} | {}", left, right), Binary::BitwiseXor => format!("{} ^ {}", left, right), - Binary::Any => todo!(), + Binary::LazyAnd => format!("{left} && {right}"), + Binary::LazyOr => format!("{left} || {right}"), + Binary::All => format!("{left}.all({right})"), + Binary::Any => format!("{left}.any({right})"), } } } #[derive(Clone, Debug)] enum StackElem { - Closure(Vec, Vec), + Closure(Vec, Vec), Term(Term), } @@ -313,8 +337,8 @@ impl Expression { ops.reverse(); while let Some(op) = ops.pop() { - println!("ops: {ops:?}"); - println!("op: {:?}\t| stack: {:?}", op, stack); + // println!("ops: {ops:?}"); + // println!("op: {:?}\t| stack: {:?}", op, stack); match op { Op::Value(Term::Variable(i)) => match values.get(&i) { @@ -351,13 +375,12 @@ impl Expression { return Err(error::Expression::InvalidStack); } }, - Op::Closure(param, ops) => { - stack.push(StackElem::Closure(param, ops)); + Op::Closure(params, ops) => { + stack.push(StackElem::Closure(params, ops)); } - Op::Param(_) => todo!(), } } - println!("stack: {stack:?}"); + //println!("stack: {stack:?}"); if stack.len() == 1 { match stack.remove(0) { @@ -384,8 +407,24 @@ impl Expression { (Some(right), Some(left)) => stack.push(binary.print(left, right, symbols)), _ => return None, }, - Op::Closure(_, _) => stack.push("todo".to_owned()), - Op::Param(_) => {} + Op::Closure(params, ops) => { + let exp_body = Expression { ops: ops.clone() }; + let body = match exp_body.print(symbols) { + Some(c) => c, + _ => return None, + }; + + if params.is_empty() { + stack.push(body); + } else { + let param_group = params + .iter() + .map(|s| symbols.print_term(&Term::Variable(*s))) + .collect::>() + .join(", "); + stack.push(format!("({param_group}) -> {body}")); + } + } } } @@ -581,10 +620,10 @@ mod tests { vec![ Op::Value(Term::Bool(true)), Op::Closure(vec![], vec![Op::Value(Term::Bool(true))]), - Op::Binary(Binary::And), + Op::Binary(Binary::LazyAnd), ], ), - Op::Binary(Binary::Or), + Op::Binary(Binary::LazyOr), ]; let e2 = Expression { ops: ops2 }; @@ -594,17 +633,19 @@ mod tests { #[test] fn any() { - let symbols = SymbolTable::new(); - let mut symbols = TemporarySymbolTable::new(&symbols); + let mut symbols = SymbolTable::new(); + let p = symbols.insert("param") as u32; + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); let ops1 = vec![ Op::Value(Term::Set([Term::Bool(false), Term::Bool(true)].into())), - Op::Closure(vec!["0".to_owned()], vec![Op::Param("0".to_owned())]), + Op::Closure(vec![p], vec![Op::Value(Term::Variable(p))]), Op::Binary(Binary::Any), ]; let e1 = Expression { ops: ops1 }; + println!("{:?}", e1.print(&symbols)); - let res1 = e1.evaluate(&HashMap::new(), &mut symbols).unwrap(); + let res1 = e1.evaluate(&HashMap::new(), &mut tmp_symbols).unwrap(); assert_eq!(res1, Term::Bool(true)); } } diff --git a/biscuit-auth/src/format/convert.rs b/biscuit-auth/src/format/convert.rs index a9b70e09..24d79883 100644 --- a/biscuit-auth/src/format/convert.rs +++ b/biscuit-auth/src/format/convert.rs @@ -571,121 +571,139 @@ pub mod v2 { } } + fn token_op_to_proto_op(op: &Op) -> schema::Op { + let content = match op { + Op::Value(i) => schema::op::Content::Value(token_term_to_proto_id(i)), + Op::Unary(u) => { + use schema::op_unary::Kind; + + schema::op::Content::Unary(schema::OpUnary { + kind: match u { + Unary::Negate => Kind::Negate, + Unary::Parens => Kind::Parens, + Unary::Length => Kind::Length, + } as i32, + }) + } + Op::Binary(b) => { + use schema::op_binary::Kind; + + schema::op::Content::Binary(schema::OpBinary { + kind: match b { + Binary::LessThan => Kind::LessThan, + Binary::GreaterThan => Kind::GreaterThan, + Binary::LessOrEqual => Kind::LessOrEqual, + Binary::GreaterOrEqual => Kind::GreaterOrEqual, + Binary::Equal => Kind::Equal, + Binary::Contains => Kind::Contains, + Binary::Prefix => Kind::Prefix, + Binary::Suffix => Kind::Suffix, + Binary::Regex => Kind::Regex, + Binary::Add => Kind::Add, + Binary::Sub => Kind::Sub, + Binary::Mul => Kind::Mul, + Binary::Div => Kind::Div, + Binary::And => Kind::And, + Binary::Or => Kind::Or, + Binary::Intersection => Kind::Intersection, + Binary::Union => Kind::Union, + Binary::BitwiseAnd => Kind::BitwiseAnd, + Binary::BitwiseOr => Kind::BitwiseOr, + Binary::BitwiseXor => Kind::BitwiseXor, + Binary::NotEqual => Kind::NotEqual, + Binary::LazyAnd => Kind::LazyAnd, + Binary::LazyOr => Kind::LazyOr, + Binary::All => Kind::All, + Binary::Any => Kind::Any, + } as i32, + }) + } + Op::Closure(params, ops) => schema::op::Content::Closure(schema::OpClosure { + params: params.clone(), + ops: ops.iter().map(token_op_to_proto_op).collect(), + }), + }; + + schema::Op { + content: Some(content), + } + } + pub fn token_expression_to_proto_expression(input: &Expression) -> schema::ExpressionV2 { schema::ExpressionV2 { - ops: input - .ops - .iter() - .map(|op| { - let content = match op { - Op::Value(i) => schema::op::Content::Value(token_term_to_proto_id(i)), - Op::Unary(u) => { - use schema::op_unary::Kind; - - schema::op::Content::Unary(schema::OpUnary { - kind: match u { - Unary::Negate => Kind::Negate, - Unary::Parens => Kind::Parens, - Unary::Length => Kind::Length, - } as i32, - }) - } - Op::Binary(b) => { - use schema::op_binary::Kind; - - schema::op::Content::Binary(schema::OpBinary { - kind: match b { - Binary::LessThan => Kind::LessThan, - Binary::GreaterThan => Kind::GreaterThan, - Binary::LessOrEqual => Kind::LessOrEqual, - Binary::GreaterOrEqual => Kind::GreaterOrEqual, - Binary::Equal => Kind::Equal, - Binary::Contains => Kind::Contains, - Binary::Prefix => Kind::Prefix, - Binary::Suffix => Kind::Suffix, - Binary::Regex => Kind::Regex, - Binary::Add => Kind::Add, - Binary::Sub => Kind::Sub, - Binary::Mul => Kind::Mul, - Binary::Div => Kind::Div, - Binary::And => Kind::And, - Binary::Or => Kind::Or, - Binary::Intersection => Kind::Intersection, - Binary::Union => Kind::Union, - Binary::BitwiseAnd => Kind::BitwiseAnd, - Binary::BitwiseOr => Kind::BitwiseOr, - Binary::BitwiseXor => Kind::BitwiseXor, - Binary::NotEqual => Kind::NotEqual, - Binary::Any => todo!(), - } as i32, - }) - } - Op::Closure(_, _) => todo!(), - Op::Param(_) => todo!(), - }; - - schema::Op { - content: Some(content), - } - }) - .collect(), + ops: input.ops.iter().map(token_op_to_proto_op).collect(), } } + fn proto_op_to_token_op(op: &schema::Op) -> Result { + use schema::{op, op_binary, op_unary}; + Ok(match op.content.as_ref() { + Some(op::Content::Value(id)) => Op::Value(proto_id_to_token_term(id)?), + Some(op::Content::Unary(u)) => match op_unary::Kind::from_i32(u.kind) { + Some(op_unary::Kind::Negate) => Op::Unary(Unary::Negate), + Some(op_unary::Kind::Parens) => Op::Unary(Unary::Parens), + Some(op_unary::Kind::Length) => Op::Unary(Unary::Length), + None => { + return Err(error::Format::DeserializationError( + "deserialization error: unary operation is empty".to_string(), + )) + } + }, + Some(op::Content::Binary(b)) => match op_binary::Kind::from_i32(b.kind) { + Some(op_binary::Kind::LessThan) => Op::Binary(Binary::LessThan), + Some(op_binary::Kind::GreaterThan) => Op::Binary(Binary::GreaterThan), + Some(op_binary::Kind::LessOrEqual) => Op::Binary(Binary::LessOrEqual), + Some(op_binary::Kind::GreaterOrEqual) => Op::Binary(Binary::GreaterOrEqual), + Some(op_binary::Kind::Equal) => Op::Binary(Binary::Equal), + Some(op_binary::Kind::Contains) => Op::Binary(Binary::Contains), + Some(op_binary::Kind::Prefix) => Op::Binary(Binary::Prefix), + Some(op_binary::Kind::Suffix) => Op::Binary(Binary::Suffix), + Some(op_binary::Kind::Regex) => Op::Binary(Binary::Regex), + Some(op_binary::Kind::Add) => Op::Binary(Binary::Add), + Some(op_binary::Kind::Sub) => Op::Binary(Binary::Sub), + Some(op_binary::Kind::Mul) => Op::Binary(Binary::Mul), + Some(op_binary::Kind::Div) => Op::Binary(Binary::Div), + Some(op_binary::Kind::And) => Op::Binary(Binary::And), + Some(op_binary::Kind::Or) => Op::Binary(Binary::Or), + Some(op_binary::Kind::Intersection) => Op::Binary(Binary::Intersection), + Some(op_binary::Kind::Union) => Op::Binary(Binary::Union), + Some(op_binary::Kind::BitwiseAnd) => Op::Binary(Binary::BitwiseAnd), + Some(op_binary::Kind::BitwiseOr) => Op::Binary(Binary::BitwiseOr), + Some(op_binary::Kind::BitwiseXor) => Op::Binary(Binary::BitwiseXor), + Some(op_binary::Kind::NotEqual) => Op::Binary(Binary::NotEqual), + Some(op_binary::Kind::LazyAnd) => Op::Binary(Binary::LazyAnd), + Some(op_binary::Kind::LazyOr) => Op::Binary(Binary::LazyOr), + Some(op_binary::Kind::All) => Op::Binary(Binary::All), + Some(op_binary::Kind::Any) => Op::Binary(Binary::Any), + None => { + return Err(error::Format::DeserializationError( + "deserialization error: binary operation is empty".to_string(), + )) + } + }, + Some(op::Content::Closure(op_closure)) => Op::Closure( + op_closure.params.clone(), + op_closure + .ops + .iter() + .map(proto_op_to_token_op) + .collect::>()?, + ), + None => { + return Err(error::Format::DeserializationError( + "deserialization error: operation is empty".to_string(), + )) + } + }) + } + pub fn proto_expression_to_token_expression( input: &schema::ExpressionV2, ) -> Result { - use schema::{op, op_binary, op_unary}; let mut ops = Vec::new(); for op in input.ops.iter() { - let translated = match op.content.as_ref() { - Some(op::Content::Value(id)) => Op::Value(proto_id_to_token_term(id)?), - Some(op::Content::Unary(u)) => match op_unary::Kind::from_i32(u.kind) { - Some(op_unary::Kind::Negate) => Op::Unary(Unary::Negate), - Some(op_unary::Kind::Parens) => Op::Unary(Unary::Parens), - Some(op_unary::Kind::Length) => Op::Unary(Unary::Length), - None => { - return Err(error::Format::DeserializationError( - "deserialization error: unary operation is empty".to_string(), - )) - } - }, - Some(op::Content::Binary(b)) => match op_binary::Kind::from_i32(b.kind) { - Some(op_binary::Kind::LessThan) => Op::Binary(Binary::LessThan), - Some(op_binary::Kind::GreaterThan) => Op::Binary(Binary::GreaterThan), - Some(op_binary::Kind::LessOrEqual) => Op::Binary(Binary::LessOrEqual), - Some(op_binary::Kind::GreaterOrEqual) => Op::Binary(Binary::GreaterOrEqual), - Some(op_binary::Kind::Equal) => Op::Binary(Binary::Equal), - Some(op_binary::Kind::Contains) => Op::Binary(Binary::Contains), - Some(op_binary::Kind::Prefix) => Op::Binary(Binary::Prefix), - Some(op_binary::Kind::Suffix) => Op::Binary(Binary::Suffix), - Some(op_binary::Kind::Regex) => Op::Binary(Binary::Regex), - Some(op_binary::Kind::Add) => Op::Binary(Binary::Add), - Some(op_binary::Kind::Sub) => Op::Binary(Binary::Sub), - Some(op_binary::Kind::Mul) => Op::Binary(Binary::Mul), - Some(op_binary::Kind::Div) => Op::Binary(Binary::Div), - Some(op_binary::Kind::And) => Op::Binary(Binary::And), - Some(op_binary::Kind::Or) => Op::Binary(Binary::Or), - Some(op_binary::Kind::Intersection) => Op::Binary(Binary::Intersection), - Some(op_binary::Kind::Union) => Op::Binary(Binary::Union), - Some(op_binary::Kind::BitwiseAnd) => Op::Binary(Binary::BitwiseAnd), - Some(op_binary::Kind::BitwiseOr) => Op::Binary(Binary::BitwiseOr), - Some(op_binary::Kind::BitwiseXor) => Op::Binary(Binary::BitwiseXor), - Some(op_binary::Kind::NotEqual) => Op::Binary(Binary::NotEqual), - None => { - return Err(error::Format::DeserializationError( - "deserialization error: binary operation is empty".to_string(), - )) - } - }, - None => { - return Err(error::Format::DeserializationError( - "deserialization error: operation is empty".to_string(), - )) - } - }; - ops.push(translated); + ops.push(proto_op_to_token_op(op)?); } Ok(Expression { ops }) diff --git a/biscuit-auth/src/format/schema.proto b/biscuit-auth/src/format/schema.proto index bc6cb295..0d5bf5a8 100644 --- a/biscuit-auth/src/format/schema.proto +++ b/biscuit-auth/src/format/schema.proto @@ -113,6 +113,7 @@ message Op { TermV2 value = 1; OpUnary unary = 2; OpBinary Binary = 3; + OpClosure closure = 4; } } @@ -149,11 +150,20 @@ message OpBinary { BitwiseOr = 18; BitwiseXor = 19; NotEqual = 20; + LazyAnd = 21; + LazyOr = 22; + All = 23; + Any = 24; } required Kind kind = 1; } +message OpClosure { + repeated uint32 params = 1; + repeated Op ops = 2; +} + message Policy { enum Kind { Allow = 0; @@ -228,4 +238,4 @@ message SnapshotBlock { repeated CheckV2 checks_v2 = 5; repeated Scope scope = 6; optional PublicKey externalKey = 7; -} \ No newline at end of file +} diff --git a/biscuit-auth/src/format/schema.rs b/biscuit-auth/src/format/schema.rs index 869858f9..3d2bb3e8 100644 --- a/biscuit-auth/src/format/schema.rs +++ b/biscuit-auth/src/format/schema.rs @@ -173,7 +173,7 @@ pub struct ExpressionV2 { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct Op { - #[prost(oneof="op::Content", tags="1, 2, 3")] + #[prost(oneof="op::Content", tags="1, 2, 3, 4")] pub content: ::core::option::Option, } /// Nested message and enum types in `Op`. @@ -186,6 +186,8 @@ pub mod op { Unary(super::OpUnary), #[prost(message, tag="3")] Binary(super::OpBinary), + #[prost(message, tag="4")] + Closure(super::OpClosure), } } #[derive(Clone, PartialEq, ::prost::Message)] @@ -234,9 +236,20 @@ pub mod op_binary { BitwiseOr = 18, BitwiseXor = 19, NotEqual = 20, + LazyAnd = 21, + LazyOr = 22, + All = 23, + Any = 24, } } #[derive(Clone, PartialEq, ::prost::Message)] +pub struct OpClosure { + #[prost(uint32, repeated, packed="false", tag="1")] + pub params: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="2")] + pub ops: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Policy { #[prost(message, repeated, tag="1")] pub queries: ::prost::alloc::vec::Vec, diff --git a/biscuit-auth/src/token/builder.rs b/biscuit-auth/src/token/builder.rs index 6294efd2..8a65be5b 100644 --- a/biscuit-auth/src/token/builder.rs +++ b/biscuit-auth/src/token/builder.rs @@ -904,6 +904,7 @@ pub enum Op { Value(Term), Unary(Unary), Binary(Binary), + Closure(Vec, Vec), } impl Convert for Op { @@ -912,6 +913,10 @@ impl Convert for Op { Op::Value(t) => datalog::Op::Value(t.convert(symbols)), Op::Unary(u) => datalog::Op::Unary(u.clone()), Op::Binary(b) => datalog::Op::Binary(b.clone()), + Op::Closure(ps, os) => datalog::Op::Closure( + ps.iter().map(|p| symbols.insert(p) as u32).collect(), + os.iter().map(|o| o.convert(symbols)).collect(), + ), } } @@ -920,8 +925,14 @@ impl Convert for Op { datalog::Op::Value(t) => Op::Value(Term::convert_from(t, symbols)?), datalog::Op::Unary(u) => Op::Unary(u.clone()), datalog::Op::Binary(b) => Op::Binary(b.clone()), - datalog::Op::Closure(_, _) => todo!(), - datalog::Op::Param(_) => todo!(), + datalog::Op::Closure(ps, os) => Op::Closure( + ps.iter() + .map(|p| symbols.print_symbol(*p as u64)) + .collect::>()?, + os.iter() + .map(|o| Op::convert_from(o, symbols)) + .collect::>()?, + ), }) } } From 0f6c3ad92eafb8862eb425883ff53b529b61fc1c Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Tue, 2 Jan 2024 18:10:07 +0100 Subject: [PATCH 06/18] parser support for closures and lazy boolean ops --- biscuit-auth/examples/testcases.rs | 69 ++++++++++++--- biscuit-auth/samples/README.md | 83 ++++++++++++++---- biscuit-auth/samples/samples.json | 63 ++++++++++--- biscuit-auth/samples/test017_expressions.bc | Bin 1725 -> 1613 bytes .../samples/test027_integer_wraparound.bc | Bin 329 -> 293 bytes .../samples/test031_expressions_v5.bc | Bin 0 -> 634 bytes biscuit-auth/src/datalog/expression.rs | 29 +++++- biscuit-auth/src/parser.rs | 33 +++++-- biscuit-auth/src/token/builder.rs | 7 ++ biscuit-auth/tests/macros.rs | 6 +- biscuit-parser/src/builder.rs | 15 ++++ biscuit-parser/src/parser.rs | 82 +++++++++++++---- 12 files changed, 317 insertions(+), 70 deletions(-) create mode 100644 biscuit-auth/samples/test031_expressions_v5.bc diff --git a/biscuit-auth/examples/testcases.rs b/biscuit-auth/examples/testcases.rs index 09cfff64..5b4faf73 100644 --- a/biscuit-auth/examples/testcases.rs +++ b/biscuit-auth/examples/testcases.rs @@ -141,6 +141,8 @@ fn main() { add_test_result(&mut results, expressions_v4(&target, &root, test)); + add_test_result(&mut results, expressions_v5(&target, &root, test)); + if json { let s = serde_json::to_string_pretty(&TestCases { root_private_key: hex::encode(root.private().to_bytes()), @@ -1199,12 +1201,6 @@ fn expressions(target: &str, root: &KeyPair, test: bool) -> TestResult { check if true; //boolean false and negation check if !false; - //boolean and - check if !false && true; - //boolean or - check if false || true; - //boolean parens - check if (true || false) && true; // boolean equality check if true == true; check if false == false; @@ -1225,7 +1221,7 @@ fn expressions(target: &str, root: &KeyPair, test: bool) -> TestResult { check if 1 + 2 * 3 - 4 /2 == 5; // string prefix and suffix - check if "hello world".starts_with("hello") && "hello world".ends_with("world"); + check if "hello world".starts_with("hello"), "hello world".ends_with("world"); // string regex check if "aaabde".matches("a*c?.e"); // string contains @@ -1811,12 +1807,9 @@ fn integer_wraparound(target: &str, root: &KeyPair, test: bool) -> TestResult { let biscuit = biscuit!( r#" - // integer overflows must abort evaluating the whole expression - // todo update this test when integer overflows abort - // the whole datalog evaluation - check if true || 10000000000 * 10000000000 != 0; - check if true || 9223372036854775807 + 1 != 0; - check if true || -9223372036854775808 - 1 != 0; + check if 10000000000 * 10000000000 != 0; + check if 9223372036854775807 + 1 != 0; + check if -9223372036854775808 - 1 != 0; "# ) .build_with_rng(&root, SymbolTable::default(), &mut rng) @@ -1882,6 +1875,56 @@ fn expressions_v4(target: &str, root: &KeyPair, test: bool) -> TestResult { } } +fn expressions_v5(target: &str, root: &KeyPair, test: bool) -> TestResult { + let mut rng: StdRng = SeedableRng::seed_from_u64(1234); + let title = "test expression syntax and all available operations (v5 blocks)".to_string(); + let filename = "test031_expressions_v5".to_string(); + let token; + + let biscuit = biscuit!( + r#" + //boolean and + check if !false && true; + //boolean or + check if false || true; + //boolean parens + check if (true || false) && true; + + // boolean and laziness + check if !(false && "x".intersection("x")); + // boolean or laziness + check if true || "x".intersection("x"); + + //all + check if [1,2,3].all($p -> $p > 0); + //all + check if ![1,2,3].all($p -> $p == 2); + //any + check if [1,2,3].any($p -> $p > 2); + //any + check if ![1,2,3].any($p -> $p > 3); + "# + ) + .build_with_rng(&root, SymbolTable::default(), &mut rng) + .unwrap(); + token = print_blocks(&biscuit); + + let data = write_or_load_testcase(target, &filename, root, &biscuit, test); + + let mut validations = BTreeMap::new(); + validations.insert( + "".to_string(), + validate_token(root, &data[..], "allow if true"), + ); + + TestResult { + title, + filename, + token, + validations, + } +} + fn print_blocks(token: &Biscuit) -> Vec { let mut v = Vec::new(); diff --git a/biscuit-auth/samples/README.md b/biscuit-auth/samples/README.md index 0f915a7b..7d0fb9e5 100644 --- a/biscuit-auth/samples/README.md +++ b/biscuit-auth/samples/README.md @@ -1162,9 +1162,6 @@ public keys: [] ``` check if true; check if !false; -check if !false && true; -check if false || true; -check if (true || false) && true; check if true == true; check if false == false; check if 1 < 2; @@ -1175,7 +1172,7 @@ check if 2 >= 1; check if 2 >= 2; check if 3 == 3; check if 1 + 2 * 3 - 4 / 2 == 5; -check if "hello world".starts_with("hello") && "hello world".ends_with("world"); +check if "hello world".starts_with("hello"), "hello world".ends_with("world"); check if "aaabde".matches("a*c?.e"); check if "aaabde".contains("abd"); check if "aaabde" == "aaa" + "b" + "de"; @@ -1209,7 +1206,7 @@ allow if true; ``` revocation ids: -- `f61b4cb4fc58777fec6c8d39fe62259dc3c78511868236c391e9f67ffd03a3a8b8e3042d4bacce0d5756d053f5afccd4c5e4df0597af44b36bdfab492e5fe50e` +- `bd42e447a03262a6041333ab762008700fce5baa6e8f4f9996159fd5beb6af70193aeaa49d2b733e9aaddce22ce41904e5a66025b90fe59da288a5d3984d0907` authorizer world: ``` @@ -1218,13 +1215,11 @@ World { rules: {} checks: { "check if !false", - "check if !false && true", "check if \"aaabde\" == \"aaa\" + \"b\" + \"de\"", "check if \"aaabde\".contains(\"abd\")", "check if \"aaabde\".matches(\"a*c?.e\")", "check if \"abcD12\" == \"abcD12\"", - "check if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\")", - "check if (true || false) && true", + "check if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\")", "check if 1 + 2 * 3 - 4 / 2 == 5", "check if 1 < 2", "check if 1 <= 1", @@ -1251,7 +1246,6 @@ World { "check if [false, true].contains(true)", "check if [hex:12ab, hex:34de].contains(hex:34de)", "check if false == false", - "check if false || true", "check if hex:12ab == hex:12ab", "check if true", "check if true == true", @@ -2257,9 +2251,9 @@ symbols: [] public keys: [] ``` -check if true || 10000000000 * 10000000000 != 0; -check if true || 9223372036854775807 + 1 != 0; -check if true || -9223372036854775808 - 1 != 0; +check if 10000000000 * 10000000000 != 0; +check if 9223372036854775807 + 1 != 0; +check if -9223372036854775808 - 1 != 0; ``` ### validation @@ -2270,7 +2264,7 @@ allow if true; ``` revocation ids: -- `3346a22aae0abfc1ffa526f02f7650e90af909e5e519989026441e78cdc245b7fd126503cfdc8831325fc04307edc65238db319724477915f7040a2f6a719a05` +- `fb5e7ac2bb892f5cf2fb59677cfad1f96deabbc8e158e3fd1b5ee7c4b6949c999e2169187cbee53b943eebdadaaf68832747baa8cffa2ff9f78025a1f55f440c` authorizer world: ``` @@ -2278,9 +2272,9 @@ World { facts: {} rules: {} checks: { - "check if true || -9223372036854775808 - 1 != 0", - "check if true || 10000000000 * 10000000000 != 0", - "check if true || 9223372036854775807 + 1 != 0", + "check if -9223372036854775808 - 1 != 0", + "check if 10000000000 * 10000000000 != 0", + "check if 9223372036854775807 + 1 != 0", } policies: { "allow if true", @@ -2341,3 +2335,60 @@ World { result: `Ok(0)` + +------------------------------ + +## test expression syntax and all available operations (v5 blocks): test031_expressions_v5.bc +### token + +authority: +symbols: ["x", "p"] + +public keys: [] + +``` +check if !false && true; +check if false || true; +check if (true || false) && true; +check if !(false && "x".intersection("x")); +check if true || "x".intersection("x"); +check if [1, 2, 3].all($p -> $p > 0); +check if ![1, 2, 3].all($p -> $p == 2); +check if [1, 2, 3].any($p -> $p > 2); +check if ![1, 2, 3].any($p -> $p > 3); +``` + +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `ccf395f06eff4d847b390f4b9734f78f7b69bd365aa8610fca56b6124778549bfba205775007ba3338012cef4993c47e230d4a24ccb0c94d8aef155a3f2c4203` + +authorizer world: +``` +World { + facts: {} + rules: {} + checks: { + "check if !(false && \"x\".intersection(\"x\"))", + "check if ![1, 2, 3].all($p -> $p == 2)", + "check if ![1, 2, 3].any($p -> $p > 3)", + "check if !false && true", + "check if (true || false) && true", + "check if [1, 2, 3].all($p -> $p > 0)", + "check if [1, 2, 3].any($p -> $p > 2)", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", +} + policies: { + "allow if true", +} +} +``` + +result: `Ok(0)` + diff --git a/biscuit-auth/samples/samples.json b/biscuit-auth/samples/samples.json index 6e9a4bb8..9d884bf5 100644 --- a/biscuit-auth/samples/samples.json +++ b/biscuit-auth/samples/samples.json @@ -1199,7 +1199,7 @@ ], "public_keys": [], "external_key": null, - "code": "check if true;\ncheck if !false;\ncheck if !false && true;\ncheck if false || true;\ncheck if (true || false) && true;\ncheck if true == true;\ncheck if false == false;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 == 3;\ncheck if 1 + 2 * 3 - 4 / 2 == 5;\ncheck if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" == \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" == \"abcD12\";\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z;\ncheck if hex:12ab == hex:12ab;\ncheck if [1, 2].contains(2);\ncheck if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z);\ncheck if [false, true].contains(true);\ncheck if [\"abc\", \"def\"].contains(\"abc\");\ncheck if [hex:12ab, hex:34de].contains(hex:34de);\ncheck if [1, 2].contains([2]);\ncheck if [1, 2] == [1, 2];\ncheck if [1, 2].intersection([2, 3]) == [2];\ncheck if [1, 2].union([2, 3]) == [1, 2, 3];\ncheck if [1, 2, 3].intersection([1, 2]).contains(1);\ncheck if [1, 2, 3].intersection([1, 2]).length() == 2;\n" + "code": "check if true;\ncheck if !false;\ncheck if true == true;\ncheck if false == false;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 == 3;\ncheck if 1 + 2 * 3 - 4 / 2 == 5;\ncheck if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" == \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" == \"abcD12\";\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z;\ncheck if hex:12ab == hex:12ab;\ncheck if [1, 2].contains(2);\ncheck if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z);\ncheck if [false, true].contains(true);\ncheck if [\"abc\", \"def\"].contains(\"abc\");\ncheck if [hex:12ab, hex:34de].contains(hex:34de);\ncheck if [1, 2].contains([2]);\ncheck if [1, 2] == [1, 2];\ncheck if [1, 2].intersection([2, 3]) == [2];\ncheck if [1, 2].union([2, 3]) == [1, 2, 3];\ncheck if [1, 2, 3].intersection([1, 2]).contains(1);\ncheck if [1, 2, 3].intersection([1, 2]).length() == 2;\n" } ], "validations": { @@ -1209,13 +1209,11 @@ "rules": [], "checks": [ "check if !false", - "check if !false && true", "check if \"aaabde\" == \"aaa\" + \"b\" + \"de\"", "check if \"aaabde\".contains(\"abd\")", "check if \"aaabde\".matches(\"a*c?.e\")", "check if \"abcD12\" == \"abcD12\"", - "check if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\")", - "check if (true || false) && true", + "check if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\")", "check if 1 + 2 * 3 - 4 / 2 == 5", "check if 1 < 2", "check if 1 <= 1", @@ -1242,7 +1240,6 @@ "check if [false, true].contains(true)", "check if [hex:12ab, hex:34de].contains(hex:34de)", "check if false == false", - "check if false || true", "check if hex:12ab == hex:12ab", "check if true", "check if true == true" @@ -1256,7 +1253,7 @@ }, "authorizer_code": "allow if true;\n", "revocation_ids": [ - "f61b4cb4fc58777fec6c8d39fe62259dc3c78511868236c391e9f67ffd03a3a8b8e3042d4bacce0d5756d053f5afccd4c5e4df0597af44b36bdfab492e5fe50e" + "bd42e447a03262a6041333ab762008700fce5baa6e8f4f9996159fd5beb6af70193aeaa49d2b733e9aaddce22ce41904e5a66025b90fe59da288a5d3984d0907" ] } } @@ -2081,7 +2078,7 @@ "symbols": [], "public_keys": [], "external_key": null, - "code": "check if true || 10000000000 * 10000000000 != 0;\ncheck if true || 9223372036854775807 + 1 != 0;\ncheck if true || -9223372036854775808 - 1 != 0;\n" + "code": "check if 10000000000 * 10000000000 != 0;\ncheck if 9223372036854775807 + 1 != 0;\ncheck if -9223372036854775808 - 1 != 0;\n" } ], "validations": { @@ -2090,9 +2087,9 @@ "facts": [], "rules": [], "checks": [ - "check if true || -9223372036854775808 - 1 != 0", - "check if true || 10000000000 * 10000000000 != 0", - "check if true || 9223372036854775807 + 1 != 0" + "check if -9223372036854775808 - 1 != 0", + "check if 10000000000 * 10000000000 != 0", + "check if 9223372036854775807 + 1 != 0" ], "policies": [ "allow if true" @@ -2105,7 +2102,7 @@ }, "authorizer_code": "allow if true;\n", "revocation_ids": [ - "3346a22aae0abfc1ffa526f02f7650e90af909e5e519989026441e78cdc245b7fd126503cfdc8831325fc04307edc65238db319724477915f7040a2f6a719a05" + "fb5e7ac2bb892f5cf2fb59677cfad1f96deabbc8e158e3fd1b5ee7c4b6949c999e2169187cbee53b943eebdadaaf68832747baa8cffa2ff9f78025a1f55f440c" ] } } @@ -2150,6 +2147,50 @@ ] } } + }, + { + "title": "test expression syntax and all available operations (v5 blocks)", + "filename": "test031_expressions_v5.bc", + "token": [ + { + "symbols": [ + "x", + "p" + ], + "public_keys": [], + "external_key": null, + "code": "check if !false && true;\ncheck if false || true;\ncheck if (true || false) && true;\ncheck if !(false && \"x\".intersection(\"x\"));\ncheck if true || \"x\".intersection(\"x\");\ncheck if [1, 2, 3].all($p -> $p > 0);\ncheck if ![1, 2, 3].all($p -> $p == 2);\ncheck if [1, 2, 3].any($p -> $p > 2);\ncheck if ![1, 2, 3].any($p -> $p > 3);\n" + } + ], + "validations": { + "": { + "world": { + "facts": [], + "rules": [], + "checks": [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if ![1, 2, 3].all($p -> $p == 2)", + "check if ![1, 2, 3].any($p -> $p > 3)", + "check if !false && true", + "check if (true || false) && true", + "check if [1, 2, 3].all($p -> $p > 0)", + "check if [1, 2, 3].any($p -> $p > 2)", + "check if false || true", + "check if true || \"x\".intersection(\"x\")" + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "ccf395f06eff4d847b390f4b9734f78f7b69bd365aa8610fca56b6124778549bfba205775007ba3338012cef4993c47e230d4a24ccb0c94d8aef155a3f2c4203" + ] + } + } } ] } diff --git a/biscuit-auth/samples/test017_expressions.bc b/biscuit-auth/samples/test017_expressions.bc index 10f50241d96621a4a6d9b28f9e7386e98f21d91a..ef7a2c82d2146901646fa1f5d6ed1ca6294e04ab 100644 GIT binary patch delta 144 zcmdnXdzObyXc-UJZtjU}G;n~aMh-3(DJBj!RM94g zDEsCNrjN`Hd!3%RFEC14#v*LIx=ewifd5?ds=R*xnbSn)U){HDeSxIat0i-_i|uBu gz4J)ti6qO@WeKV~`Jc{R)UovP3|~(6$urqh0O@utVE_OC delta 276 zcmX@hvzM1mXc{lqYMzN~<@E|&a$HOt(ozy!EL=9of#p_YI@^=GaeeR7|5`g)hm+4om_>cv0ho2<#E0s!{>INJaK diff --git a/biscuit-auth/samples/test027_integer_wraparound.bc b/biscuit-auth/samples/test027_integer_wraparound.bc index 50aa63b93cca6a963d474c44f4ea66bf90aba4b0..463a6c53b3901de38033590779d3ae94969654af 100644 GIT binary patch delta 126 zcmX@fw3JCm=pQ53WJU=VBRwu1E+!6XDYc0bYFtRX5Fk&VOBXDsK5>Hj#Jw60zvHS7 z?e5f%`Sd$7z2?`&pSiDgpLiJY_^))_^CR1)%$Ye)F;k*u-&5-;cCT;UTA$IZ?!Ifq V`Cs}!zc;8Z{2K4VGg*~U1pw^dGFbos delta 163 zcmZ3=bdpIzXb}_Fc18&nBP%WoE+!6XDI+cxE+zxUi9%{(Tr5&d9DEouPH-7(E=#Z; zW2l}YbvB5ei8D1EjNKM#t>fB%@c&Y^5Bg;RFS&klK7A@VV}hECT*cW#uG{|#r81wt k(_v^7f54gj?Xe(>+lJFs+$%-DvvBEW70zOv%*dz$07-%>TL1t6 diff --git a/biscuit-auth/samples/test031_expressions_v5.bc b/biscuit-auth/samples/test031_expressions_v5.bc new file mode 100644 index 0000000000000000000000000000000000000000..77dab3bda0829db43da8803cf29a4ab8fafba365 GIT binary patch literal 634 zcmWf-%)<48nTxT4i?Kk0*+_*;iHnItT1tkCg^S66fr~|miGzWQLy1iY#AoDUkz(Qy zHIn0!0jm;&szQ|)Gcw^a0?X@woS0vDyAeN#LC4i z(ZB(tK)U%MZh)ErcAt?Bmljy33e-M)I>n5vxGcb`jkpB3_^f!im;@MsmXGxks#8zfnD!S?E!Vl*5_NQ$OVW_id@R zGza=j0=G%Di4GXEvPl?Rqa7 uWv}DJtfa)HF!Mz<-^7Suxp!Nc|BI^SX)7OE6i_YPGgn$(YfHp^-9!L_KwK~Y literal 0 HcmV?d00001 diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index 63e51787..b9cf5003 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -132,7 +132,7 @@ impl Binary { _ => return Err(error::Expression::InvalidType), }; } - Ok(Term::Bool(false)) + Ok(Term::Bool(true)) } (Binary::Any, Term::Set(set_values), [param]) => { for value in set_values.iter() { @@ -422,7 +422,7 @@ impl Expression { .map(|s| symbols.print_term(&Term::Variable(*s))) .collect::>() .join(", "); - stack.push(format!("({param_group}) -> {body}")); + stack.push(format!("{param_group} -> {body}")); } } } @@ -648,4 +648,29 @@ mod tests { let res1 = e1.evaluate(&HashMap::new(), &mut tmp_symbols).unwrap(); assert_eq!(res1, Term::Bool(true)); } + + #[test] + fn all() { + let mut symbols = SymbolTable::new(); + let p = symbols.insert("param") as u32; + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + + let ops1 = vec![ + Op::Value(Term::Set([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::GreaterThan), + ], + ), + Op::Binary(Binary::All), + ]; + let e1 = Expression { ops: ops1 }; + println!("{:?}", e1.print(&symbols)); + + let res1 = e1.evaluate(&HashMap::new(), &mut tmp_symbols).unwrap(); + assert_eq!(res1, Term::Bool(true)); + } } diff --git a/biscuit-auth/src/parser.rs b/biscuit-auth/src/parser.rs index 3bc92c88..4756d172 100644 --- a/biscuit-auth/src/parser.rs +++ b/biscuit-auth/src/parser.rs @@ -31,6 +31,7 @@ mod tests { Value(builder::Term), Unary(builder::Op, Box), Binary(builder::Op, Box, Box), + Closure(Vec, Box), } impl Expr { @@ -52,6 +53,11 @@ mod tests { right.into_opcodes(v); v.push(op); } + Expr::Closure(params, body) => { + let mut ops = vec![]; + body.into_opcodes(&mut ops); + v.push(builder::Op::Closure(params, ops)); + } } } } @@ -68,6 +74,9 @@ mod tests { Box::new((*expr1).into()), Box::new((*expr2).into()), ), + biscuit_parser::parser::Expr::Closure(params, body) => { + Expr::Closure(params, Box::new((*body).into())) + } } } } @@ -307,22 +316,28 @@ mod tests { Ok(( " ", Expr::Binary( - Op::Binary(Binary::And), + Op::Binary(Binary::LazyAnd), Box::new(Expr::Binary( - Op::Binary(Binary::And), + Op::Binary(Binary::LazyAnd), Box::new(Expr::Binary( Op::Binary(Binary::LessThan), Box::new(Expr::Value(int(2))), Box::new(Expr::Value(var("test"))), )), - Box::new(Expr::Binary( - Op::Binary(Binary::Prefix), - Box::new(Expr::Value(var("var2"))), - Box::new(Expr::Value(string("test"))), - )), + Box::new(Expr::Closure( + vec![], + Box::new(Expr::Binary( + Op::Binary(Binary::Prefix), + Box::new(Expr::Value(var("var2"))), + Box::new(Expr::Value(string("test"))) + ),) + )) )), - Box::new(Expr::Value(Term::Bool(true))), - ) + Box::new(Expr::Closure( + vec![], + Box::new(Expr::Value(Term::Bool(true))) + )), + ), )) ); diff --git a/biscuit-auth/src/token/builder.rs b/biscuit-auth/src/token/builder.rs index 8a65be5b..667891c6 100644 --- a/biscuit-auth/src/token/builder.rs +++ b/biscuit-auth/src/token/builder.rs @@ -943,6 +943,9 @@ impl From for Op { biscuit_parser::builder::Op::Value(t) => Op::Value(t.into()), biscuit_parser::builder::Op::Unary(u) => Op::Unary(u.into()), biscuit_parser::builder::Op::Binary(b) => Op::Binary(b.into()), + biscuit_parser::builder::Op::Closure(ps, os) => { + Op::Closure(ps, os.into_iter().map(|o| o.into()).collect()) + } } } } @@ -981,6 +984,10 @@ impl From for Binary { biscuit_parser::builder::Binary::BitwiseOr => Binary::BitwiseOr, biscuit_parser::builder::Binary::BitwiseXor => Binary::BitwiseXor, biscuit_parser::builder::Binary::NotEqual => Binary::NotEqual, + biscuit_parser::builder::Binary::LazyAnd => Binary::LazyAnd, + biscuit_parser::builder::Binary::LazyOr => Binary::LazyOr, + biscuit_parser::builder::Binary::All => Binary::All, + biscuit_parser::builder::Binary::Any => Binary::Any, } } } diff --git a/biscuit-auth/tests/macros.rs b/biscuit-auth/tests/macros.rs index 49244655..f38a14ea 100644 --- a/biscuit-auth/tests/macros.rs +++ b/biscuit-auth/tests/macros.rs @@ -12,8 +12,9 @@ fn block_macro() { let my_key = "my_value"; let mut b = block!( r#"fact("test", hex:aabbcc, [true], {my_key}, {term_set}); - rule($0, true) <- fact($0, $1, $2, {my_key}); + rule($0, true) <- fact($0, $1, $2, {my_key}), true || false; check if {my_key}.starts_with("my"); + check if [true,false].any($p -> true); "#, ); @@ -24,8 +25,9 @@ fn block_macro() { b.to_string(), r#"fact("test", hex:aabbcc, [true], "my_value", [0]); appended(true); -rule($0, true) <- fact($0, $1, $2, "my_value"); +rule($0, true) <- fact($0, $1, $2, "my_value"), true || false; check if "my_value".starts_with("my"); +check if [false, true].any($p -> true); "#, ); } diff --git a/biscuit-parser/src/builder.rs b/biscuit-parser/src/builder.rs index 77897cc7..e4a76f54 100644 --- a/biscuit-parser/src/builder.rs +++ b/biscuit-parser/src/builder.rs @@ -181,6 +181,7 @@ pub enum Op { Value(Term), Unary(Unary), Binary(Binary), + Closure(Vec, Vec), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -213,6 +214,10 @@ pub enum Binary { BitwiseOr, BitwiseXor, NotEqual, + LazyAnd, + LazyOr, + All, + Any, } #[cfg(feature = "datalog-macro")] @@ -222,6 +227,12 @@ impl ToTokens for Op { Op::Value(t) => quote! { ::biscuit_auth::builder::Op::Value(#t) }, Op::Unary(u) => quote! { ::biscuit_auth::builder::Op::Unary(#u) }, Op::Binary(b) => quote! { ::biscuit_auth::builder::Op::Binary(#b) }, + Op::Closure(params, os) => quote! { + ::biscuit_auth::builder::Op::Closure( + <[String]>::into_vec(Box::new([#(#params.to_string()),*])), + <[::biscuit_auth::builder::Op]>::into_vec(Box::new([#(#os),*])) + ) + } }); } } @@ -262,6 +273,10 @@ impl ToTokens for Binary { Binary::BitwiseOr => quote! { ::biscuit_auth::datalog::Binary::BitwiseOr }, Binary::BitwiseXor => quote! { ::biscuit_auth::datalog::Binary::BitwiseXor }, Binary::NotEqual => quote! { ::biscuit_auth::datalog::Binary::NotEqual }, + Binary::LazyAnd => quote! { ::biscuit_auth::datalog::Binary::LazyAnd }, + Binary::LazyOr => quote! { ::biscuit_auth::datalog::Binary::LazyOr }, + Binary::All => quote! { ::biscuit_auth::datalog::Binary::All }, + Binary::Any => quote! { ::biscuit_auth::datalog::Binary::Any }, }); } } diff --git a/biscuit-parser/src/parser.rs b/biscuit-parser/src/parser.rs index 392e46b3..7774d42a 100644 --- a/biscuit-parser/src/parser.rs +++ b/biscuit-parser/src/parser.rs @@ -383,6 +383,7 @@ pub enum Expr { Value(builder::Term), Unary(builder::Op, Box), Binary(builder::Op, Box, Box), + Closure(Vec, Box), } impl Expr { @@ -404,6 +405,11 @@ impl Expr { right.into_opcodes(v); v.push(op); } + Expr::Closure(params, expr) => { + let mut ops = vec![]; + expr.into_opcodes(&mut ops); + v.push(builder::Op::Closure(params, ops)) + } } } } @@ -436,12 +442,12 @@ fn unary_parens(i: &str) -> IResult<&str, Expr, Error> { fn binary_op_0(i: &str) -> IResult<&str, builder::Binary, Error> { use builder::Binary; - value(Binary::Or, tag("||"))(i) + value(Binary::LazyOr, tag("||"))(i) } fn binary_op_1(i: &str) -> IResult<&str, builder::Binary, Error> { use builder::Binary; - value(Binary::And, tag("&&"))(i) + value(Binary::LazyAnd, tag("&&"))(i) } fn binary_op_2(i: &str) -> IResult<&str, builder::Binary, Error> { @@ -491,6 +497,8 @@ fn binary_op_8(i: &str) -> IResult<&str, builder::Binary, Error> { value(Binary::Regex, tag("matches")), value(Binary::Intersection, tag("intersection")), value(Binary::Union, tag("union")), + value(Binary::All, tag("all")), + value(Binary::Any, tag("any")), ))(i) } @@ -503,7 +511,14 @@ fn expr_term(i: &str) -> IResult<&str, Expr, Error> { fn fold_exprs(initial: Expr, remainder: Vec<(builder::Binary, Expr)>) -> Expr { remainder.into_iter().fold(initial, |acc, pair| { let (op, expr) = pair; - Expr::Binary(builder::Op::Binary(op), Box::new(acc), Box::new(expr)) + match op { + builder::Binary::LazyAnd | builder::Binary::LazyOr => Expr::Binary( + builder::Op::Binary(op), + Box::new(acc), + Box::new(Expr::Closure(vec![], Box::new(expr))), + ), + _ => Expr::Binary(builder::Op::Binary(op), Box::new(acc), Box::new(expr)), + } }) } @@ -630,10 +645,24 @@ fn expr9(i: &str) -> IResult<&str, Expr, Error> { let bin_result = binary_method(i); let un_result = unary_method(i); match (bin_result, un_result) { - (Ok((i, (op, arg))), _) => { + (Ok((i, (op, params, arg))), _) => { input = i; - initial = - Expr::Binary(builder::Op::Binary(op), Box::new(initial), Box::new(arg)); + match params { + Some(params) => { + initial = Expr::Binary( + builder::Op::Binary(op), + Box::new(initial), + Box::new(Expr::Closure(params, Box::new(arg))), + ); + } + None => { + initial = Expr::Binary( + builder::Op::Binary(op), + Box::new(initial), + Box::new(arg), + ); + } + } } (_, Ok((i, op))) => { input = i; @@ -648,17 +677,31 @@ fn expr9(i: &str) -> IResult<&str, Expr, Error> { } } -fn binary_method(i: &str) -> IResult<&str, (builder::Binary, Expr), Error> { +fn binary_method(i: &str) -> IResult<&str, (builder::Binary, Option>, Expr), Error> { let (i, op) = binary_op_8(i)?; let (i, _) = char('(')(i)?; let (i, _) = space0(i)?; // we only support a single argument for now - let (i, arg) = expr(i)?; - let (i, _) = space0(i)?; - let (i, _) = char(')')(i)?; + match op { + builder::Binary::All | builder::Binary::Any => { + let (i, param) = preceded(char('$'), name)(i)?; + let (i, _) = space0(i)?; + let (i, _) = tag("->")(i)?; + let (i, _) = space0(i)?; + let (i, arg) = expr(i)?; + let (i, _) = space0(i)?; + let (i, _) = char(')')(i)?; + Ok((i, (op, Some(vec![param.to_owned()]), arg))) + } + _ => { + let (i, arg) = expr(i)?; + let (i, _) = space0(i)?; + let (i, _) = char(')')(i)?; - Ok((i, (op, arg))) + Ok((i, (op, None, arg))) + } + } } fn unary_method(i: &str) -> IResult<&str, builder::Unary, Error> { @@ -1357,8 +1400,8 @@ mod tests { vec![ Op::Value(boolean(false)), Op::Unary(Unary::Negate), - Op::Value(boolean(true)), - Op::Binary(Binary::And), + Op::Closure(vec![], vec![Op::Value(boolean(true)),]), + Op::Binary(Binary::LazyAnd), ], )) ); @@ -1369,10 +1412,15 @@ mod tests { "", vec![ Op::Value(boolean(true)), - Op::Value(boolean(true)), - Op::Value(boolean(true)), - Op::Binary(Binary::And), - Op::Binary(Binary::Or), + Op::Closure( + vec![], + vec![ + Op::Value(boolean(true)), + Op::Closure(vec![], vec![Op::Value(boolean(true)),]), + Op::Binary(Binary::LazyAnd), + ] + ), + Op::Binary(Binary::LazyOr), ], )) ); From 0d029b05fa2bf3a70e5f5eab3748b3bb1cf4fc52 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Wed, 3 Jan 2024 10:40:06 +0100 Subject: [PATCH 07/18] wip: nested closures --- biscuit-auth/examples/testcases.rs | 3 + biscuit-auth/src/datalog/expression.rs | 138 +++++++++++++++++-------- 2 files changed, 99 insertions(+), 42 deletions(-) diff --git a/biscuit-auth/examples/testcases.rs b/biscuit-auth/examples/testcases.rs index 5b4faf73..16d727c4 100644 --- a/biscuit-auth/examples/testcases.rs +++ b/biscuit-auth/examples/testcases.rs @@ -1903,6 +1903,9 @@ fn expressions_v5(target: &str, root: &KeyPair, test: bool) -> TestResult { check if [1,2,3].any($p -> $p > 2); //any check if ![1,2,3].any($p -> $p > 3); + + // nested closures + check if [1,2,3].any($p -> $p > 1 && [3,4,5].any($q -> $p == $q)); "# ) .build_with_rng(&root, SymbolTable::default(), &mut rng) diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index b9cf5003..43b52a10 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -94,39 +94,30 @@ impl Binary { mut right: Vec, params: &[u32], ops: &mut Vec, - values: &HashMap, + values: &mut HashMap, symbols: &mut TemporarySymbolTable, ) -> Result { + println!("Recursing, values before: {values:?}"); match (self, left, params) { (Binary::LazyOr, Term::Bool(true), []) => Ok(Term::Bool(true)), (Binary::LazyOr, Term::Bool(false), []) => { - ops.push(Op::Binary(Binary::Or)); - right.reverse(); - for op in right { - ops.push(op); - } - Ok(Term::Bool(false)) + let e = Expression { ops: right.clone() }; + e.evaluate(values, symbols) } (Binary::LazyAnd, Term::Bool(false), []) => Ok(Term::Bool(false)), (Binary::LazyAnd, Term::Bool(true), []) => { - ops.push(Op::Binary(Binary::And)); - for op in right { - ops.push(op); - } - Ok(Term::Bool(true)) + let e = Expression { ops: right.clone() }; + e.evaluate(values, symbols) } (Binary::All, Term::Set(set_values), [param]) => { for value in set_values.iter() { - let ops = right - .clone() - .iter() - .map(|op| match op { - Op::Value(Term::Variable(v)) if v == param => Op::Value(value.clone()), - _ => op.clone(), - }) - .collect::>(); - let e = Expression { ops }; - match e.evaluate(values, symbols)? { + values.insert(*param, value.clone()); + println!("Recursing, values during: {values:?}"); + let e = Expression { ops: right.clone() }; + let result = e.evaluate(values, symbols); + values.remove(param); + println!("Recursing, values after: {values:?}"); + match result? { Term::Bool(true) => {} Term::Bool(false) => return Ok(Term::Bool(false)), _ => return Err(error::Expression::InvalidType), @@ -136,16 +127,13 @@ impl Binary { } (Binary::Any, Term::Set(set_values), [param]) => { for value in set_values.iter() { - let ops = right - .clone() - .iter() - .map(|op| match op { - Op::Value(Term::Variable(v)) if v == param => Op::Value(value.clone()), - _ => op.clone(), - }) - .collect::>(); - let e = Expression { ops }; - match e.evaluate(values, symbols)? { + values.insert(*param, value.clone()); + println!("Recursing, values during: {values:?}"); + let e = Expression { ops: right.clone() }; + let result = e.evaluate(values, symbols); + values.remove(param); + println!("Recursing, values after: {values:?}"); + match result? { Term::Bool(false) => {} Term::Bool(true) => return Ok(Term::Bool(true)), _ => return Err(error::Expression::InvalidType), @@ -336,10 +324,12 @@ impl Expression { let mut ops = self.ops.clone(); ops.reverse(); + println!("-- begin -- {values:?}"); while let Some(op) = ops.pop() { - // println!("ops: {ops:?}"); - // println!("op: {:?}\t| stack: {:?}", op, stack); + println!("ops: {ops:?}"); + println!("op: {:?}\t| stack: {:?}", op, stack); + let opop = op.clone(); match op { Op::Value(Term::Variable(i)) => match values.get(&i) { Some(term) => stack.push(StackElem::Term(term.clone())), @@ -354,7 +344,7 @@ impl Expression { stack.push(StackElem::Term(unary.evaluate(term, symbols)?)) } _ => { - //println!("expected a value on the stack"); + println!("expected a value on the stack"); return Err(error::Expression::InvalidStack); } }, @@ -366,12 +356,26 @@ impl Expression { ( Some(StackElem::Closure(params, right_ops)), Some(StackElem::Term(left_term)), - ) => stack.push(StackElem::Term(binary.evaluate_with_closure( - left_term, right_ops, ¶ms, &mut ops, values, symbols, - )?)), + ) => { + let mut values = values.clone(); + stack.push(StackElem::Term(binary.evaluate_with_closure( + left_term, + right_ops, + ¶ms, + &mut ops, + &mut values, + symbols, + )?)) + } - _ => { - //println!("expected two values on the stack"); + e => { + println!( + "while evaluating {}", + self.print(&SymbolTable::new()).unwrap() + ); + println!("while evaluating {opop:?}"); + println!("with context {values:?}"); + println!("expected two values on the stack, got: {e:?}"); return Err(error::Expression::InvalidStack); } }, @@ -380,14 +384,18 @@ impl Expression { } } } - //println!("stack: {stack:?}"); + println!("-- end {stack:?}"); if stack.len() == 1 { match stack.remove(0) { StackElem::Term(t) => Ok(t), - _ => Err(error::Expression::InvalidStack), + _ => { + println!("expected a term on the stack after evaluation"); + Err(error::Expression::InvalidStack) + } } } else { + println!("expected one value the stack after evaluation"); Err(error::Expression::InvalidStack) } } @@ -673,4 +681,50 @@ mod tests { let res1 = e1.evaluate(&HashMap::new(), &mut tmp_symbols).unwrap(); assert_eq!(res1, Term::Bool(true)); } + + #[test] + fn nested_closures() { + let mut symbols = SymbolTable::new(); + let p = symbols.insert("p") as u32; + let q = symbols.insert("q") as u32; + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + + let ops1 = vec![ + Op::Value(Term::Set( + [Term::Integer(1), Term::Integer(2), Term::Integer(3)].into(), + )), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(1)), + Op::Binary(Binary::GreaterThan), + Op::Closure( + vec![], + vec![ + Op::Value(Term::Set( + [Term::Integer(3), Term::Integer(4), Term::Integer(5)].into(), + )), + Op::Closure( + vec![q], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Variable(q)), + Op::Binary(Binary::Equal), + ], + ), + Op::Binary(Binary::Any), + ], + ), + Op::Binary(Binary::LazyAnd), + ], + ), + Op::Binary(Binary::Any), + ]; + let e1 = Expression { ops: ops1 }; + println!("{}", e1.print(&symbols).unwrap()); + + let res1 = e1.evaluate(&HashMap::new(), &mut tmp_symbols).unwrap(); + assert_eq!(res1, Term::Bool(true)); + } } From 1fb6e21573200a17822c2cd66771cda87a0097f3 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Wed, 3 Jan 2024 16:14:04 +0100 Subject: [PATCH 08/18] Remove self-modifying ops stack Closures are all evaluated through recursive calls now --- biscuit-auth/src/datalog/expression.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index 43b52a10..7c2591f6 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -91,9 +91,8 @@ impl Binary { fn evaluate_with_closure( &self, left: Term, - mut right: Vec, + right: Vec, params: &[u32], - ops: &mut Vec, values: &mut HashMap, symbols: &mut TemporarySymbolTable, ) -> Result { @@ -321,21 +320,17 @@ impl Expression { ) -> Result { let mut stack: Vec = Vec::new(); - let mut ops = self.ops.clone(); - ops.reverse(); - println!("-- begin -- {values:?}"); - while let Some(op) = ops.pop() { - println!("ops: {ops:?}"); + for op in self.ops.iter() { println!("op: {:?}\t| stack: {:?}", op, stack); let opop = op.clone(); match op { - Op::Value(Term::Variable(i)) => match values.get(&i) { + Op::Value(Term::Variable(i)) => match values.get(i) { Some(term) => stack.push(StackElem::Term(term.clone())), None => { //println!("unknown variable {}", i); - return Err(error::Expression::UnknownVariable(i)); + return Err(error::Expression::UnknownVariable(*i)); } }, Op::Value(term) => stack.push(StackElem::Term(term.clone())), @@ -362,7 +357,6 @@ impl Expression { left_term, right_ops, ¶ms, - &mut ops, &mut values, symbols, )?)) @@ -380,7 +374,7 @@ impl Expression { } }, Op::Closure(params, ops) => { - stack.push(StackElem::Closure(params, ops)); + stack.push(StackElem::Closure(params.clone(), ops.clone())); } } } From 025ba46996f721922bf19affeef1ecd788fb2ec4 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Wed, 22 May 2024 14:03:32 +0200 Subject: [PATCH 09/18] remove unused sample --- biscuit-auth/samples/test031_expressions_v5.bc | Bin 634 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 biscuit-auth/samples/test031_expressions_v5.bc diff --git a/biscuit-auth/samples/test031_expressions_v5.bc b/biscuit-auth/samples/test031_expressions_v5.bc deleted file mode 100644 index 77dab3bda0829db43da8803cf29a4ab8fafba365..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 634 zcmWf-%)<48nTxT4i?Kk0*+_*;iHnItT1tkCg^S66fr~|miGzWQLy1iY#AoDUkz(Qy zHIn0!0jm;&szQ|)Gcw^a0?X@woS0vDyAeN#LC4i z(ZB(tK)U%MZh)ErcAt?Bmljy33e-M)I>n5vxGcb`jkpB3_^f!im;@MsmXGxks#8zfnD!S?E!Vl*5_NQ$OVW_id@R zGza=j0=G%Di4GXEvPl?Rqa7 uWv}DJtfa)HF!Mz<-^7Suxp!Nc|BI^SX)7OE6i_YPGgn$(YfHp^-9!L_KwK~Y From 987651492a65a2d6200ab9e390dcfaecc55f0f1a Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Thu, 23 May 2024 16:57:56 +0200 Subject: [PATCH 10/18] chore: remove leftover printlns --- biscuit-auth/src/datalog/expression.rs | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index 88839bc2..17ded966 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -96,7 +96,6 @@ impl Binary { values: &mut HashMap, symbols: &mut TemporarySymbolTable, ) -> Result { - println!("Recursing, values before: {values:?}"); match (self, left, params) { (Binary::LazyOr, Term::Bool(true), []) => Ok(Term::Bool(true)), (Binary::LazyOr, Term::Bool(false), []) => { @@ -111,11 +110,9 @@ impl Binary { (Binary::All, Term::Set(set_values), [param]) => { for value in set_values.iter() { values.insert(*param, value.clone()); - println!("Recursing, values during: {values:?}"); let e = Expression { ops: right.clone() }; let result = e.evaluate(values, symbols); values.remove(param); - println!("Recursing, values after: {values:?}"); match result? { Term::Bool(true) => {} Term::Bool(false) => return Ok(Term::Bool(false)), @@ -127,11 +124,9 @@ impl Binary { (Binary::Any, Term::Set(set_values), [param]) => { for value in set_values.iter() { values.insert(*param, value.clone()); - println!("Recursing, values during: {values:?}"); let e = Expression { ops: right.clone() }; let result = e.evaluate(values, symbols); values.remove(param); - println!("Recursing, values after: {values:?}"); match result? { Term::Bool(false) => {} Term::Bool(true) => return Ok(Term::Bool(true)), @@ -328,9 +323,8 @@ impl Expression { ) -> Result { let mut stack: Vec = Vec::new(); - println!("-- begin -- {values:?}"); for op in self.ops.iter() { - println!("op: {:?}\t| stack: {:?}", op, stack); + // println!("op: {:?}\t| stack: {:?}", op, stack); let opop = op.clone(); match op { @@ -347,7 +341,6 @@ impl Expression { stack.push(StackElem::Term(unary.evaluate(term, symbols)?)) } _ => { - println!("expected a value on the stack"); return Err(error::Expression::InvalidStack); } }, @@ -371,13 +364,6 @@ impl Expression { } e => { - println!( - "while evaluating {}", - self.print(&SymbolTable::new()).unwrap() - ); - println!("while evaluating {opop:?}"); - println!("with context {values:?}"); - println!("expected two values on the stack, got: {e:?}"); return Err(error::Expression::InvalidStack); } }, @@ -386,18 +372,13 @@ impl Expression { } } } - println!("-- end {stack:?}"); if stack.len() == 1 { match stack.remove(0) { StackElem::Term(t) => Ok(t), - _ => { - println!("expected a term on the stack after evaluation"); - Err(error::Expression::InvalidStack) - } + _ => Err(error::Expression::InvalidStack), } } else { - println!("expected one value the stack after evaluation"); Err(error::Expression::InvalidStack) } } From 8433c34cd450e3761b35a4dfbc0420720911de4b Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Thu, 23 May 2024 17:04:34 +0200 Subject: [PATCH 11/18] fix: don't print legacy && / || the same way They are not parsed anymore, but we need to be aware if older operators are still used. --- biscuit-auth/src/datalog/expression.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index 17ded966..d1fb1298 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -294,8 +294,8 @@ impl Binary { Binary::Sub => format!("{} - {}", left, right), Binary::Mul => format!("{} * {}", left, right), Binary::Div => format!("{} / {}", left, right), - Binary::And => format!("{} && {}", left, right), - Binary::Or => format!("{} || {}", left, right), + Binary::And => format!("{} &&! {}", left, right), + Binary::Or => format!("{} ||! {}", left, right), Binary::Intersection => format!("{}.intersection({})", left, right), Binary::Union => format!("{}.union({})", left, right), Binary::BitwiseAnd => format!("{} & {}", left, right), From ac30afae7c873c02abcf98301f98b8b3000017de Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Fri, 24 May 2024 08:52:06 +0200 Subject: [PATCH 12/18] fix: detect shadowed variables in closure parameters This only happens during evaluation. Earlier checks depend on changes from https://github.com/biscuit-auth/biscuit-rust/pull/179 --- biscuit-auth/src/datalog/expression.rs | 11 ++++++++++- biscuit-auth/src/error.rs | 2 ++ biscuit-parser/src/builder.rs | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index d1fb1298..53702dc6 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -3,7 +3,7 @@ use crate::error; use super::Term; use super::{SymbolTable, TemporarySymbolTable}; use regex::Regex; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; #[derive(Debug, Clone, PartialEq, Hash, Eq)] pub struct Expression { @@ -353,6 +353,15 @@ impl Expression { Some(StackElem::Closure(params, right_ops)), Some(StackElem::Term(left_term)), ) => { + if values + .keys() + .collect::>() + .intersection(¶ms.iter().collect()) + .next() + .is_some() + { + return Err(error::Expression::ShadowedVariable); + } let mut values = values.clone(); stack.push(StackElem::Term(binary.evaluate_with_closure( left_term, diff --git a/biscuit-auth/src/error.rs b/biscuit-auth/src/error.rs index 35856121..984369c5 100644 --- a/biscuit-auth/src/error.rs +++ b/biscuit-auth/src/error.rs @@ -248,6 +248,8 @@ pub enum Expression { DivideByZero, #[error("Wrong number of elements on stack")] InvalidStack, + #[error("Shadowed variable")] + ShadowedVariable, } /// runtime limits errors diff --git a/biscuit-parser/src/builder.rs b/biscuit-parser/src/builder.rs index 157a022c..0514d4ca 100644 --- a/biscuit-parser/src/builder.rs +++ b/biscuit-parser/src/builder.rs @@ -236,7 +236,7 @@ impl ToTokens for Op { <[String]>::into_vec(Box::new([#(#params.to_string()),*])), <[::biscuit_auth::builder::Op]>::into_vec(Box::new([#(#os),*])) ) - } + }, }); } } From 89cbdb57ea400abf2b2457246cbb6623b18fba21 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Fri, 24 May 2024 08:53:13 +0200 Subject: [PATCH 13/18] fix: unused variables --- biscuit-auth/src/datalog/expression.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index 53702dc6..8d5e8feb 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -326,7 +326,6 @@ impl Expression { for op in self.ops.iter() { // println!("op: {:?}\t| stack: {:?}", op, stack); - let opop = op.clone(); match op { Op::Value(Term::Variable(i)) => match values.get(i) { Some(term) => stack.push(StackElem::Term(term.clone())), @@ -372,7 +371,7 @@ impl Expression { )?)) } - e => { + _ => { return Err(error::Expression::InvalidStack); } }, From e98cb78f090976eb1581a6db7c8d041cbd239703 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Fri, 24 May 2024 09:02:10 +0200 Subject: [PATCH 14/18] more tests for closures --- biscuit-auth/src/datalog/expression.rs | 89 ++++++++++++++++++++------ 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index 8d5e8feb..ba582958 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -678,23 +678,7 @@ mod tests { let symbols = SymbolTable::new(); let mut symbols = TemporarySymbolTable::new(&symbols); - // let ops1 = vec![ - // Op::Value(Term::Bool(true)), - // Op::Suspend, - // Op::Value(Term::Bool(false)), - // Op::Suspend, - // Op::Value(Term::Bool(false)), - // Op::Unsuspend, - // Op::Binary(Binary::Or), - // Op::Unsuspend, - // Op::Binary(Binary::Or), - // ]; - // let e1 = Expression { ops: ops1 }; - - // let res1 = e1.evaluate(&HashMap::new(), &mut symbols).unwrap(); - // assert_eq!(res1, Term::Bool(true)); - - let ops2 = vec![ + let ops1 = vec![ Op::Value(Term::Bool(false)), Op::Closure( vec![], @@ -706,7 +690,7 @@ mod tests { ), Op::Binary(Binary::LazyOr), ]; - let e2 = Expression { ops: ops2 }; + let e2 = Expression { ops: ops1 }; let res2 = e2.evaluate(&HashMap::new(), &mut symbols).unwrap(); assert_eq!(res2, Term::Bool(true)); @@ -800,4 +784,73 @@ mod tests { let res1 = e1.evaluate(&HashMap::new(), &mut tmp_symbols).unwrap(); assert_eq!(res1, Term::Bool(true)); } + + #[test] + fn variable_shadowing() { + let mut symbols = SymbolTable::new(); + let p = symbols.insert("param") as u32; + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + + let ops1 = vec![ + Op::Value(Term::Set([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::GreaterThan), + ], + ), + Op::Binary(Binary::All), + ]; + let e1 = Expression { ops: ops1 }; + println!("{:?}", e1.print(&symbols)); + + let mut values = HashMap::new(); + values.insert(p, Term::Null); + let res1 = e1.evaluate(&values, &mut tmp_symbols); + assert_eq!(res1, Err(error::Expression::ShadowedVariable)); + + let mut symbols = SymbolTable::new(); + let p = symbols.insert("p") as u32; + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + + let ops2 = vec![ + Op::Value(Term::Set( + [Term::Integer(1), Term::Integer(2), Term::Integer(3)].into(), + )), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(1)), + Op::Binary(Binary::GreaterThan), + Op::Closure( + vec![], + vec![ + Op::Value(Term::Set( + [Term::Integer(3), Term::Integer(4), Term::Integer(5)].into(), + )), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Variable(p)), + Op::Binary(Binary::Equal), + ], + ), + Op::Binary(Binary::Any), + ], + ), + Op::Binary(Binary::LazyAnd), + ], + ), + Op::Binary(Binary::Any), + ]; + let e2 = Expression { ops: ops2 }; + println!("{}", e2.print(&symbols).unwrap()); + + let res2 = e2.evaluate(&HashMap::new(), &mut tmp_symbols); + assert_eq!(res2, Err(error::Expression::ShadowedVariable)); + } } From de7d2076e132a6b7096da5737fae4daa00dca78a Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Fri, 24 May 2024 09:17:33 +0200 Subject: [PATCH 15/18] fix: remove debug output from samples README --- biscuit-auth/samples/README.md | 691 --------------------------------- 1 file changed, 691 deletions(-) diff --git a/biscuit-auth/samples/README.md b/biscuit-auth/samples/README.md index b61045b6..85b09cca 100644 --- a/biscuit-auth/samples/README.md +++ b/biscuit-auth/samples/README.md @@ -1,694 +1,3 @@ --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {5: Date(1608542592)} -op: Value(Variable(5)) | stack: [] -op: Value(Date(1545264000)) | stack: [Term(Date(1608542592))] -op: Binary(LessOrEqual) | stack: [Term(Date(1608542592)), Term(Date(1545264000))] --- end [Term(Bool(false))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {1027: Date(1608542592)} -op: Value(Variable(1027)) | stack: [] -op: Value(Date(1924952399)) | stack: [Term(Date(1608542592))] -op: Binary(LessOrEqual) | stack: [Term(Date(1608542592)), Term(Date(1924952399))] --- end [Term(Bool(true))] --- begin -- {1028: Str(1024), 1027: Date(1608542592)} -op: Value(Variable(1027)) | stack: [] -op: Value(Date(946645199)) | stack: [Term(Date(1608542592))] -op: Binary(LessOrEqual) | stack: [Term(Date(1608542592)), Term(Date(946645199))] --- end [Term(Bool(false))] --- begin -- {1027: Date(1608542592)} -op: Value(Variable(1027)) | stack: [] -op: Value(Date(1924952399)) | stack: [Term(Date(1608542592))] -op: Binary(LessOrEqual) | stack: [Term(Date(1608542592)), Term(Date(1924952399))] --- end [Term(Bool(true))] --- begin -- {1027: Date(1608542592), 1028: Str(1024)} -op: Value(Variable(1027)) | stack: [] -op: Value(Date(946645199)) | stack: [Term(Date(1608542592))] -op: Binary(LessOrEqual) | stack: [Term(Date(1608542592)), Term(Date(946645199))] --- end [Term(Bool(false))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {1027: Date(1608542592)} -op: Value(Variable(1027)) | stack: [] -op: Value(Date(1924952399)) | stack: [Term(Date(1608542592))] -op: Binary(LessOrEqual) | stack: [Term(Date(1608542592)), Term(Date(1924952399))] --- end [Term(Bool(true))] --- begin -- {1028: Str(1024), 1027: Date(1608542592)} -op: Value(Variable(1027)) | stack: [] -op: Value(Date(946645199)) | stack: [Term(Date(1608542592))] -op: Binary(LessOrEqual) | stack: [Term(Date(1608542592)), Term(Date(946645199))] --- end [Term(Bool(false))] --- begin -- {1027: Date(1608542592), 1028: Str(1025)} -op: Value(Variable(1027)) | stack: [] -op: Value(Date(946645199)) | stack: [Term(Date(1608542592))] -op: Binary(LessOrEqual) | stack: [Term(Date(1608542592)), Term(Date(946645199))] --- end [Term(Bool(false))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {1028: Str(1025), 1027: Date(1608542592)} -op: Value(Variable(1027)) | stack: [] -op: Value(Date(946645199)) | stack: [Term(Date(1608542592))] -op: Binary(LessOrEqual) | stack: [Term(Date(1608542592)), Term(Date(946645199))] --- end [Term(Bool(false))] --- begin -- {1024: Str(1026)} -op: Value(Variable(1024)) | stack: [] -op: Value(Str(1025)) | stack: [Term(Str(1026))] -op: Binary(Regex) | stack: [Term(Str(1026)), Term(Str(1025))] --- end [Term(Bool(false))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {1024: Str(1026)} -op: Value(Variable(1024)) | stack: [] -op: Value(Str(1025)) | stack: [Term(Str(1026))] -op: Binary(Regex) | stack: [Term(Str(1026)), Term(Str(1025))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(false)) | stack: [] -op: Unary(Negate) | stack: [Term(Bool(false))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] -op: Value(Bool(true)) | stack: [Term(Bool(true))] -op: Binary(Equal) | stack: [Term(Bool(true)), Term(Bool(true))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(false)) | stack: [] -op: Value(Bool(false)) | stack: [Term(Bool(false))] -op: Binary(Equal) | stack: [Term(Bool(false)), Term(Bool(false))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Integer(1)) | stack: [] -op: Value(Integer(2)) | stack: [Term(Integer(1))] -op: Binary(LessThan) | stack: [Term(Integer(1)), Term(Integer(2))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Integer(2)) | stack: [] -op: Value(Integer(1)) | stack: [Term(Integer(2))] -op: Binary(GreaterThan) | stack: [Term(Integer(2)), Term(Integer(1))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Integer(1)) | stack: [] -op: Value(Integer(2)) | stack: [Term(Integer(1))] -op: Binary(LessOrEqual) | stack: [Term(Integer(1)), Term(Integer(2))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Integer(1)) | stack: [] -op: Value(Integer(1)) | stack: [Term(Integer(1))] -op: Binary(LessOrEqual) | stack: [Term(Integer(1)), Term(Integer(1))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Integer(2)) | stack: [] -op: Value(Integer(1)) | stack: [Term(Integer(2))] -op: Binary(GreaterOrEqual) | stack: [Term(Integer(2)), Term(Integer(1))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Integer(2)) | stack: [] -op: Value(Integer(2)) | stack: [Term(Integer(2))] -op: Binary(GreaterOrEqual) | stack: [Term(Integer(2)), Term(Integer(2))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Integer(3)) | stack: [] -op: Value(Integer(3)) | stack: [Term(Integer(3))] -op: Binary(Equal) | stack: [Term(Integer(3)), Term(Integer(3))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Integer(1)) | stack: [] -op: Value(Integer(2)) | stack: [Term(Integer(1))] -op: Value(Integer(3)) | stack: [Term(Integer(1)), Term(Integer(2))] -op: Binary(Mul) | stack: [Term(Integer(1)), Term(Integer(2)), Term(Integer(3))] -op: Binary(Add) | stack: [Term(Integer(1)), Term(Integer(6))] -op: Value(Integer(4)) | stack: [Term(Integer(7))] -op: Value(Integer(2)) | stack: [Term(Integer(7)), Term(Integer(4))] -op: Binary(Div) | stack: [Term(Integer(7)), Term(Integer(4)), Term(Integer(2))] -op: Binary(Sub) | stack: [Term(Integer(7)), Term(Integer(2))] -op: Value(Integer(5)) | stack: [Term(Integer(5))] -op: Binary(Equal) | stack: [Term(Integer(5)), Term(Integer(5))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Str(1024)) | stack: [] -op: Value(Str(1025)) | stack: [Term(Str(1024))] -op: Binary(Prefix) | stack: [Term(Str(1024)), Term(Str(1025))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Str(1024)) | stack: [] -op: Value(Str(1026)) | stack: [Term(Str(1024))] -op: Binary(Suffix) | stack: [Term(Str(1024)), Term(Str(1026))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Str(1027)) | stack: [] -op: Value(Str(1028)) | stack: [Term(Str(1027))] -op: Binary(Regex) | stack: [Term(Str(1027)), Term(Str(1028))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Str(1027)) | stack: [] -op: Value(Str(1029)) | stack: [Term(Str(1027))] -op: Binary(Contains) | stack: [Term(Str(1027)), Term(Str(1029))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Str(1027)) | stack: [] -op: Value(Str(1030)) | stack: [Term(Str(1027))] -op: Value(Str(1031)) | stack: [Term(Str(1027)), Term(Str(1030))] -op: Binary(Add) | stack: [Term(Str(1027)), Term(Str(1030)), Term(Str(1031))] -op: Value(Str(1032)) | stack: [Term(Str(1027)), Term(Str(1037))] -op: Binary(Add) | stack: [Term(Str(1027)), Term(Str(1037)), Term(Str(1032))] -op: Binary(Equal) | stack: [Term(Str(1027)), Term(Str(1027))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Str(1033)) | stack: [] -op: Value(Str(1033)) | stack: [Term(Str(1033))] -op: Binary(Equal) | stack: [Term(Str(1033)), Term(Str(1033))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Str(1033)) | stack: [] -op: Unary(Length) | stack: [Term(Str(1033))] -op: Value(Integer(6)) | stack: [Term(Integer(6))] -op: Binary(Equal) | stack: [Term(Integer(6)), Term(Integer(6))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Str(1034)) | stack: [] -op: Unary(Length) | stack: [Term(Str(1034))] -op: Value(Integer(2)) | stack: [Term(Integer(2))] -op: Binary(Equal) | stack: [Term(Integer(2)), Term(Integer(2))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Date(1575452801)) | stack: [] -op: Value(Date(1607075201)) | stack: [Term(Date(1575452801))] -op: Binary(LessThan) | stack: [Term(Date(1575452801)), Term(Date(1607075201))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Date(1607075201)) | stack: [] -op: Value(Date(1575452801)) | stack: [Term(Date(1607075201))] -op: Binary(GreaterThan) | stack: [Term(Date(1607075201)), Term(Date(1575452801))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Date(1575452801)) | stack: [] -op: Value(Date(1607075201)) | stack: [Term(Date(1575452801))] -op: Binary(LessOrEqual) | stack: [Term(Date(1575452801)), Term(Date(1607075201))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Date(1607075201)) | stack: [] -op: Value(Date(1607075201)) | stack: [Term(Date(1607075201))] -op: Binary(GreaterOrEqual) | stack: [Term(Date(1607075201)), Term(Date(1607075201))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Date(1607075201)) | stack: [] -op: Value(Date(1575452801)) | stack: [Term(Date(1607075201))] -op: Binary(GreaterOrEqual) | stack: [Term(Date(1607075201)), Term(Date(1575452801))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Date(1607075201)) | stack: [] -op: Value(Date(1607075201)) | stack: [Term(Date(1607075201))] -op: Binary(GreaterOrEqual) | stack: [Term(Date(1607075201)), Term(Date(1607075201))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Date(1607075201)) | stack: [] -op: Value(Date(1607075201)) | stack: [Term(Date(1607075201))] -op: Binary(Equal) | stack: [Term(Date(1607075201)), Term(Date(1607075201))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bytes([18, 171])) | stack: [] -op: Value(Bytes([18, 171])) | stack: [Term(Bytes([18, 171]))] -op: Binary(Equal) | stack: [Term(Bytes([18, 171])), Term(Bytes([18, 171]))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Integer(1), Integer(2)})) | stack: [] -op: Value(Integer(2)) | stack: [Term(Set({Integer(1), Integer(2)}))] -op: Binary(Contains) | stack: [Term(Set({Integer(1), Integer(2)})), Term(Integer(2))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Date(1575452801), Date(1607075201)})) | stack: [] -op: Value(Date(1607075201)) | stack: [Term(Set({Date(1575452801), Date(1607075201)}))] -op: Binary(Contains) | stack: [Term(Set({Date(1575452801), Date(1607075201)})), Term(Date(1607075201))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Bool(false), Bool(true)})) | stack: [] -op: Value(Bool(true)) | stack: [Term(Set({Bool(false), Bool(true)}))] -op: Binary(Contains) | stack: [Term(Set({Bool(false), Bool(true)})), Term(Bool(true))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Str(1035), Str(1036)})) | stack: [] -op: Value(Str(1035)) | stack: [Term(Set({Str(1035), Str(1036)}))] -op: Binary(Contains) | stack: [Term(Set({Str(1035), Str(1036)})), Term(Str(1035))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Bytes([18, 171]), Bytes([52, 222])})) | stack: [] -op: Value(Bytes([52, 222])) | stack: [Term(Set({Bytes([18, 171]), Bytes([52, 222])}))] -op: Binary(Contains) | stack: [Term(Set({Bytes([18, 171]), Bytes([52, 222])})), Term(Bytes([52, 222]))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Integer(1), Integer(2)})) | stack: [] -op: Value(Set({Integer(2)})) | stack: [Term(Set({Integer(1), Integer(2)}))] -op: Binary(Contains) | stack: [Term(Set({Integer(1), Integer(2)})), Term(Set({Integer(2)}))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Integer(1), Integer(2)})) | stack: [] -op: Value(Set({Integer(1), Integer(2)})) | stack: [Term(Set({Integer(1), Integer(2)}))] -op: Binary(Equal) | stack: [Term(Set({Integer(1), Integer(2)})), Term(Set({Integer(1), Integer(2)}))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Integer(1), Integer(2)})) | stack: [] -op: Value(Set({Integer(2), Integer(3)})) | stack: [Term(Set({Integer(1), Integer(2)}))] -op: Binary(Intersection) | stack: [Term(Set({Integer(1), Integer(2)})), Term(Set({Integer(2), Integer(3)}))] -op: Value(Set({Integer(2)})) | stack: [Term(Set({Integer(2)}))] -op: Binary(Equal) | stack: [Term(Set({Integer(2)})), Term(Set({Integer(2)}))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Integer(1), Integer(2)})) | stack: [] -op: Value(Set({Integer(2), Integer(3)})) | stack: [Term(Set({Integer(1), Integer(2)}))] -op: Binary(Union) | stack: [Term(Set({Integer(1), Integer(2)})), Term(Set({Integer(2), Integer(3)}))] -op: Value(Set({Integer(1), Integer(2), Integer(3)})) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)}))] -op: Binary(Equal) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)})), Term(Set({Integer(1), Integer(2), Integer(3)}))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Integer(1), Integer(2), Integer(3)})) | stack: [] -op: Value(Set({Integer(1), Integer(2)})) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)}))] -op: Binary(Intersection) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)})), Term(Set({Integer(1), Integer(2)}))] -op: Value(Integer(1)) | stack: [Term(Set({Integer(1), Integer(2)}))] -op: Binary(Contains) | stack: [Term(Set({Integer(1), Integer(2)})), Term(Integer(1))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Integer(1), Integer(2), Integer(3)})) | stack: [] -op: Value(Set({Integer(1), Integer(2)})) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)}))] -op: Binary(Intersection) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)})), Term(Set({Integer(1), Integer(2)}))] -op: Unary(Length) | stack: [Term(Set({Integer(1), Integer(2)}))] -op: Value(Integer(2)) | stack: [Term(Integer(2))] -op: Binary(Equal) | stack: [Term(Integer(2)), Term(Integer(2))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {1028: Set({Str(1025), Str(1026)}), 1027: Str(1025)} -op: Value(Variable(1028)) | stack: [] -op: Value(Variable(1027)) | stack: [Term(Set({Str(1025), Str(1026)}))] -op: Binary(Contains) | stack: [Term(Set({Str(1025), Str(1026)})), Term(Str(1025))] --- end [Term(Bool(true))] --- begin -- {1027: Str(1026), 1028: Set({Str(1025), Str(1026)})} -op: Value(Variable(1028)) | stack: [] -op: Value(Variable(1027)) | stack: [Term(Set({Str(1025), Str(1026)}))] -op: Binary(Contains) | stack: [Term(Set({Str(1025), Str(1026)})), Term(Str(1026))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {1028: Set({Str(1025), Str(1026)}), 1027: Str(1025)} -op: Value(Variable(1028)) | stack: [] -op: Value(Variable(1027)) | stack: [Term(Set({Str(1025), Str(1026)}))] -op: Binary(Contains) | stack: [Term(Set({Str(1025), Str(1026)})), Term(Str(1025))] --- end [Term(Bool(true))] --- begin -- {1027: Str(1029), 1028: Set({Str(1025), Str(1026)})} -op: Value(Variable(1028)) | stack: [] -op: Value(Variable(1027)) | stack: [Term(Set({Str(1025), Str(1026)}))] -op: Binary(Contains) | stack: [Term(Set({Str(1025), Str(1026)})), Term(Str(1029))] --- end [Term(Bool(false))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Integer(10000000000)) | stack: [] -op: Value(Integer(10000000000)) | stack: [Term(Integer(10000000000))] -op: Binary(Mul) | stack: [Term(Integer(10000000000)), Term(Integer(10000000000))] --- begin -- {} -op: Value(Integer(1)) | stack: [] -op: Value(Integer(3)) | stack: [Term(Integer(1))] -op: Binary(NotEqual) | stack: [Term(Integer(1)), Term(Integer(3))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Integer(1)) | stack: [] -op: Value(Integer(2)) | stack: [Term(Integer(1))] -op: Binary(BitwiseOr) | stack: [Term(Integer(1)), Term(Integer(2))] -op: Value(Integer(3)) | stack: [Term(Integer(3))] -op: Binary(BitwiseXor) | stack: [Term(Integer(3)), Term(Integer(3))] -op: Value(Integer(0)) | stack: [Term(Integer(0))] -op: Binary(Equal) | stack: [Term(Integer(0)), Term(Integer(0))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Str(1024)) | stack: [] -op: Value(Str(1025)) | stack: [Term(Str(1024))] -op: Binary(NotEqual) | stack: [Term(Str(1024)), Term(Str(1025))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Date(1670147201)) | stack: [] -op: Value(Date(1607075201)) | stack: [Term(Date(1670147201))] -op: Binary(NotEqual) | stack: [Term(Date(1670147201)), Term(Date(1607075201))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bytes([18, 171, 205])) | stack: [] -op: Value(Bytes([18, 171])) | stack: [Term(Bytes([18, 171, 205]))] -op: Binary(NotEqual) | stack: [Term(Bytes([18, 171, 205])), Term(Bytes([18, 171]))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Integer(1), Integer(4)})) | stack: [] -op: Value(Set({Integer(1), Integer(2)})) | stack: [Term(Set({Integer(1), Integer(4)}))] -op: Binary(NotEqual) | stack: [Term(Set({Integer(1), Integer(4)})), Term(Set({Integer(1), Integer(2)}))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {1024: Bool(false)} -op: Value(Variable(1024)) | stack: [] --- end [Term(Bool(false))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {1024: Bool(true)} -op: Value(Variable(1024)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {1025: Null} -op: Value(Variable(1025)) | stack: [] -op: Value(Null) | stack: [Term(Null)] -op: Binary(Equal) | stack: [Term(Null), Term(Null)] --- end [Term(Bool(true))] --- begin -- {1025: Null} -op: Value(Variable(1025)) | stack: [] -op: Value(Null) | stack: [Term(Null)] -op: Binary(NotEqual) | stack: [Term(Null), Term(Null)] --- end [Term(Bool(false))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {1025: Integer(1)} -op: Value(Variable(1025)) | stack: [] -op: Value(Null) | stack: [Term(Integer(1))] -op: Binary(Equal) | stack: [Term(Integer(1)), Term(Null)] --- end [Term(Bool(false))] --- begin -- {1025: Integer(1)} -op: Value(Variable(1025)) | stack: [] -op: Value(Null) | stack: [Term(Integer(1))] -op: Binary(NotEqual) | stack: [Term(Integer(1)), Term(Null)] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {1025: Bool(true)} -op: Value(Variable(1025)) | stack: [] -op: Value(Null) | stack: [Term(Bool(true))] -op: Binary(Equal) | stack: [Term(Bool(true)), Term(Null)] --- end [Term(Bool(false))] --- begin -- {1025: Bool(true)} -op: Value(Variable(1025)) | stack: [] -op: Value(Null) | stack: [Term(Bool(true))] -op: Binary(NotEqual) | stack: [Term(Bool(true)), Term(Null)] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {1025: Str(1026)} -op: Value(Variable(1025)) | stack: [] -op: Value(Null) | stack: [Term(Str(1026))] -op: Binary(Equal) | stack: [Term(Str(1026)), Term(Null)] --- end [Term(Bool(false))] --- begin -- {1025: Str(1026)} -op: Value(Variable(1025)) | stack: [] -op: Value(Null) | stack: [Term(Str(1026))] -op: Binary(NotEqual) | stack: [Term(Str(1026)), Term(Null)] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(false)) | stack: [] -op: Unary(Negate) | stack: [Term(Bool(false))] -op: Closure([], [Value(Bool(true))]) | stack: [Term(Bool(true))] -op: Binary(LazyAnd) | stack: [Term(Bool(true)), Closure([], [Value(Bool(true))])] -Recursing, values before: {} --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(false)) | stack: [] -op: Closure([], [Value(Bool(true))]) | stack: [Term(Bool(false))] -op: Binary(LazyOr) | stack: [Term(Bool(false)), Closure([], [Value(Bool(true))])] -Recursing, values before: {} --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] -op: Closure([], [Value(Bool(false))]) | stack: [Term(Bool(true))] -op: Binary(LazyOr) | stack: [Term(Bool(true)), Closure([], [Value(Bool(false))])] -Recursing, values before: {} -op: Unary(Parens) | stack: [Term(Bool(true))] -op: Closure([], [Value(Bool(true))]) | stack: [Term(Bool(true))] -op: Binary(LazyAnd) | stack: [Term(Bool(true)), Closure([], [Value(Bool(true))])] -Recursing, values before: {} --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(false)) | stack: [] -op: Closure([], [Value(Str(1024)), Value(Str(1024)), Binary(Intersection)]) | stack: [Term(Bool(false))] -op: Binary(LazyAnd) | stack: [Term(Bool(false)), Closure([], [Value(Str(1024)), Value(Str(1024)), Binary(Intersection)])] -Recursing, values before: {} -op: Unary(Parens) | stack: [Term(Bool(false))] -op: Unary(Negate) | stack: [Term(Bool(false))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] -op: Closure([], [Value(Str(1024)), Value(Str(1024)), Binary(Intersection)]) | stack: [Term(Bool(true))] -op: Binary(LazyOr) | stack: [Term(Bool(true)), Closure([], [Value(Str(1024)), Value(Str(1024)), Binary(Intersection)])] -Recursing, values before: {} --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Integer(1), Integer(2), Integer(3)})) | stack: [] -op: Closure([1025], [Value(Variable(1025)), Value(Integer(0)), Binary(GreaterThan)]) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)}))] -op: Binary(All) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)})), Closure([1025], [Value(Variable(1025)), Value(Integer(0)), Binary(GreaterThan)])] -Recursing, values before: {} -Recursing, values during: {1025: Integer(1)} --- begin -- {1025: Integer(1)} -op: Value(Variable(1025)) | stack: [] -op: Value(Integer(0)) | stack: [Term(Integer(1))] -op: Binary(GreaterThan) | stack: [Term(Integer(1)), Term(Integer(0))] --- end [Term(Bool(true))] -Recursing, values after: {} -Recursing, values during: {1025: Integer(2)} --- begin -- {1025: Integer(2)} -op: Value(Variable(1025)) | stack: [] -op: Value(Integer(0)) | stack: [Term(Integer(2))] -op: Binary(GreaterThan) | stack: [Term(Integer(2)), Term(Integer(0))] --- end [Term(Bool(true))] -Recursing, values after: {} -Recursing, values during: {1025: Integer(3)} --- begin -- {1025: Integer(3)} -op: Value(Variable(1025)) | stack: [] -op: Value(Integer(0)) | stack: [Term(Integer(3))] -op: Binary(GreaterThan) | stack: [Term(Integer(3)), Term(Integer(0))] --- end [Term(Bool(true))] -Recursing, values after: {} --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Integer(1), Integer(2), Integer(3)})) | stack: [] -op: Closure([1025], [Value(Variable(1025)), Value(Integer(2)), Binary(Equal)]) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)}))] -op: Binary(All) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)})), Closure([1025], [Value(Variable(1025)), Value(Integer(2)), Binary(Equal)])] -Recursing, values before: {} -Recursing, values during: {1025: Integer(1)} --- begin -- {1025: Integer(1)} -op: Value(Variable(1025)) | stack: [] -op: Value(Integer(2)) | stack: [Term(Integer(1))] -op: Binary(Equal) | stack: [Term(Integer(1)), Term(Integer(2))] --- end [Term(Bool(false))] -Recursing, values after: {} -op: Unary(Negate) | stack: [Term(Bool(false))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Integer(1), Integer(2), Integer(3)})) | stack: [] -op: Closure([1025], [Value(Variable(1025)), Value(Integer(2)), Binary(GreaterThan)]) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)}))] -op: Binary(Any) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)})), Closure([1025], [Value(Variable(1025)), Value(Integer(2)), Binary(GreaterThan)])] -Recursing, values before: {} -Recursing, values during: {1025: Integer(1)} --- begin -- {1025: Integer(1)} -op: Value(Variable(1025)) | stack: [] -op: Value(Integer(2)) | stack: [Term(Integer(1))] -op: Binary(GreaterThan) | stack: [Term(Integer(1)), Term(Integer(2))] --- end [Term(Bool(false))] -Recursing, values after: {} -Recursing, values during: {1025: Integer(2)} --- begin -- {1025: Integer(2)} -op: Value(Variable(1025)) | stack: [] -op: Value(Integer(2)) | stack: [Term(Integer(2))] -op: Binary(GreaterThan) | stack: [Term(Integer(2)), Term(Integer(2))] --- end [Term(Bool(false))] -Recursing, values after: {} -Recursing, values during: {1025: Integer(3)} --- begin -- {1025: Integer(3)} -op: Value(Variable(1025)) | stack: [] -op: Value(Integer(2)) | stack: [Term(Integer(3))] -op: Binary(GreaterThan) | stack: [Term(Integer(3)), Term(Integer(2))] --- end [Term(Bool(true))] -Recursing, values after: {} --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Integer(1), Integer(2), Integer(3)})) | stack: [] -op: Closure([1025], [Value(Variable(1025)), Value(Integer(3)), Binary(GreaterThan)]) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)}))] -op: Binary(Any) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)})), Closure([1025], [Value(Variable(1025)), Value(Integer(3)), Binary(GreaterThan)])] -Recursing, values before: {} -Recursing, values during: {1025: Integer(1)} --- begin -- {1025: Integer(1)} -op: Value(Variable(1025)) | stack: [] -op: Value(Integer(3)) | stack: [Term(Integer(1))] -op: Binary(GreaterThan) | stack: [Term(Integer(1)), Term(Integer(3))] --- end [Term(Bool(false))] -Recursing, values after: {} -Recursing, values during: {1025: Integer(2)} --- begin -- {1025: Integer(2)} -op: Value(Variable(1025)) | stack: [] -op: Value(Integer(3)) | stack: [Term(Integer(2))] -op: Binary(GreaterThan) | stack: [Term(Integer(2)), Term(Integer(3))] --- end [Term(Bool(false))] -Recursing, values after: {} -Recursing, values during: {1025: Integer(3)} --- begin -- {1025: Integer(3)} -op: Value(Variable(1025)) | stack: [] -op: Value(Integer(3)) | stack: [Term(Integer(3))] -op: Binary(GreaterThan) | stack: [Term(Integer(3)), Term(Integer(3))] --- end [Term(Bool(false))] -Recursing, values after: {} -op: Unary(Negate) | stack: [Term(Bool(false))] --- end [Term(Bool(true))] --- begin -- {} -op: Value(Set({Integer(1), Integer(2), Integer(3)})) | stack: [] -op: Closure([1025], [Value(Variable(1025)), Value(Integer(1)), Binary(GreaterThan), Closure([], [Value(Set({Integer(3), Integer(4), Integer(5)})), Closure([1026], [Value(Variable(1025)), Value(Variable(1026)), Binary(Equal)]), Binary(Any)]), Binary(LazyAnd)]) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)}))] -op: Binary(Any) | stack: [Term(Set({Integer(1), Integer(2), Integer(3)})), Closure([1025], [Value(Variable(1025)), Value(Integer(1)), Binary(GreaterThan), Closure([], [Value(Set({Integer(3), Integer(4), Integer(5)})), Closure([1026], [Value(Variable(1025)), Value(Variable(1026)), Binary(Equal)]), Binary(Any)]), Binary(LazyAnd)])] -Recursing, values before: {} -Recursing, values during: {1025: Integer(1)} --- begin -- {1025: Integer(1)} -op: Value(Variable(1025)) | stack: [] -op: Value(Integer(1)) | stack: [Term(Integer(1))] -op: Binary(GreaterThan) | stack: [Term(Integer(1)), Term(Integer(1))] -op: Closure([], [Value(Set({Integer(3), Integer(4), Integer(5)})), Closure([1026], [Value(Variable(1025)), Value(Variable(1026)), Binary(Equal)]), Binary(Any)]) | stack: [Term(Bool(false))] -op: Binary(LazyAnd) | stack: [Term(Bool(false)), Closure([], [Value(Set({Integer(3), Integer(4), Integer(5)})), Closure([1026], [Value(Variable(1025)), Value(Variable(1026)), Binary(Equal)]), Binary(Any)])] -Recursing, values before: {1025: Integer(1)} --- end [Term(Bool(false))] -Recursing, values after: {} -Recursing, values during: {1025: Integer(2)} --- begin -- {1025: Integer(2)} -op: Value(Variable(1025)) | stack: [] -op: Value(Integer(1)) | stack: [Term(Integer(2))] -op: Binary(GreaterThan) | stack: [Term(Integer(2)), Term(Integer(1))] -op: Closure([], [Value(Set({Integer(3), Integer(4), Integer(5)})), Closure([1026], [Value(Variable(1025)), Value(Variable(1026)), Binary(Equal)]), Binary(Any)]) | stack: [Term(Bool(true))] -op: Binary(LazyAnd) | stack: [Term(Bool(true)), Closure([], [Value(Set({Integer(3), Integer(4), Integer(5)})), Closure([1026], [Value(Variable(1025)), Value(Variable(1026)), Binary(Equal)]), Binary(Any)])] -Recursing, values before: {1025: Integer(2)} --- begin -- {1025: Integer(2)} -op: Value(Set({Integer(3), Integer(4), Integer(5)})) | stack: [] -op: Closure([1026], [Value(Variable(1025)), Value(Variable(1026)), Binary(Equal)]) | stack: [Term(Set({Integer(3), Integer(4), Integer(5)}))] -op: Binary(Any) | stack: [Term(Set({Integer(3), Integer(4), Integer(5)})), Closure([1026], [Value(Variable(1025)), Value(Variable(1026)), Binary(Equal)])] -Recursing, values before: {1025: Integer(2)} -Recursing, values during: {1025: Integer(2), 1026: Integer(3)} --- begin -- {1025: Integer(2), 1026: Integer(3)} -op: Value(Variable(1025)) | stack: [] -op: Value(Variable(1026)) | stack: [Term(Integer(2))] -op: Binary(Equal) | stack: [Term(Integer(2)), Term(Integer(3))] --- end [Term(Bool(false))] -Recursing, values after: {1025: Integer(2)} -Recursing, values during: {1025: Integer(2), 1026: Integer(4)} --- begin -- {1025: Integer(2), 1026: Integer(4)} -op: Value(Variable(1025)) | stack: [] -op: Value(Variable(1026)) | stack: [Term(Integer(2))] -op: Binary(Equal) | stack: [Term(Integer(2)), Term(Integer(4))] --- end [Term(Bool(false))] -Recursing, values after: {1025: Integer(2)} -Recursing, values during: {1025: Integer(2), 1026: Integer(5)} --- begin -- {1025: Integer(2), 1026: Integer(5)} -op: Value(Variable(1025)) | stack: [] -op: Value(Variable(1026)) | stack: [Term(Integer(2))] -op: Binary(Equal) | stack: [Term(Integer(2)), Term(Integer(5))] --- end [Term(Bool(false))] -Recursing, values after: {1025: Integer(2)} --- end [Term(Bool(false))] --- end [Term(Bool(false))] -Recursing, values after: {} -Recursing, values during: {1025: Integer(3)} --- begin -- {1025: Integer(3)} -op: Value(Variable(1025)) | stack: [] -op: Value(Integer(1)) | stack: [Term(Integer(3))] -op: Binary(GreaterThan) | stack: [Term(Integer(3)), Term(Integer(1))] -op: Closure([], [Value(Set({Integer(3), Integer(4), Integer(5)})), Closure([1026], [Value(Variable(1025)), Value(Variable(1026)), Binary(Equal)]), Binary(Any)]) | stack: [Term(Bool(true))] -op: Binary(LazyAnd) | stack: [Term(Bool(true)), Closure([], [Value(Set({Integer(3), Integer(4), Integer(5)})), Closure([1026], [Value(Variable(1025)), Value(Variable(1026)), Binary(Equal)]), Binary(Any)])] -Recursing, values before: {1025: Integer(3)} --- begin -- {1025: Integer(3)} -op: Value(Set({Integer(3), Integer(4), Integer(5)})) | stack: [] -op: Closure([1026], [Value(Variable(1025)), Value(Variable(1026)), Binary(Equal)]) | stack: [Term(Set({Integer(3), Integer(4), Integer(5)}))] -op: Binary(Any) | stack: [Term(Set({Integer(3), Integer(4), Integer(5)})), Closure([1026], [Value(Variable(1025)), Value(Variable(1026)), Binary(Equal)])] -Recursing, values before: {1025: Integer(3)} -Recursing, values during: {1025: Integer(3), 1026: Integer(3)} --- begin -- {1025: Integer(3), 1026: Integer(3)} -op: Value(Variable(1025)) | stack: [] -op: Value(Variable(1026)) | stack: [Term(Integer(3))] -op: Binary(Equal) | stack: [Term(Integer(3)), Term(Integer(3))] --- end [Term(Bool(true))] -Recursing, values after: {1025: Integer(3)} --- end [Term(Bool(true))] --- end [Term(Bool(true))] -Recursing, values after: {} --- end [Term(Bool(true))] --- begin -- {} -op: Value(Bool(true)) | stack: [] --- end [Term(Bool(true))] # Biscuit samples and expected results root secret key: 99e87b0e9158531eeeb503ff15266e2b23c2a2507b138c9d1b1f2ab458df2d61 From 85d7d51d8a9f64838c5883ee64d122e738e03c33 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Sun, 26 May 2024 18:58:07 +0200 Subject: [PATCH 16/18] feat: v5 block detection for closures and new operators --- biscuit-auth/samples/README.md | 2 +- biscuit-auth/samples/samples.json | 2 +- .../samples/test032_laziness_closures.bc | Bin 758 -> 758 bytes biscuit-auth/src/datalog/mod.rs | 30 ++++++++++++------ 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/biscuit-auth/samples/README.md b/biscuit-auth/samples/README.md index 3862e091..599c846e 100644 --- a/biscuit-auth/samples/README.md +++ b/biscuit-auth/samples/README.md @@ -2791,7 +2791,7 @@ allow if true; ``` revocation ids: -- `65c9ae800acea84acc516a767c34f61d68c23d1b2538a6f2dc25aac7d2fe5fe9d2fdf61dfadfbdedf7b888a62f883aebd26abf5bfdd6d3f8d92bd2b221288b0b` +- `65e4da4fa213559d3b1097424504d2c9daeb28b4db51c49254852b6f57dc55e200f2f977b459f0c35e17c3c06394bfcaf5db7106e23bb2a623f48c4b84649a0b` authorizer world: ``` diff --git a/biscuit-auth/samples/samples.json b/biscuit-auth/samples/samples.json index e74635d5..5244f18e 100644 --- a/biscuit-auth/samples/samples.json +++ b/biscuit-auth/samples/samples.json @@ -2650,7 +2650,7 @@ }, "authorizer_code": "allow if true;\n", "revocation_ids": [ - "65c9ae800acea84acc516a767c34f61d68c23d1b2538a6f2dc25aac7d2fe5fe9d2fdf61dfadfbdedf7b888a62f883aebd26abf5bfdd6d3f8d92bd2b221288b0b" + "65e4da4fa213559d3b1097424504d2c9daeb28b4db51c49254852b6f57dc55e200f2f977b459f0c35e17c3c06394bfcaf5db7106e23bb2a623f48c4b84649a0b" ] } } diff --git a/biscuit-auth/samples/test032_laziness_closures.bc b/biscuit-auth/samples/test032_laziness_closures.bc index 7f716d7803fb40e7b09b7ed996a9b3663908311e..b95a36efbf740a585241ac3209c0028f44883484 100644 GIT binary patch delta 93 zcmV-j0HXi)1@;9I63+z+ON3;alhFbt$!RJG delta 93 zcmV-j0HXi)1@;9I63+z+DU9caQm z8znfV^4uk=$I|{^>C*l79s1wB?f1BdrZ0#(>(Xk!Tm9D4_}MGcvLPsolhFbtEfXzt diff --git a/biscuit-auth/src/datalog/mod.rs b/biscuit-auth/src/datalog/mod.rs index 71cf84cd..8b954e6b 100644 --- a/biscuit-auth/src/datalog/mod.rs +++ b/biscuit-auth/src/datalog/mod.rs @@ -878,7 +878,7 @@ impl SchemaVersion { } } else if version < 5 && self.contains_v5 { Err(error::Format::DeserializationError( - "v5 blocks must not have reject if".to_string(), + "v3 or v4 blocks must not have v5 features".to_string(), )) } else { Ok(()) @@ -918,7 +918,7 @@ pub fn get_schema_version( .any(|query| contains_v4_op(&query.expressions)) }); - // null + // null, heterogeneous equals, closures if !contains_v5 { contains_v5 = rules.iter().any(|rule| { contains_v5_predicate(&rule.head) @@ -967,10 +967,16 @@ fn contains_v5_op(expressions: &[Expression]) -> bool { expressions.iter().any(|expression| { expression.ops.iter().any(|op| match op { Op::Value(term) => contains_v5_term(term), - Op::Binary(binary) => match binary { - Binary::HeterogeneousEqual | Binary::HeterogeneousNotEqual => true, - _ => false, - }, + Op::Closure(_, _) => true, + Op::Binary(binary) => matches!( + binary, + Binary::HeterogeneousEqual + | Binary::HeterogeneousNotEqual + | Binary::LazyAnd + | Binary::LazyOr + | Binary::All + | Binary::Any + ), _ => false, }) }) @@ -1049,10 +1055,14 @@ mod tests { println!("adding r2: {}", syms.print_rule(&r2)); w.add_rule(0, &[0].iter().collect(), r2); - w.run_with_limits(&syms, RunLimits { - max_time: Duration::from_secs(10), - ..Default::default() - }).unwrap(); + w.run_with_limits( + &syms, + RunLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }, + ) + .unwrap(); println!("parents:"); let res = w From 159998bc49a530f524f82fdd39c724d896928226 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Sun, 26 May 2024 19:13:46 +0200 Subject: [PATCH 17/18] trigger shadowing detection --- biscuit-auth/examples/testcases.rs | 8 ++++++ biscuit-auth/samples/README.md | 41 ++++++++++++++++++++++++++++++ biscuit-auth/samples/samples.json | 35 +++++++++++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/biscuit-auth/examples/testcases.rs b/biscuit-auth/examples/testcases.rs index e5b66b0a..82ee78ae 100644 --- a/biscuit-auth/examples/testcases.rs +++ b/biscuit-auth/examples/testcases.rs @@ -2110,6 +2110,14 @@ fn closures(target: &str, root: &KeyPair, test: bool) -> TestResult { "".to_string(), validate_token(root, &data[..], "allow if true"), ); + validations.insert( + "shadowing".to_string(), + validate_token( + root, + &data[..], + "allow if [true].any($p -> [true].all($p -> $p))", + ), + ); TestResult { title, diff --git a/biscuit-auth/samples/README.md b/biscuit-auth/samples/README.md index 599c846e..667cbedb 100644 --- a/biscuit-auth/samples/README.md +++ b/biscuit-auth/samples/README.md @@ -2824,4 +2824,45 @@ World { ``` result: `Ok(0)` +### validation for "shadowing" + +authorizer code: +``` +allow if [true].any($p -> [true].all($p -> $p)); +``` + +revocation ids: +- `65e4da4fa213559d3b1097424504d2c9daeb28b4db51c49254852b6f57dc55e200f2f977b459f0c35e17c3c06394bfcaf5db7106e23bb2a623f48c4b84649a0b` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if ![1, 2, 3].all($p -> $p == 2)", + "check if ![1, 2, 3].any($p -> $p > 3)", + "check if !false && true", + "check if (true || false) && true", + "check if [1, 2, 3].all($p -> $p > 0)", + "check if [1, 2, 3].any($p -> $p > 1 && [3, 4, 5].any($q -> $p == $q))", + "check if [1, 2, 3].any($p -> $p > 2)", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", + ], + }, +] + policies: [ + "allow if [true].any($p -> [true].all($p -> $p))", +] +} +``` + +result: `Err(Execution(ShadowedVariable))` diff --git a/biscuit-auth/samples/samples.json b/biscuit-auth/samples/samples.json index 5244f18e..82856d84 100644 --- a/biscuit-auth/samples/samples.json +++ b/biscuit-auth/samples/samples.json @@ -2652,6 +2652,41 @@ "revocation_ids": [ "65e4da4fa213559d3b1097424504d2c9daeb28b4db51c49254852b6f57dc55e200f2f977b459f0c35e17c3c06394bfcaf5db7106e23bb2a623f48c4b84649a0b" ] + }, + "shadowing": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if ![1, 2, 3].all($p -> $p == 2)", + "check if ![1, 2, 3].any($p -> $p > 3)", + "check if !false && true", + "check if (true || false) && true", + "check if [1, 2, 3].all($p -> $p > 0)", + "check if [1, 2, 3].any($p -> $p > 1 && [3, 4, 5].any($q -> $p == $q))", + "check if [1, 2, 3].any($p -> $p > 2)", + "check if false || true", + "check if true || \"x\".intersection(\"x\")" + ] + } + ], + "policies": [ + "allow if [true].any($p -> [true].all($p -> $p))" + ] + }, + "result": { + "Err": { + "Execution": "ShadowedVariable" + } + }, + "authorizer_code": "allow if [true].any($p -> [true].all($p -> $p));\n", + "revocation_ids": [ + "65e4da4fa213559d3b1097424504d2c9daeb28b4db51c49254852b6f57dc55e200f2f977b459f0c35e17c3c06394bfcaf5db7106e23bb2a623f48c4b84649a0b" + ] } } } From fab2f24470e62b7d02f2b717893cab2a694f4c26 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Sun, 26 May 2024 19:42:57 +0200 Subject: [PATCH 18/18] improve coverage for closures handling --- biscuit-auth/src/datalog/expression.rs | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index 722f0d21..c07e42e4 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -825,6 +825,35 @@ mod tests { let res1 = e1.evaluate(&HashMap::new(), &mut tmp_symbols).unwrap(); assert_eq!(res1, Term::Bool(true)); + + let ops2 = vec![ + Op::Value(Term::Set([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::LessThan), + ], + ), + Op::Binary(Binary::Any), + ]; + let e2 = Expression { ops: ops2 }; + println!("{:?}", e2.print(&symbols)); + + let res2 = e2.evaluate(&HashMap::new(), &mut tmp_symbols).unwrap(); + assert_eq!(res2, Term::Bool(false)); + + let ops3 = vec![ + Op::Value(Term::Set([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure(vec![p], vec![Op::Value(Term::Integer(0))]), + Op::Binary(Binary::Any), + ]; + let e3 = Expression { ops: ops3 }; + println!("{:?}", e3.print(&symbols)); + + let err3 = e3.evaluate(&HashMap::new(), &mut tmp_symbols).unwrap_err(); + assert_eq!(err3, error::Expression::InvalidType); } #[test] @@ -850,6 +879,35 @@ mod tests { let res1 = e1.evaluate(&HashMap::new(), &mut tmp_symbols).unwrap(); assert_eq!(res1, Term::Bool(true)); + + let ops2 = vec![ + Op::Value(Term::Set([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::LessThan), + ], + ), + Op::Binary(Binary::All), + ]; + let e2 = Expression { ops: ops2 }; + println!("{:?}", e2.print(&symbols)); + + let res2 = e2.evaluate(&HashMap::new(), &mut tmp_symbols).unwrap(); + assert_eq!(res2, Term::Bool(false)); + + let ops3 = vec![ + Op::Value(Term::Set([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure(vec![p], vec![Op::Value(Term::Integer(0))]), + Op::Binary(Binary::All), + ]; + let e3 = Expression { ops: ops3 }; + println!("{:?}", e3.print(&symbols)); + + let err3 = e3.evaluate(&HashMap::new(), &mut tmp_symbols).unwrap_err(); + assert_eq!(err3, error::Expression::InvalidType); } #[test]