Skip to content

Commit

Permalink
Support identifiers as context decls in human-readable schemas (#734)
Browse files Browse the repository at this point in the history
Signed-off-by: Shaobo He <shaobohe@amazon.com>
  • Loading branch information
shaobo-he-aws authored Mar 15, 2024
1 parent 6b0f170 commit d6d5e98
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 13 deletions.
4 changes: 2 additions & 2 deletions cedar-policy-validator/src/human_schema/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use cedar_policy_core::{
ast::Id,
parser::{Loc, Node},
};
use itertools::Itertools;
use itertools::{Either, Itertools};
use nonempty::NonEmpty;
use smol_str::SmolStr;
// We don't need this import on macOS but CI fails without it
Expand Down Expand Up @@ -311,7 +311,7 @@ pub enum AppDecl {
/// Constraints on the `principal`` or `resource``
PR(PRAppDecl),
/// Constraints on the `context`
Context(Vec<Node<AttrDecl>>),
Context(Either<Path, Vec<Node<AttrDecl>>>),
}

/// An action declaration
Expand Down
20 changes: 16 additions & 4 deletions cedar-policy-validator/src/human_schema/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Entity: Node<Declaration> = {
=> Node::with_source_loc(Declaration::Entity(EntityDecl { names: ets, member_of_types: ps.unwrap_or_default(), attrs: ds.map(|ds| ds.unwrap_or_default()).unwrap_or_default()}), Loc::new(l..r, Arc::clone(src))),
}

// Action := 'action' Names ['in' QualNameOrNames)]
// Action := 'action' Names ['in' QualNameOrNames]
Action: Node<Declaration> = {
<l:@L> ACTION <ns:Names> <ps:(IN <QualNameOrQualNames>)?> <ads:(APPLIESTO "{" <AppDecls> "}")?> <attrs:(ATTRIBUTES "{" "}")?>";" <r:@R>
=> Node::with_source_loc(Declaration::Action(ActionDecl { names: ns, parents: ps, app_decls: ads}), Loc::new(l..r, Arc::clone(src))),
Expand All @@ -108,7 +108,7 @@ TypeDecl: Node<Declaration> = {
}

// AppDecls := ('principal' | 'resource') ':' EntOrTyps [',' | ',' AppDecls]
// | 'context' ':' RecType [',' | ',' AppDecls]
// | 'context' ':' (Path | RecType) [',' | ',' AppDecls]
AppDecls: Node<NonEmpty<Node<AppDecl>>> = {
<l:@L> <pr: PrincipalOrResource> ":" <ets:EntTypes> ","? <r:@R>
=>?
Expand All @@ -128,15 +128,27 @@ AppDecls: Node<NonEmpty<Node<AppDecl>>> = {
ds.insert(0, Node::with_source_loc(AppDecl::PR(PRAppDecl { kind:pr, entity_tys: ets}), Loc::new(l..r, Arc::clone(src))));
Node::with_source_loc(ds, Loc::new(l..r, Arc::clone(src)))
}),
<l:@L> CONTEXT ":" <p:Path> ","? <r:@R>
=> Node::with_source_loc(
nonempty![Node::with_source_loc(AppDecl::Context(Either::Left(p)), Loc::new(l..r, Arc::clone(src)))],
Loc::new(l..r, Arc::clone(src))),
<l:@L> CONTEXT ":" <p:Path> "," <r:@R> <mut ds: AppDecls>
=> {
let (mut ds, _) = ds.into_inner();
ds.insert(0, Node::with_source_loc(AppDecl::Context(Either::Left(p)), Loc::new(l..r, Arc::clone(src))));
Node::with_source_loc(
ds,
Loc::new(l..r, Arc::clone(src)))
},
<l:@L> CONTEXT ":" "{" <attrs:AttrDecls?> "}" ","? <r:@R>
=>
Node::with_source_loc(
nonempty![Node::with_source_loc(AppDecl::Context(attrs.unwrap_or_default()), Loc::new(l..r, Arc::clone(src)))],
nonempty![Node::with_source_loc(AppDecl::Context(Either::Right(attrs.unwrap_or_default())), Loc::new(l..r, Arc::clone(src)))],
Loc::new(l..r, Arc::clone(src))),
<l:@L> CONTEXT ":" "{" <attrs:AttrDecls?> "}" "," <r:@R> <mut ds: AppDecls>
=> {
let (mut ds, _) = ds.into_inner();
ds.insert(0, Node::with_source_loc(AppDecl::Context(attrs.unwrap_or_default()), Loc::new(l..r, Arc::clone(src))));
ds.insert(0, Node::with_source_loc(AppDecl::Context(Either::Right(attrs.unwrap_or_default())), Loc::new(l..r, Arc::clone(src))));
Node::with_source_loc(
ds,
Loc::new(l..r, Arc::clone(src)))
Expand Down
57 changes: 57 additions & 0 deletions cedar-policy-validator/src/human_schema/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,63 @@ mod demo_tests {
assert!(as_src.contains(expected), "src was:\n`{as_src}`");
}

#[test]
fn context_is_common_type() {
assert!(SchemaFragment::from_str_natural(
r#"
type empty = {};
action "Foo" appliesTo {
context: empty,
};
"#
)
.is_ok());
assert!(SchemaFragment::from_str_natural(
r#"
type flag = { value: __cedar::Bool };
action "Foo" appliesTo {
context: flag
};
"#
)
.is_ok());
assert!(SchemaFragment::from_str_natural(
r#"
namespace Bar { type empty = {}; }
action "Foo" appliesTo {
context: Bar::empty
};
"#
)
.is_ok());
assert!(SchemaFragment::from_str_natural(
r#"
namespace Bar { type flag = { value: Bool }; }
namespace Baz {action "Foo" appliesTo {
context: Bar::flag
};}
"#
)
.is_ok());
assert!(SchemaFragment::from_str_natural(
r#"
type authcontext = {
ip: ipaddr,
is_authenticated: Bool,
timestamp: Long
};
entity Ticket {
who: String,
operation: Long,
request: authcontext
};
action view appliesTo { context: authcontext };
action upload appliesTo { context: authcontext };
"#
)
.is_ok());
}

#[test]
fn print_actions() {
let namespace = NamespaceDefinition {
Expand Down
30 changes: 23 additions & 7 deletions cedar-policy-validator/src/human_schema/to_json_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ impl<'a> ConversionContext<'a> {
let context = contexts
.into_iter()
.at_most_one()
.map_err(|e| convert_context_error(e, loc.clone()))?
.map(|attrs| self.convert_attr_decls(attrs))
.map_err(|_| convert_context_error(loc.clone()))?
.map(|attrs| self.convert_context_decl(attrs))
.transpose()?
.unwrap_or_default();

Expand Down Expand Up @@ -291,6 +291,25 @@ impl<'a> ConversionContext<'a> {
)))
}

/// Create a context decl
fn convert_context_decl(
&self,
decl: Either<Path, Vec<Node<AttrDecl>>>,
) -> Result<AttributesOrContext, ToJsonSchemaErrors> {
Ok(AttributesOrContext(match decl {
Either::Left(p) => SchemaType::TypeDef {
type_name: p.to_smolstr(),
},
Either::Right(attrs) => SchemaType::Type(SchemaTypeVariant::Record {
attributes: collect_all_errors(
attrs.into_iter().map(|attr| self.convert_attr_decl(attr)),
)?
.collect(),
additional_attributes: false,
}),
}))
}

/// Convert an attribute type from an AttrDecl
fn convert_attr_decl(
&self,
Expand Down Expand Up @@ -402,18 +421,15 @@ fn convert_pr_error(
}

/// Wrap [`ExactlyOneError`] for the purpose of converting ContextDecls
fn convert_context_error(
_e: ExactlyOneError<std::vec::IntoIter<Vec<Node<AttrDecl>>>>,
loc: Loc,
) -> ToJsonSchemaError {
fn convert_context_error(loc: Loc) -> ToJsonSchemaError {
ToJsonSchemaError::DuplicateContext {
start: loc.clone(),
end: loc,
}
}

/// Partition on whether or not this [`AppDecl`] is defining a context
fn is_context_decl(n: Node<AppDecl>) -> Either<Vec<Node<AttrDecl>>, PRAppDecl> {
fn is_context_decl(n: Node<AppDecl>) -> Either<Either<Path, Vec<Node<AttrDecl>>>, PRAppDecl> {
match n.node {
AppDecl::PR(decl) => Either::Right(decl),
AppDecl::Context(attrs) => Either::Left(attrs),
Expand Down

0 comments on commit d6d5e98

Please sign in to comment.