pub mod cst;
mod cst_to_ast;
pub mod err;
mod fmt;
mod node;
pub use node::{ASTNode, SourceInfo};
pub mod text_to_cst;
pub(crate) mod unescape;
use smol_str::SmolStr;
use std::collections::HashMap;
use crate::ast;
use crate::est;
pub fn parse_policyset(text: &str) -> Result<ast::PolicySet, Vec<err::ParseError>> {
let mut errs = Vec::new();
text_to_cst::parse_policies(text)?
.to_policyset(&mut errs)
.ok_or(errs)
}
pub fn parse_policyset_to_ests_and_pset(
text: &str,
) -> Result<(HashMap<ast::PolicyID, est::Policy>, ast::PolicySet), err::ParseErrors> {
let mut errs = Vec::new();
let cst = text_to_cst::parse_policies(text).map_err(err::ParseErrors)?;
let pset = cst
.to_policyset(&mut errs)
.ok_or_else(|| err::ParseErrors(errs.clone()))?;
#[allow(clippy::expect_used)]
let ests = cst
.with_generated_policyids()
.expect("shouldn't be None since parse_policies() and to_policyset didn't return Err")
.map(|(id, policy)| match &policy.node {
Some(p) => Ok(Some((id, p.clone().try_into()?))),
None => Ok(None),
})
.collect::<Result<Option<HashMap<ast::PolicyID, est::Policy>>, err::ParseErrors>>()?;
match (errs.is_empty(), ests) {
(true, Some(ests)) => Ok((ests, pset)),
(_, _) => Err(err::ParseErrors(errs)),
}
}
pub fn parse_policy_template(
id: Option<String>,
text: &str,
) -> Result<ast::Template, Vec<err::ParseError>> {
let mut errs = Vec::new();
let id = match id {
Some(id) => ast::PolicyID::from_string(id),
None => ast::PolicyID::from_string("policy0"),
};
let r = text_to_cst::parse_policy(text)?.to_policy_template(id, &mut errs);
if errs.is_empty() {
r.ok_or(errs).map(ast::Template::from)
} else {
Err(errs)
}
}
pub fn parse_policy_template_to_est_and_ast(
id: Option<String>,
text: &str,
) -> Result<(est::Policy, ast::Template), err::ParseErrors> {
let mut errs = Vec::new();
let id = match id {
Some(id) => ast::PolicyID::from_string(id),
None => ast::PolicyID::from_string("policy0"),
};
let cst = text_to_cst::parse_policy(text).map_err(err::ParseErrors)?;
let ast = cst
.to_policy_template(id, &mut errs)
.ok_or_else(|| err::ParseErrors(errs.clone()))?;
let est = cst.node.map(TryInto::try_into).transpose()?;
match (errs.is_empty(), est) {
(true, Some(est)) => Ok((est, ast)),
(_, _) => Err(err::ParseErrors(errs)),
}
}
pub fn parse_policy(
id: Option<String>,
text: &str,
) -> Result<ast::StaticPolicy, Vec<err::ParseError>> {
let mut errs = Vec::new();
let id = match id {
Some(id) => ast::PolicyID::from_string(id),
None => ast::PolicyID::from_string("policy0"),
};
let r = text_to_cst::parse_policy(text)?.to_policy(id, &mut errs);
if errs.is_empty() {
r.ok_or(errs)
} else {
Err(errs)
}
}
pub fn parse_policy_to_est_and_ast(
id: Option<String>,
text: &str,
) -> Result<(est::Policy, ast::StaticPolicy), err::ParseErrors> {
let mut errs = Vec::new();
let id = match id {
Some(id) => ast::PolicyID::from_string(id),
None => ast::PolicyID::from_string("policy0"),
};
let cst = text_to_cst::parse_policy(text).map_err(err::ParseErrors)?;
let ast = cst
.to_policy(id, &mut errs)
.ok_or_else(|| err::ParseErrors(errs.clone()))?;
let est = cst.node.map(TryInto::try_into).transpose()?;
match (errs.is_empty(), est) {
(true, Some(est)) => Ok((est, ast)),
(_, _) => Err(err::ParseErrors(errs)),
}
}
pub(crate) fn parse_expr(ptext: &str) -> Result<ast::Expr, Vec<err::ParseError>> {
let mut errs = Vec::new();
text_to_cst::parse_expr(ptext)?
.to_expr(&mut errs)
.ok_or(errs)
}
pub(crate) fn parse_restrictedexpr(
ptext: &str,
) -> Result<ast::RestrictedExpr, Vec<err::ParseError>> {
parse_expr(ptext)
.and_then(|expr| ast::RestrictedExpr::new(expr).map_err(|err| vec![err.into()]))
}
pub(crate) fn parse_euid(euid: &str) -> Result<ast::EntityUID, Vec<err::ParseError>> {
let mut errs = Vec::new();
text_to_cst::parse_ref(euid)?.to_ref(&mut errs).ok_or(errs)
}
pub(crate) fn parse_name(name: &str) -> Result<ast::Name, Vec<err::ParseError>> {
let mut errs = Vec::new();
text_to_cst::parse_name(name)?
.to_name(&mut errs)
.ok_or(errs)
}
pub(crate) fn parse_literal(val: &str) -> Result<ast::Literal, Vec<err::ParseError>> {
let mut errs = Vec::new();
match text_to_cst::parse_primary(val)?
.to_expr(&mut errs)
.ok_or(errs)?
.into_expr_kind()
{
ast::ExprKind::Lit(v) => Ok(v),
_ => Err(vec![err::ParseError::ToAST(
"text is not a literal".to_string(),
)]),
}
}
pub fn parse_internal_string(val: &str) -> Result<SmolStr, Vec<err::ParseError>> {
let mut errs = Vec::new();
text_to_cst::parse_primary(&format!(r#""{val}""#))?
.to_string_literal(&mut errs)
.ok_or(errs)
}
pub(crate) fn parse_ident(id: &str) -> Result<ast::Id, Vec<err::ParseError>> {
let mut errs = Vec::new();
text_to_cst::parse_ident(id)?
.to_valid_ident(&mut errs)
.ok_or(errs)
}
#[cfg(test)]
mod test {
use super::*;
use crate::ast::{test_generators::*, Template};
use itertools::Itertools;
use std::collections::HashSet;
#[test]
fn test_template_parsing() {
for template in all_templates().map(Template::from) {
let id = template.id();
let src = format!("{template}");
let parsed = parse_policy_template(Some(id.to_string()), &src);
match parsed {
Ok(p) => {
assert_eq!(
p.slots().collect::<HashSet<_>>(),
template.slots().collect::<HashSet<_>>()
);
assert_eq!(p.id(), template.id());
assert_eq!(p.effect(), template.effect());
assert_eq!(p.principal_constraint(), template.principal_constraint());
assert_eq!(p.action_constraint(), template.action_constraint());
assert_eq!(p.resource_constraint(), template.resource_constraint());
assert!(
p.non_head_constraints()
.eq_shape(template.non_head_constraints()),
"{:?} and {:?} should have the same shape.",
p.non_head_constraints(),
template.non_head_constraints()
);
}
Err(e) => panic!(
"Failed to parse {src}, {}",
e.into_iter().map(|e| format!("{e}")).join("\n")
),
}
}
}
#[test]
fn test_error_out() {
let errors = parse_policyset(
r#"
permit(principal:p,action:a,resource:r)
when{w or if c but not z} // expr error
unless{u if c else d or f} // expr error
advice{"doit"};
permit(principality in Group::"jane_friends", // policy error
action in [PhotoOp::"view", PhotoOp::"comment"],
resource in Album::"jane_trips");
forbid(principal, action, resource)
when { "private" in resource.tags }
unless { resource in principal.account };
"#,
)
.expect_err("multiple errors above");
println!("{:?}", errors);
assert!(errors.len() >= 3);
}
}
#[cfg(test)]
mod eval_tests {
use super::*;
use crate::evaluator as eval;
use crate::extensions::Extensions;
#[test]
fn interpret_exprs() {
let request = eval::test::basic_request();
let entities = eval::test::basic_entities();
let exts = Extensions::none();
let evaluator = eval::Evaluator::new(&request, &entities, &exts).unwrap();
let expr = parse_expr("false").expect("parse fail");
assert_eq!(
evaluator
.interpret_inline_policy(&expr)
.expect("interpret fail"),
ast::Value::Lit(ast::Literal::Bool(false))
);
let expr = parse_expr("true && true").expect("parse fail");
assert_eq!(
evaluator
.interpret_inline_policy(&expr)
.expect("interpret fail"),
ast::Value::Lit(ast::Literal::Bool(true))
);
let expr = parse_expr("!true || false && !true").expect("parse fail");
assert_eq!(
evaluator
.interpret_inline_policy(&expr)
.expect("interpret fail"),
ast::Value::Lit(ast::Literal::Bool(false))
);
let expr = parse_expr("!!!!true").expect("parse fail");
assert_eq!(
evaluator
.interpret_inline_policy(&expr)
.expect("interpret fail"),
ast::Value::Lit(ast::Literal::Bool(true))
);
let expr = parse_expr(
r#"
if false || true != 4 then
600
else
-200
"#,
)
.expect("parse fail");
assert_eq!(
evaluator
.interpret_inline_policy(&expr)
.expect("interpret fail"),
ast::Value::Lit(ast::Literal::Long(600))
);
}
#[test]
fn interpret_membership() {
let request = eval::test::basic_request();
let entities = eval::test::rich_entities();
let exts = Extensions::none();
let evaluator = eval::Evaluator::new(&request, &entities, &exts).unwrap();
let expr = parse_expr(
r#"
test_entity_type::"child" in
test_entity_type::"unrelated"
"#,
)
.expect("parse fail");
assert_eq!(
evaluator
.interpret_inline_policy(&expr)
.expect("interpret fail"),
ast::Value::Lit(ast::Literal::Bool(false))
);
let expr = parse_expr(
r#"
test_entity_type::"child" in
test_entity_type::"child"
"#,
)
.expect("parse fail");
assert_eq!(
evaluator
.interpret_inline_policy(&expr)
.expect("interpret fail"),
ast::Value::Lit(ast::Literal::Bool(true))
);
let expr = parse_expr(
r#"
other_type::"other_child" in
test_entity_type::"parent"
"#,
)
.expect("parse fail");
assert_eq!(
evaluator
.interpret_inline_policy(&expr)
.expect("interpret fail"),
ast::Value::Lit(ast::Literal::Bool(true))
);
let expr = parse_expr(
r#"
test_entity_type::"child" in
test_entity_type::"grandparent"
"#,
)
.expect("parse fail");
assert_eq!(
evaluator
.interpret_inline_policy(&expr)
.expect("interpret fail"),
ast::Value::Lit(ast::Literal::Bool(true))
);
}
#[test]
fn interpret_relation() {
let request = eval::test::basic_request();
let entities = eval::test::basic_entities();
let exts = Extensions::none();
let evaluator = eval::Evaluator::new(&request, &entities, &exts).unwrap();
let expr = parse_expr(
r#"
3 < 2 || 2 > 3
"#,
)
.expect("parse fail");
assert_eq!(
evaluator
.interpret_inline_policy(&expr)
.expect("interpret fail"),
ast::Value::Lit(ast::Literal::Bool(false))
);
let expr = parse_expr(
r#"
7 <= 7 && 4 != 5
"#,
)
.expect("parse fail");
assert_eq!(
evaluator
.interpret_inline_policy(&expr)
.expect("interpret fail"),
ast::Value::Lit(ast::Literal::Bool(true))
);
}
}
#[cfg(test)]
mod parse_tests {
use super::*;
#[test]
fn parse_exists() {
let result = parse_policyset(
r#"
permit(principal, action, resource)
when{ true };
"#,
);
assert!(!result.expect("parse error").is_empty());
}
#[test]
fn test_parse_string() {
assert_eq!(
ast::Eid::new(parse_internal_string(r#"a\nblock\nid"#).expect("should parse"))
.to_string(),
r#"a\nblock\nid"#,
);
parse_internal_string(r#"oh, no, a '! "#).expect("single quote should be fine");
parse_internal_string(r#"oh, no, a "! "#).expect_err("double quote not allowed");
parse_internal_string(r#"oh, no, a \"! and a \'! "#).expect("escaped quotes should parse");
}
#[test]
fn good_cst_bad_ast() {
let src = r#"
permit(principal, action, resource) when { principal.name.like == "3" };
"#;
let _ = parse_policyset_to_ests_and_pset(src);
}
#[test]
fn no_slots_in_condition() {
let srcs = [
r#"
permit(principal, action, resource) when {
resource == ?resource
};
"#,
r#"
permit(principal, action, resource) when {
resource == ?principal
};
"#,
r#"
permit(principal, action, resource) when {
resource == ?blah
};
"#,
r#"
permit(principal, action, resource) unless {
resource == ?resource
};
"#,
r#"
permit(principal, action, resource) unless {
resource == ?principal
};
"#,
r#"
permit(principal, action, resource) unless {
resource == ?blah
};
"#,
r#"
permit(principal, action, resource) unless {
resource == ?resource
} when {
resource == ?resource
}
"#,
];
for src in srcs {
let p = parse_policy(None, src);
assert!(p.is_err());
let p = parse_policy_template(None, src);
assert!(p.is_err());
let p = parse_policy_to_est_and_ast(None, src);
assert!(p.is_err());
let p = parse_policy_template_to_est_and_ast(None, src);
assert!(p.is_err());
let p = parse_policyset(src);
assert!(p.is_err());
let p = parse_policyset_to_ests_and_pset(src);
assert!(p.is_err());
}
}
}