lalrpop_mod!(
#[allow(warnings, unused)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
#[allow(clippy::unreachable)]
#[allow(clippy::panic)]
pub grammar,
"/src/parser/grammar.rs"
);
use super::*;
use std::sync::Arc;
fn parse_collect_errors<'a, P, T>(
parser: &P,
parse: impl FnOnce(
&P,
&mut Vec<err::RawErrorRecovery<'a>>,
&Arc<str>,
&'a str,
) -> Result<T, err::RawParseError<'a>>,
text: &'a str,
) -> Result<T, err::ParseErrors> {
let mut errs = Vec::new();
let result = parse(parser, &mut errs, &Arc::from(text), text);
let mut errors: err::ParseErrors = errs
.into_iter()
.map(err::ToCSTError::from_raw_err_recovery)
.collect();
let parsed = match result {
Ok(parsed) => parsed,
Err(e) => {
errors.push(err::ToCSTError::from_raw_parse_err(e));
return Err(errors);
}
};
if errors.is_empty() {
Ok(parsed)
} else {
Err(errors)
}
}
lazy_static::lazy_static! {
static ref POLICIES_PARSER: grammar::PoliciesParser = grammar::PoliciesParser::new();
static ref POLICY_PARSER: grammar::PolicyParser = grammar::PolicyParser::new();
static ref EXPR_PARSER: grammar::ExprParser = grammar::ExprParser::new();
static ref REF_PARSER: grammar::RefParser = grammar::RefParser::new();
static ref PRIMARY_PARSER: grammar::PrimaryParser = grammar::PrimaryParser::new();
static ref NAME_PARSER: grammar::NameParser = grammar::NameParser::new();
static ref IDENT_PARSER: grammar::IdentParser = grammar::IdentParser::new();
}
pub fn parse_policies(text: &str) -> Result<Node<Option<cst::Policies>>, err::ParseErrors> {
parse_collect_errors(&*POLICIES_PARSER, grammar::PoliciesParser::parse, text)
}
pub fn parse_policy(text: &str) -> Result<Node<Option<cst::Policy>>, err::ParseErrors> {
parse_collect_errors(&*POLICY_PARSER, grammar::PolicyParser::parse, text)
}
pub fn parse_expr(text: &str) -> Result<Node<Option<cst::Expr>>, err::ParseErrors> {
parse_collect_errors(&*EXPR_PARSER, grammar::ExprParser::parse, text)
}
pub fn parse_ref(text: &str) -> Result<Node<Option<cst::Ref>>, err::ParseErrors> {
parse_collect_errors(&*REF_PARSER, grammar::RefParser::parse, text)
}
pub fn parse_primary(text: &str) -> Result<Node<Option<cst::Primary>>, err::ParseErrors> {
parse_collect_errors(&*PRIMARY_PARSER, grammar::PrimaryParser::parse, text)
}
pub fn parse_name(text: &str) -> Result<Node<Option<cst::Name>>, err::ParseErrors> {
parse_collect_errors(&*NAME_PARSER, grammar::NameParser::parse, text)
}
pub fn parse_ident(text: &str) -> Result<Node<Option<cst::Ident>>, err::ParseErrors> {
parse_collect_errors(&*IDENT_PARSER, grammar::IdentParser::parse, text)
}
#[allow(clippy::panic)]
#[allow(clippy::indexing_slicing)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn expr1() {
assert!(parse_expr(
r#"
1
"#
)
.expect("parser fail")
.node
.is_some());
}
#[test]
fn expr2() {
assert!(parse_expr(
r#"
"string"
"#
)
.expect("parser fail")
.node
.is_some());
}
#[test]
fn expr3() {
assert!(parse_expr(
r#"
"string".foo == !7
"#
)
.expect("parser fail")
.node
.is_some());
}
#[test]
fn expr4() {
let result = parse_expr(
r#"
5 < 3 || -7 == 2 && 3 >= 6
"#,
)
.expect("parser fail")
.node;
assert!(result.is_some());
}
#[test]
fn expr5() {
assert!(parse_expr(
r#"
if 7 then 6 > 5 else !5 || "thursday"
"#
)
.expect("parser fail")
.node
.is_some());
}
#[test]
fn expr6() {
let result = parse_expr(
r#"
if 7 then 6 > 5 else !5 || "thursday" && ((8) >= "fish")
"#,
)
.expect("parser fail")
.node;
assert!(result.is_some());
}
#[test]
fn expr_overflow() {
assert!(parse_expr(
r#"
principal == -5555555555555555555555
"#
)
.is_err());
assert!(parse_expr(
r#"
principal == 5555555555555555555555
"#
)
.is_err());
}
#[test]
fn variable1() {
let policy = parse_policy(
r#"
permit(principal, action, var:h in 1);
"#,
);
assert!(policy.is_ok());
}
#[test]
fn variable2() {
let policy = parse_policy(
r#"
permit(principal, action, more in 2);
"#,
);
assert!(policy.is_ok());
}
#[test]
fn variable3() {
let policy = parse_policy(
r#"
permit(principal, action:a_name, resource);
"#,
);
assert!(policy.is_ok());
}
#[test]
fn variable4() {
let policy = parse_policy(
r#"
permit(principalorsomeotherident, action, resource);
"#,
);
assert!(policy.is_ok());
}
#[test]
fn variable6() {
let policy = parse_policy(
r#"
permit(var : in 6, action, resource);
"#,
);
assert!(policy.is_err());
}
#[test]
fn member1() {
let policy = parse_policy(
r#"
permit(principal, action, resource)
when{
2._field // oh, look, comments!
};
"#,
);
assert!(policy.is_ok());
}
#[test]
fn member2() {
let policy = parse_policy(
r#"
permit(principal, action, resource)
when{
"first".some_ident()
};
"#,
);
assert!(policy.is_ok());
}
#[test]
fn member3() {
let policy = parse_policy(
r#"
permit(principal, action, resource)
when{
[2,3,4].foo[2]
};
"#,
);
assert!(policy.is_ok());
}
#[test]
fn member4() {
let policy = parse_policy(
r#"
permit(principal, action, resource)
when{
{3<-4:"what?","ok then":-5>4}
};
"#,
);
assert!(policy.is_ok());
}
#[test]
fn member5() {
let policy = parse_policy(
r#"
permit(principal, action, resource)
when{
[3<4,"ok then",17,("none")]
};
"#,
);
assert!(policy.is_ok());
}
#[test]
fn member6() {
let policy = parse_policy(
r#"
permit(principal, action, resource)
when{
one.two
};
"#,
);
assert!(policy.is_ok());
}
#[test] fn member7() {
let policy = parse_policy(
r#"
permit(principal, action, resource)
when{
one{num:true,trivia:"first!"}
};
"#,
);
let errs = match policy.err() {
Some(pes) => pes,
_ => panic!("Expected parsing policy to error"),
};
assert!(errs.len() == 2);
assert!(format!("{:?}", errs[0])
.contains("ToCST(ToCSTError { err: UnrecognizedToken { token: (98, \"{\", 99)"));
assert!(format!("{:?}", errs[1])
.contains("ToCST(ToCSTError { err: UnrecognizedToken { token: (141, \"}\", 142)"));
}
#[test]
fn member8() {
let policy = parse_policy(
r#"
permit(principal, action, resource)
when{
{2:true,4:me}.with["pizza"]
};
"#,
);
assert!(policy.is_ok());
}
#[test]
fn member9() {
let policy = parse_policy(
r#"
permit(principal, action, resource)
when{
AllRects({two:2,four:3+5/5})
};
"#,
);
assert!(policy.is_ok());
}
#[test]
fn ident1() {
let ident = parse_ident(
r#"
principal
"#,
);
assert!(ident.is_ok());
}
#[test]
fn ident2() {
let ident = parse_ident(
r#"
if
"#,
);
assert!(ident.is_ok());
let ident = parse_ident(
r#"
false
"#,
);
assert!(ident.is_ok());
}
#[test]
fn ident3() {
let ident = parse_expr(
r#"
if
"#,
);
assert!(ident.is_err());
let name = parse_expr(
r#"
if::then::else
"#,
);
assert!(name.is_ok());
let names = parse_expr(
r#"
if::true::then::false::else::true
"#,
);
assert!(names.is_ok());
}
#[test]
fn ident4() {
let ident = parse_expr(
r#"
true(true)
"#,
);
assert!(ident.is_ok());
let ident = parse_expr(
r#"
if(true)
"#,
);
assert!(ident.is_err());
}
#[test]
fn ident5() {
let ident = parse_expr(
r#"
{true : false}
"#,
);
assert!(ident.is_ok());
let ident = parse_expr(
r#"
{ if : true }
"#,
);
assert!(ident.is_ok());
}
#[test]
fn ident6() {
let ident = parse_expr(
r#"
{true : false} has false
"#,
);
assert!(ident.is_ok());
let ident = parse_expr(
r#"
{ if : true } has if
"#,
);
assert!(ident.is_ok());
}
#[test]
fn comments_has() {
let policy_text = r#"
permit(principal, action,resource)
when{ principal //comment p
has //comment has
age //comment
};
"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn comments_like() {
let policy_text = r#"
permit(principal, action,resource)
when{ principal //comment p
like //comment like
age //comment
};
"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn comments_and() {
let policy_text = r#"
permit(principal, action,resource)
when{ 1 //comment p
&& //comment &&
//comment &&
"hello" //comment
};
"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn comments_or() {
let policy_text = r#"
permit(principal, action,resource)
when{ 1 //comment 1
// comment 1
|| //comment ||
//comments ||
"hello" //comment
//comment hello
};
"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn comments_add() {
let policy_text = r#"
permit(principal, action,resource)
when{ 1 //comment 1
//comment 1_2
+ //comment +
//comment +
2 //comment 2
//comment 2
};
"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn comments_paren() {
let policy_text = r#"
permit(principal, action,resource)
when{
( //comment 1
( //comment 2
1
) //comment 3
) //comment 4
};
"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn comments_set() {
let policy_text = r#"
permit(principal, action,resource)
when{
[ // comment 1
"hello" //comment 2
, // comment 3
// comment 3-2
1 //comment 4
//comment 5
] //comment 5-0
. //comment 5-1
contains //comment 5-2
( //comment 6
"a" //comment 7
) //comment 20
};
"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn comments_if() {
let policy_text = r#"
permit(principal, action,resource)
when{
( //comment open outer
( //comment open inner
if //comment if
1 //comment
< //comment <
2 //commment 2
then // comment then
"hello" //comment hello
else //comment else
1 //comment 1
) //comment close inner
) //comment close outer
};
"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn comments_member_access() {
let policy_text = r#"
permit(principal, action,resource)
when{ principal. //comment .
age // comment age
};
"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn comments_principal() {
let policy_text = r#"
permit(principal //comment 1
==
User::"alice" //comment 3
, //comment 4
action,resource);
"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn comments_annotation() {
let policy_text = r#"
//comment policy
// comment policy 2
@anno("good annotation") // comments after annotation
// comments after annotation 2
permit(principal //comment 1
==
User::"alice" //comment 3
, //comment 4
action,resource);
"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn comments_policy() {
let policy_text = r#"
//comment policy 1
//comment policy 2
permit( //comment 3
// comment 4
principal //comment principal
== //comment == 1
//comment == 2
User::"alice" //comment alice
, //comment comma 1
//comment comma 2
action //comment action 1
//comment action 2
, //comment comma action
resource // comment resource
)
//comment 5
//comment 6
;
"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
let policy = parse_policy(
r#" /* multi-line
comment */
permit(principal, action, resource)
when{
one.two
};
"#,
);
assert!(policy.is_err());
let expr = parse_expr(
r#"
1 /* multi-line
comment */d
"#,
);
assert!(expr.is_err());
}
#[test]
fn no_comments_policy() {
let policy_text = r#"
permit(
principal
==
User::"alice"
,
action
,
resource
)
;
"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn no_comments_policy2() {
let policy_text = r#"permit (
principal == IAM::Principal::"arn:aws:iam::12345678901:user/Dave",
action == S3::Action::"GetAccountPublicAccessBlock",
resource == Account::"12345678901"
);"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn no_comments_policy4() {
let policy_text = r#"
permit(principal,action,resource,context)
when {
context.contains(3,"four",five(6,7))
};"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn no_comments_policy5() {
let policy_text = r#"
permit (
principal,
action,
resource == Album::{uid: "772358b3-de11-42dc-8681-f0a32e34aab8",
displayName: "vacation_photos"}
);"#;
let policy = parse_policy(policy_text);
assert!(policy.is_ok());
}
#[test]
fn policies1() {
let policy = parse_policy(
r#"
permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
"#,
);
assert!(policy.is_ok());
}
#[test]
fn policies2() {
let result = parse_policies(
r#"
permit(
principal in Group::"jane_friends", // Policy c1
action in [PhotoOp::"view", PhotoOp::"comment"],
resource in Album::"jane_trips",
context:Group
);
"#,
);
assert!(result.is_ok());
}
#[test]
fn policies3() {
assert!(parse_policies(
r#"
forbid(principal, action, resource) // Policy c2
when { "private" in resource.tags } // resource.tags is a set of strings
unless { resource in user.account };
"#
)
.expect("parse fail")
.node
.expect("no data")
.0
.iter()
.all(|p| p.node.is_some()));
}
#[test]
fn policies3p() {
assert!(parse_policies(
r#"
forbid(principality, action, resource) // Policy c2
when { "private" in resource.tags } // resource.tags is a set of strings
unless { resource in user.account };
"#
)
.expect("parse fail")
.node
.expect("no data")
.0
.iter()
.all(|p| p.node.is_some()));
}
#[test]
fn policies4() {
let result = parse_policies(
r#"
permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
permit(principal in Group::"jane_friends", // Policy c1
action in [PhotoOp::"view", PhotoOp::"comment"],
resource in Album::"jane_trips");
forbid(principal, action, resource) // Policy c2
when { "private" in resource.tags } // resource.tags is a set of strings
unless { resource in user.account };
"#,
)
.expect("parse fail")
.node
.expect("no data");
assert!(result.0.iter().all(|p| p.node.is_some()));
}
#[test]
fn policies5() {
assert!(parse_policies(
r#"
permit (
principal == User::"alice",
action in PhotoflashRole::"viewer",
resource in Account::"jane"
)
advice {
"{\"type\":\"PhotoFilterInstruction\", \"anonymize\":true}"
};
"#
)
.expect("parse fail")
.node
.expect("no data")
.0
.into_iter()
.all(|p| p.node.is_some()));
}
#[test]
fn policies6() {
let src = r#"
// use a number to error
3(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
"#;
let policies = POLICIES_PARSER
.parse(&mut Vec::new(), &Arc::from(src), src)
.expect("parser error")
.node
.expect("no data");
let success = policies
.0
.into_iter()
.filter_map(|p| p.node)
.collect::<Vec<_>>();
assert!(success.len() == 2);
}
#[test]
fn policy_annotations() {
let policies = parse_policies(
r#"
@anno("good annotation") permit (principal, action, resource);
@anno1("good")@anno2("annotation") permit (principal, action, resource);
@long6wordphraseisident007("good annotation") permit (principal, action, resource);
@ spacy ( " good annotation " ) permit (principal, action, resource);
"#,
)
.expect("parse fail")
.node
.expect("no data");
let success = policies
.0
.into_iter()
.filter_map(|p| p.node)
.collect::<Vec<_>>();
assert!(success.len() == 4);
let _policy = parse_policy(
r#"
@bad-annotation("bad") permit (principal, action, resource);
"#,
)
.expect_err("should fail on dash");
let _policy = parse_policy(
r#"
@bad_annotation("bad","annotation") permit (principal, action, resource);
"#,
)
.expect_err("should fail on list");
let _policy = parse_policy(
r#"
@bad_annotation(bad_annotation) permit (principal, action, resource);
"#,
)
.expect_err("should fail without string");
let _policy = parse_policy(
r#"
permit (@comment("your name here") principal, action, resource);
"#,
)
.expect_err("should fail with poor placement");
}
#[test]
fn parse_idempotent() {
let many_policies =
std::fs::read_to_string("src/parser/testfiles/policies.cedar").expect("missing file");
let cst1 = parse_policies(&many_policies)
.expect("parse fail")
.node
.expect("no data");
let revert = format!("{}", cst1);
let cst2 = parse_policies(&revert)
.expect("parse fail")
.node
.expect("no data");
assert!(cst1 == cst2);
}
}