Skip to content

Commit

Permalink
Deal with unknown attributes as JSON literals
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Snaps <[email protected]>
  • Loading branch information
alexsnaps committed Oct 22, 2024
1 parent ff92dc9 commit f6ca3f6
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 47 deletions.
130 changes: 85 additions & 45 deletions src/data/cel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use cel_interpreter::objects::{Map, ValueType};
use cel_interpreter::{Context, Value};
use cel_parser::{parse, Expression as CelExpression, Member, ParseError};
use chrono::{DateTime, FixedOffset};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::sync::OnceLock;
Expand All @@ -24,14 +25,11 @@ impl Expression {
let attributes = props
.into_iter()
.map(|tokens| {
known_attribute_for(&Path::new(tokens))
// resolve to known root, and then inspect proper location
// path = ["auth", "identity", "anonymous", ...]
// UnknownAttribute { known_root: Path, Path }
//
// e.g. known part: ["auth", "identity"] => map it proper location
// ...anonymous
.expect("Unknown attribute")
let path = Path::new(tokens);
known_attribute_for(&path).unwrap_or(Attribute {
path,
cel_type: None,
})
})
.collect();

Expand Down Expand Up @@ -85,7 +83,7 @@ impl Predicate {

pub struct Attribute {
path: Path,
cel_type: ValueType,
cel_type: Option<ValueType>,
}

impl Debug for Attribute {
Expand All @@ -98,43 +96,49 @@ impl Clone for Attribute {
fn clone(&self) -> Self {
Attribute {
path: self.path.clone(),
cel_type: copy(&self.cel_type),
cel_type: self.cel_type.as_ref().map(copy),
}
}
}

impl Attribute {
pub fn get(&self) -> Value {
match self.cel_type {
ValueType::String => get_attribute::<String>(&self.path)
.expect("Failed getting to known attribute")
.map(|v| Value::String(v.into()))
.unwrap_or(Value::Null),
ValueType::Int => get_attribute::<i64>(&self.path)
.expect("Failed getting to known attribute")
.map(Value::Int)
.unwrap_or(Value::Null),
ValueType::UInt => get_attribute::<u64>(&self.path)
.expect("Failed getting to known attribute")
.map(Value::UInt)
.unwrap_or(Value::Null),
ValueType::Float => get_attribute::<f64>(&self.path)
.expect("Failed getting to known attribute")
.map(Value::Float)
.unwrap_or(Value::Null),
ValueType::Bool => get_attribute::<bool>(&self.path)
.expect("Failed getting to known attribute")
.map(Value::Bool)
.unwrap_or(Value::Null),
ValueType::Bytes => get_attribute::<Vec<u8>>(&self.path)
.expect("Failed getting to known attribute")
.map(|v| Value::Bytes(v.into()))
.unwrap_or(Value::Null),
ValueType::Timestamp => get_attribute::<DateTime<FixedOffset>>(&self.path)
.expect("Failed getting to known attribute")
.map(Value::Timestamp)
.unwrap_or(Value::Null),
_ => todo!("Need support for `{}`s!", self.cel_type),
match &self.cel_type {
Some(t) => match t {
ValueType::String => get_attribute::<String>(&self.path)
.expect("Failed getting to known attribute")
.map(|v| Value::String(v.into()))
.unwrap_or(Value::Null),
ValueType::Int => get_attribute::<i64>(&self.path)
.expect("Failed getting to known attribute")
.map(Value::Int)
.unwrap_or(Value::Null),
ValueType::UInt => get_attribute::<u64>(&self.path)
.expect("Failed getting to known attribute")
.map(Value::UInt)
.unwrap_or(Value::Null),
ValueType::Float => get_attribute::<f64>(&self.path)
.expect("Failed getting to known attribute")
.map(Value::Float)
.unwrap_or(Value::Null),
ValueType::Bool => get_attribute::<bool>(&self.path)
.expect("Failed getting to known attribute")
.map(Value::Bool)
.unwrap_or(Value::Null),
ValueType::Bytes => get_attribute::<Vec<u8>>(&self.path)
.expect("Failed getting to known attribute")
.map(|v| Value::Bytes(v.into()))
.unwrap_or(Value::Null),
ValueType::Timestamp => get_attribute::<DateTime<FixedOffset>>(&self.path)
.expect("Failed getting to known attribute")
.map(Value::Timestamp)
.unwrap_or(Value::Null),
_ => todo!("Need support for `{t}`s!"),
},
None => match get_attribute::<String>(&self.path).expect("Path must resolve!") {
None => Value::Null,
Some(json) => json_to_cel(&json),
},
}
}
}
Expand All @@ -146,10 +150,29 @@ pub fn known_attribute_for(path: &Path) -> Option<Attribute> {
.get(path)
.map(|t| Attribute {
path: path.clone(),
cel_type: copy(t),
cel_type: Some(copy(t)),
})
}

fn json_to_cel(json: &str) -> Value {
let json_value: JsonValue = serde_json::from_str(json).expect("json value must parse!");
match json_value {
JsonValue::Null => Value::Null,
JsonValue::Bool(b) => b.into(),
JsonValue::Number(n) => {
if n.is_u64() {
n.as_u64().unwrap().into()
} else if n.is_i64() {
n.as_i64().unwrap().into()
} else {
n.as_f64().unwrap().into()
}
}
JsonValue::String(str) => str.into(),
_ => todo!("Need support for more Json!"),
}
}

fn copy(value_type: &ValueType) -> ValueType {
match value_type {
ValueType::List => ValueType::List,
Expand Down Expand Up @@ -234,8 +257,6 @@ fn new_well_known_attribute_map() -> HashMap<Path, ValueType> {
("filter_state".into(), ValueType::Map),
("connection.mtls".into(), ValueType::Bool),
("request.raw_body".into(), ValueType::Bytes),
("auth.identity".into(), ValueType::Bytes),
("auth.identity.anonymous".into(), ValueType::Bytes),
])
}

Expand Down Expand Up @@ -416,7 +437,7 @@ pub mod data {

#[cfg(test)]
mod tests {
use crate::data::cel::{known_attribute_for, Predicate};
use crate::data::cel::{known_attribute_for, Expression, Predicate};
use cel_interpreter::objects::ValueType;

#[test]
Expand All @@ -426,6 +447,25 @@ mod tests {
assert!(predicate.test());
}

#[test]
fn expressions_to_json_resolve() {
super::super::property::test::TEST_PROPERTY_VALUE.set(Some("true".bytes().collect()));
let value = Expression::new("auth.identity.anonymous").unwrap().eval();
assert_eq!(value, true.into());
super::super::property::test::TEST_PROPERTY_VALUE.set(Some("42".bytes().collect()));
let value = Expression::new("auth.identity.age").unwrap().eval();
assert_eq!(value, 42.into());
super::super::property::test::TEST_PROPERTY_VALUE.set(Some("42.3".bytes().collect()));
let value = Expression::new("auth.identity.age").unwrap().eval();
assert_eq!(value, 42.3.into());
super::super::property::test::TEST_PROPERTY_VALUE.set(Some("\"John\"".bytes().collect()));
let value = Expression::new("auth.identity.name").unwrap().eval();
assert_eq!(value, "John".into());
super::super::property::test::TEST_PROPERTY_VALUE.set(Some("-42".bytes().collect()));
let value = Expression::new("auth.identity.age").unwrap().eval();
assert_eq!(value, (-42).into());
}

#[test]
fn attribute_resolve() {
super::super::property::test::TEST_PROPERTY_VALUE.set(Some(80_i64.to_le_bytes().into()));
Expand All @@ -444,7 +484,7 @@ mod tests {
let attr = known_attribute_for(&path).expect("Must be a hit!");
assert_eq!(attr.path, path);
match attr.cel_type {
ValueType::String => {}
Some(ValueType::String) => {}
_ => panic!("Not supposed to get here!"),
}
}
Expand Down
20 changes: 18 additions & 2 deletions src/data/property.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::data::attribute::KUADRANT_NAMESPACE;
use log::debug;
use log::warn;
use proxy_wasm::types::Status;
Expand All @@ -24,6 +25,12 @@ fn remote_address() -> Result<Option<Vec<u8>>, Status> {
}
}

fn wasm_prop(tokens: &[&str]) -> Path {
let mut flat_attr = format!("wasm\\.{KUADRANT_NAMESPACE}\\.");
flat_attr.push_str(tokens.join("\\.").as_str());
flat_attr.as_str().into()
}

#[cfg(test)]
fn host_get_property(path: &Path) -> Result<Option<Vec<u8>>, Status> {
debug!("get_property: {:?}", path);
Expand All @@ -37,8 +44,10 @@ fn host_get_property(path: &Path) -> Result<Option<Vec<u8>>, Status> {
}

pub fn get_property(path: &Path) -> Result<Option<Vec<u8>>, Status> {
match path.tokens()[..] {
match *path.tokens() {
["source", "remote_address"] => remote_address(),
// todo, unsure whether to drop the "auth" part here...
["auth", ..] => host_get_property(&wasm_prop(path.tokens().as_slice())),
// for auth stuff => resolve_host_props() => json string literal as Bytes
_ => host_get_property(path),
}
Expand Down Expand Up @@ -107,7 +116,7 @@ impl Path {

#[cfg(test)]
pub mod test {
use crate::data::property::Path;
use super::*;
use std::cell::Cell;

thread_local!(
Expand Down Expand Up @@ -146,4 +155,11 @@ pub mod test {
let path: Path = r"\one".into();
assert_eq!(path.tokens(), vec!["one"]);
}

#[test]
fn flat_wasm_prop() {
let path = wasm_prop(&["auth", "identity", "anonymous"]);
assert_eq!(path.tokens().len(), 1);
assert_eq!(path.tokens()[0], "wasm.kuadrant.auth.identity.anonymous");
}
}

0 comments on commit f6ca3f6

Please sign in to comment.