diff --git a/hipcheck/src/analysis/score.rs b/hipcheck/src/analysis/score.rs index 4817af08..ed6b073f 100644 --- a/hipcheck/src/analysis/score.rs +++ b/hipcheck/src/analysis/score.rs @@ -279,8 +279,8 @@ pub fn score_results(_phase: &SpinnerPhase, db: &dyn ScoringProvider) -> Result< // Determine if analysis passed by evaluating policy expr let passed = { if let Ok(output) = &response { - Executor::std() - .run(analysis.1.as_str(), &output.value) + Executor::std(output.value.clone()) + .run(analysis.1.as_str()) .map_err(|e| hc_error!("{}", e))? } else { false diff --git a/hipcheck/src/policy_exprs/env.rs b/hipcheck/src/policy_exprs/env.rs index 98605cd9..2ae25919 100644 --- a/hipcheck/src/policy_exprs/env.rs +++ b/hipcheck/src/policy_exprs/env.rs @@ -2,6 +2,7 @@ use crate::policy_exprs::{eval, Error, Expr, Ident, Primitive, Result, F64}; use itertools::Itertools as _; +use serde_json::Value; use std::{cmp::Ordering, collections::HashMap, ops::Not as _}; use Expr::*; use Primitive::*; @@ -13,6 +14,8 @@ pub struct Env<'parent> { /// Possible pointer to parent, for lexical scope. parent: Option<&'parent Env<'parent>>, + + pub context: Value, } /// A binding in the environment. @@ -30,16 +33,17 @@ type Op = fn(&Env, &[Expr]) -> Result; impl<'parent> Env<'parent> { /// Create an empty environment. - fn empty() -> Self { + fn empty(context: Value) -> Self { Env { bindings: HashMap::new(), parent: None, + context, } } /// Create the standard environment. - pub fn std() -> Self { - let mut env = Env::empty(); + pub fn std(context: Value) -> Self { + let mut env = Env::empty(context); // Comparison functions. env.add_fn("gt", gt); @@ -87,6 +91,7 @@ impl<'parent> Env<'parent> { Env { bindings: HashMap::new(), parent: Some(self), + context: self.context.clone(), } } diff --git a/hipcheck/src/policy_exprs/expr.rs b/hipcheck/src/policy_exprs/expr.rs index b1e00740..38c6a8c5 100644 --- a/hipcheck/src/policy_exprs/expr.rs +++ b/hipcheck/src/policy_exprs/expr.rs @@ -58,8 +58,8 @@ pub struct Ident(pub String); /// A late-binding for a JSON pointer #[derive(Debug, Clone, PartialEq, Eq)] pub struct JsonPointer { - pointer: String, - value: Option, + pub pointer: String, + pub value: Option, } /// A non-NaN 64-bit floating point number. diff --git a/hipcheck/src/policy_exprs/json_pointer.rs b/hipcheck/src/policy_exprs/json_pointer.rs index b655799f..ddd21b21 100644 --- a/hipcheck/src/policy_exprs/json_pointer.rs +++ b/hipcheck/src/policy_exprs/json_pointer.rs @@ -77,7 +77,7 @@ fn process_pointer(pointer: &str, context: &Value) -> Result { } /// Wrap serde_json's `Value::pointer` method to provide better error handling. -fn lookup_json_pointer<'val>(pointer: &str, context: &'val Value) -> Result<&'val Value> { +pub fn lookup_json_pointer<'val>(pointer: &str, context: &'val Value) -> Result<&'val Value> { // serde_json's JSON Pointer implementation does not distinguish between // syntax errors and lookup errors, so we check the syntax ourselves. // The only syntax error that serde_json currently recognizes is that a @@ -101,11 +101,15 @@ fn lookup_json_pointer<'val>(pointer: &str, context: &'val Value) -> Result<&'va /// Attempt to interpret a JSON Value as a Policy Expression. /// `pointer` and `context` are only passed in to provide more context in the /// case of errors. -fn json_to_policy_expr(val: &Value, pointer: &str, context: &Value) -> Result { +pub fn json_to_policy_expr(val: &Value, pointer: &str, context: &Value) -> Result { match val { Value::Number(n) => { - let not_nan = NotNan::new(n.as_f64().unwrap()).unwrap(); - Ok(Expr::Primitive(Primitive::Float(not_nan))) + if n.is_i64() { + Ok(Expr::Primitive(Primitive::Int(n.as_i64().unwrap()))) + } else { + let not_nan = NotNan::new(n.as_f64().unwrap()).unwrap(); + Ok(Expr::Primitive(Primitive::Float(not_nan))) + } } Value::Bool(b) => Ok(Expr::Primitive(Primitive::Bool(*b))), Value::Array(a) => { diff --git a/hipcheck/src/policy_exprs/mod.rs b/hipcheck/src/policy_exprs/mod.rs index c030275e..5b44a2c3 100644 --- a/hipcheck/src/policy_exprs/mod.rs +++ b/hipcheck/src/policy_exprs/mod.rs @@ -17,6 +17,7 @@ pub use crate::policy_exprs::{ token::LexingError, }; use env::Binding; +use expr::JsonPointer; pub use expr::{parse, Primitive}; use json_pointer::process_json_pointers; use serde_json::Value; @@ -29,22 +30,23 @@ pub struct Executor { impl Executor { /// Create an `Executor` with the standard set of functions defined. - pub fn std() -> Self { - Executor { env: Env::std() } + pub fn std(context: Value) -> Self { + Executor { + env: Env::std(context), + } } /// Run a `deke` program. - pub fn run(&self, raw_program: &str, context: &Value) -> Result { - match self.parse_and_eval(raw_program, context)? { + pub fn run(&self, raw_program: &str) -> Result { + match self.parse_and_eval(raw_program)? { Expr::Primitive(Primitive::Bool(b)) => Ok(b), result => Err(Error::DidNotReturnBool(result)), } } /// Run a `deke` program, but don't try to convert the result to a `bool`. - pub fn parse_and_eval(&self, raw_program: &str, context: &Value) -> Result { - let processed_program = process_json_pointers(raw_program, context)?; - let program = parse(&processed_program)?; + pub fn parse_and_eval(&self, raw_program: &str) -> Result { + let program = parse(raw_program)?; let expr = eval(&self.env, &program)?; Ok(expr) } @@ -67,7 +69,11 @@ pub(crate) fn eval(env: &Env, program: &Expr) -> Result { } } Expr::Lambda(_, body) => Ok((**body).clone()), - Expr::JsonPointer(_) => unreachable!(), + Expr::JsonPointer(JsonPointer { pointer, .. }) => { + let val = json_pointer::lookup_json_pointer(pointer, &env.context)?; + let expr = json_pointer::json_to_policy_expr(val, pointer, &env.context)?; + Ok(expr) + } }; log::debug!("input: {program:?}, output: {output:?}"); @@ -78,13 +84,14 @@ pub(crate) fn eval(env: &Env, program: &Expr) -> Result { #[cfg(test)] mod tests { use super::*; + use serde_json::json; use test_log::test; #[test] fn run_bool() { let program = "#t"; let context = Value::Null; - let is_true = Executor::std().run(program, &context).unwrap(); + let is_true = Executor::std(context).run(program).unwrap(); assert!(is_true); } @@ -92,7 +99,7 @@ mod tests { fn run_basic() { let program = "(eq (add 1 2) 3)"; let context = Value::Null; - let is_true = Executor::std().run(program, &context).unwrap(); + let is_true = Executor::std(context).run(program).unwrap(); assert!(is_true); } @@ -100,7 +107,7 @@ mod tests { fn eval_basic() { let program = "(add 1 2)"; let context = Value::Null; - let result = Executor::std().parse_and_eval(program, &context).unwrap(); + let result = Executor::std(context).parse_and_eval(program).unwrap(); assert_eq!(result, Expr::Primitive(Primitive::Int(3))); } @@ -108,7 +115,7 @@ mod tests { fn eval_divz_int_zero() { let program = "(divz 1 0)"; let context = Value::Null; - let result = Executor::std().parse_and_eval(program, &context).unwrap(); + let result = Executor::std(context).parse_and_eval(program).unwrap(); assert_eq!( result, Expr::Primitive(Primitive::Float(F64::new(0.0).unwrap())) @@ -119,7 +126,7 @@ mod tests { fn eval_divz_int() { let program = "(divz 1 2)"; let context = Value::Null; - let result = Executor::std().parse_and_eval(program, &context).unwrap(); + let result = Executor::std(context).parse_and_eval(program).unwrap(); assert_eq!( result, Expr::Primitive(Primitive::Float(F64::new(0.5).unwrap())) @@ -130,7 +137,7 @@ mod tests { fn eval_divz_float() { let program = "(divz 1.0 2.0)"; let context = Value::Null; - let result = Executor::std().parse_and_eval(program, &context).unwrap(); + let result = Executor::std(context).parse_and_eval(program).unwrap(); assert_eq!( result, Expr::Primitive(Primitive::Float(F64::new(0.5).unwrap())) @@ -141,7 +148,7 @@ mod tests { fn eval_divz_float_zero() { let program = "(divz 1.0 0.0)"; let context = Value::Null; - let result = Executor::std().parse_and_eval(program, &context).unwrap(); + let result = Executor::std(context).parse_and_eval(program).unwrap(); assert_eq!( result, Expr::Primitive(Primitive::Float(F64::new(0.0).unwrap())) @@ -152,7 +159,7 @@ mod tests { fn eval_bools() { let program = "(neq 1 2)"; let context = Value::Null; - let result = Executor::std().parse_and_eval(program, &context).unwrap(); + let result = Executor::std(context).parse_and_eval(program).unwrap(); assert_eq!(result, Expr::Primitive(Primitive::Bool(true))); } @@ -160,7 +167,7 @@ mod tests { fn eval_array() { let program = "(max [1 4 6 10 2 3 0])"; let context = Value::Null; - let result = Executor::std().parse_and_eval(program, &context).unwrap(); + let result = Executor::std(context).parse_and_eval(program).unwrap(); assert_eq!(result, Expr::Primitive(Primitive::Int(10))); } @@ -168,7 +175,7 @@ mod tests { fn run_array() { let program = "(eq 7 (count [1 4 6 10 2 3 0]))"; let context = Value::Null; - let is_true = Executor::std().run(program, &context).unwrap(); + let is_true = Executor::std(context).run(program).unwrap(); assert!(is_true); } @@ -176,7 +183,7 @@ mod tests { fn eval_higher_order_func() { let program = "(eq 3 (count (filter (gt 8.0) [1.0 2.0 10.0 20.0 30.0])))"; let context = Value::Null; - let result = Executor::std().parse_and_eval(program, &context).unwrap(); + let result = Executor::std(context).parse_and_eval(program).unwrap(); assert_eq!(result, Expr::Primitive(Primitive::Bool(true))); } @@ -185,7 +192,7 @@ mod tests { let program = "(eq 3 (count (filter (gt 8.0) (foreach (sub 1.0) [1.0 2.0 10.0 20.0 30.0]))))"; let context = Value::Null; - let result = Executor::std().parse_and_eval(program, &context).unwrap(); + let result = Executor::std(context).parse_and_eval(program).unwrap(); assert_eq!(result, Expr::Primitive(Primitive::Bool(true))); } @@ -193,7 +200,7 @@ mod tests { fn eval_basic_filter() { let program = "(filter (eq 0) [1 0 1 0 0 1 2])"; let context = Value::Null; - let result = Executor::std().parse_and_eval(program, &context).unwrap(); + let result = Executor::std(context).parse_and_eval(program).unwrap(); assert_eq!( result, Expr::Array(vec![ @@ -203,4 +210,35 @@ mod tests { ]) ); } + + #[test] + fn eval_basic_json_pointer_root_bool() { + let program = "$"; + let context = Value::Bool(true); + let result = Executor::std(context).parse_and_eval(program).unwrap(); + assert_eq!(result, Expr::Primitive(Primitive::Bool(true))); + } + + #[test] + fn eval_basic_json_pointer_i64() { + let program = "$/answer"; + let context = json!({ + "answer": 42, + }); + let result = Executor::std(context).parse_and_eval(program).unwrap(); + assert_eq!(result, Expr::Primitive(Primitive::Int(42))); + } + + #[test] + fn eval_basic_json_pointer_f64() { + let program = "$/number"; + let context = json!({ + "number": 7.3, + }); + let result = Executor::std(context).parse_and_eval(program).unwrap(); + assert_eq!( + result, + Expr::Primitive(Primitive::Float(F64::new(7.3).unwrap())) + ); + } } diff --git a/hipcheck/src/report/mod.rs b/hipcheck/src/report/mod.rs index 96c9a458..ad3a09d8 100644 --- a/hipcheck/src/report/mod.rs +++ b/hipcheck/src/report/mod.rs @@ -687,8 +687,8 @@ impl RecommendationKind { fn is(risk_score: RiskScore, risk_policy: RiskPolicy) -> Result { let value = serde_json::to_value(risk_score.0).unwrap(); Ok( - if Executor::std() - .run(&risk_policy.0, &value) + if Executor::std(value) + .run(&risk_policy.0) .context("investigate policy expression execution failed")? { RecommendationKind::Pass