Skip to content

Commit

Permalink
Enable generation of arbitrary annotations and test their equivalence (
Browse files Browse the repository at this point in the history
…#501)

Signed-off-by: Shaobo He <[email protected]>
Co-authored-by: Adrian Palacios <[email protected]>
  • Loading branch information
shaobo-he-aws and adpaco-aws authored Dec 19, 2024
1 parent 1036c96 commit 29382d6
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 28 deletions.
102 changes: 102 additions & 0 deletions cedar-drt/fuzz/src/schemas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,40 @@ impl<'a, T: Equiv> Equiv for &'a T {
}
}

impl Equiv for cedar_policy_core::est::Annotations {
fn equiv(lhs: &Self, rhs: &Self) -> Result<(), String> {
Equiv::equiv(&lhs.0, &rhs.0)
}
}

impl Equiv for Option<cedar_policy_core::ast::Annotation> {
fn equiv(lhs: &Self, rhs: &Self) -> Result<(), String> {
match (lhs, rhs) {
(Some(a), Some(b)) => {
if a == b {
return Ok(());
}
}
(Some(a), None) | (None, Some(a)) => {
if a.val.is_empty() {
return Ok(());
}
}
(None, None) => return Ok(()),
};
Err(format!("{lhs:?} and {rhs:?} are not equivalent"))
}
}

impl<N: Clone + PartialEq + Debug + Display + TypeName + Ord> Equiv
for json_schema::NamespaceDefinition<N>
{
fn equiv(
lhs: &json_schema::NamespaceDefinition<N>,
rhs: &json_schema::NamespaceDefinition<N>,
) -> Result<(), String> {
Equiv::equiv(&lhs.annotations, &rhs.annotations)
.map_err(|e| format!("mismatch in namespace annotations: {e}"))?;
Equiv::equiv(&lhs.entity_types, &rhs.entity_types)
.map_err(|e| format!("mismatch in entity type declarations: {e}"))?;
Equiv::equiv(&lhs.common_types, &rhs.common_types)
Expand Down Expand Up @@ -191,12 +218,16 @@ impl<K: Eq + Ord + Display, V: Equiv> Equiv for BTreeMap<K, V> {

impl<N: Clone + PartialEq + Debug + Display + TypeName + Ord> Equiv for json_schema::CommonType<N> {
fn equiv(lhs: &Self, rhs: &Self) -> Result<(), String> {
Equiv::equiv(&lhs.annotations, &rhs.annotations)
.map_err(|e| format!("mismatch in common type annotations: {e}"))?;
Equiv::equiv(&lhs.ty, &rhs.ty)
}
}

impl<N: Clone + PartialEq + Debug + Display + TypeName + Ord> Equiv for json_schema::EntityType<N> {
fn equiv(lhs: &Self, rhs: &Self) -> Result<(), String> {
Equiv::equiv(&lhs.annotations, &rhs.annotations)
.map_err(|e| format!("mismatch in entity annotations: {e}"))?;
Equiv::equiv(
&lhs.member_of_types.iter().collect::<BTreeSet<_>>(),
&rhs.member_of_types.iter().collect::<BTreeSet<_>>(),
Expand Down Expand Up @@ -226,6 +257,8 @@ impl Equiv for cedar_policy_validator::ValidatorEntityType {

impl<N: Clone + PartialEq + TypeName + Debug + Display> Equiv for json_schema::TypeOfAttribute<N> {
fn equiv(lhs: &Self, rhs: &Self) -> Result<(), String> {
Equiv::equiv(&lhs.annotations, &rhs.annotations)
.map_err(|e| format!("mismatch in type of attribute annotations: {e}"))?;
if lhs.required != rhs.required {
return Err("attributes differ in required flag".into());
}
Expand Down Expand Up @@ -434,6 +467,8 @@ impl TypeName for InternalName {

impl<N: PartialEq + Debug + Display + Clone + TypeName + Ord> Equiv for json_schema::ActionType<N> {
fn equiv(lhs: &Self, rhs: &Self) -> Result<(), String> {
Equiv::equiv(&lhs.annotations, &rhs.annotations)
.map_err(|e| format!("mismatch in action annotations: {e}"))?;
if &lhs.attributes != &rhs.attributes
&& !(lhs.attributes.as_ref().is_none_or(HashMap::is_empty)
&& rhs.attributes.as_ref().is_none_or(HashMap::is_empty))
Expand Down Expand Up @@ -517,3 +552,70 @@ impl Equiv for cedar_policy_validator::ValidatorSchema {
Ok(())
}
}

#[cfg(test)]
mod tests {
use cedar_drt::est::Annotations;

use crate::schemas::Equiv;

#[test]
fn annotations() {
// positive cases
let pairs: [(Annotations, Annotations); 5] = [
// value being null is equivalent to an empty string
(
serde_json::from_value(serde_json::json!({"a": null})).unwrap(),
serde_json::from_value(serde_json::json!({"a": ""})).unwrap(),
),
(
serde_json::from_value(serde_json::json!({"a": ""})).unwrap(),
serde_json::from_value(serde_json::json!({"a": null})).unwrap(),
),
// both values being null is also equivalent
(
serde_json::from_value(serde_json::json!({"a": null})).unwrap(),
serde_json::from_value(serde_json::json!({"a": null})).unwrap(),
),
// otherwise compare non-null values
(
serde_json::from_value(serde_json::json!({"a": "🥨"})).unwrap(),
serde_json::from_value(serde_json::json!({"a": "🥨"})).unwrap(),
),
(
serde_json::from_value(serde_json::json!({"a": "🥨", "b": "🥯🍩"})).unwrap(),
serde_json::from_value(serde_json::json!({"b": "🥯🍩", "a": "🥨"})).unwrap(),
),
];
pairs
.iter()
.for_each(|(a, b)| assert!(Equiv::equiv(a, b).is_ok()));

// negative cases
let pairs: [(Annotations, Annotations); 5] = [
(
serde_json::from_value(serde_json::json!({"a": null})).unwrap(),
serde_json::from_value(serde_json::json!({"a": "🍪"})).unwrap(),
),
(
serde_json::from_value(serde_json::json!({"a": ""})).unwrap(),
serde_json::from_value(serde_json::json!({"b": null})).unwrap(),
),
(
serde_json::from_value(serde_json::json!({"a": null})).unwrap(),
serde_json::from_value(serde_json::json!({"b": null})).unwrap(),
),
(
serde_json::from_value(serde_json::json!({"a": "🥨"})).unwrap(),
serde_json::from_value(serde_json::json!({"a": "🍪"})).unwrap(),
),
(
serde_json::from_value(serde_json::json!({"a": "🥨", "b": "🥯🍪"})).unwrap(),
serde_json::from_value(serde_json::json!({"b": "🥯🍩", "a": "🥨"})).unwrap(),
),
];
pairs
.iter()
.for_each(|(a, b)| assert!(Equiv::equiv(a, b).is_err()));
}
}
21 changes: 6 additions & 15 deletions cedar-policy-generators/src/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@ use crate::hierarchy::Hierarchy;
use crate::size_hint_utils::size_hint_for_ratio;
use arbitrary::{Arbitrary, Unstructured};
use cedar_policy_core::ast::{
Annotation, Annotations, AnyId, Effect, EntityUID, Expr, Policy, PolicyID, PolicySet,
StaticPolicy, Template,
Effect, EntityUID, Expr, Policy, PolicyID, PolicySet, StaticPolicy, Template,
};
use cedar_policy_core::{ast, est};
use serde::Serialize;
use smol_str::SmolStr;
use std::fmt::Display;
use std::sync::Arc;

Expand All @@ -39,7 +37,7 @@ use std::sync::Arc;
#[serde(into = "est::Policy")]
pub struct GeneratedPolicy {
id: PolicyID,
annotations: HashMap<AnyId, SmolStr>,
annotations: cedar_policy_core::est::Annotations,
effect: Effect,
principal_constraint: PrincipalOrResourceConstraint,
action_constraint: ActionConstraint,
Expand All @@ -59,7 +57,7 @@ impl GeneratedPolicy {
/// Create a new `GeneratedPolicy` with these fields
pub fn new(
id: PolicyID,
annotations: impl IntoIterator<Item = (AnyId, SmolStr)>,
annotations: cedar_policy_core::est::Annotations,
effect: Effect,
principal_constraint: PrincipalOrResourceConstraint,
action_constraint: ActionConstraint,
Expand All @@ -68,7 +66,7 @@ impl GeneratedPolicy {
) -> Self {
Self {
id,
annotations: annotations.into_iter().collect(),
annotations,
effect,
principal_constraint,
action_constraint,
Expand Down Expand Up @@ -141,19 +139,12 @@ impl GeneratedPolicy {
}
}

fn convert_annotations(annotations: HashMap<AnyId, SmolStr>) -> Annotations {
annotations
.into_iter()
.map(|(k, v)| (k, Annotation { val: v, loc: None }))
.collect()
}

impl From<GeneratedPolicy> for StaticPolicy {
fn from(gen: GeneratedPolicy) -> StaticPolicy {
StaticPolicy::new(
gen.id,
None,
convert_annotations(gen.annotations),
gen.annotations.into(),
gen.effect,
gen.principal_constraint.into(),
gen.action_constraint.into(),
Expand All @@ -169,7 +160,7 @@ impl From<GeneratedPolicy> for Template {
Template::new(
gen.id,
None,
convert_annotations(gen.annotations),
gen.annotations.into(),
gen.effect,
gen.principal_constraint.into(),
gen.action_constraint.into(),
Expand Down
25 changes: 12 additions & 13 deletions cedar-policy-generators/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ use crate::size_hint_utils::{size_hint_for_choose, size_hint_for_range, size_hin
use crate::{accum, gen, gen_inner, uniform};
use arbitrary::{self, Arbitrary, MaxRecursionReached, Unstructured};
use cedar_policy_core::ast::{self, Effect, PolicyID, UnreservedId};
use cedar_policy_core::est::Annotations;
use cedar_policy_core::extensions::Extensions;
use cedar_policy_validator::json_schema::{CommonType, CommonTypeId};
use cedar_policy_validator::{
Expand Down Expand Up @@ -153,15 +152,16 @@ fn arbitrary_typeofattribute_with_bounded_depth<N: From<ast::Name>>(
Ok(json_schema::TypeOfAttribute {
ty: arbitrary_schematype_with_bounded_depth::<N>(settings, entity_types, max_depth, u)?,
required: u.arbitrary()?,
annotations: Annotations::new(),
annotations: u.arbitrary()?,
})
}
/// size hint for arbitrary_typeofattribute_with_bounded_depth
fn arbitrary_typeofattribute_size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::and(
arbitrary::size_hint::and_all(&[
arbitrary_schematype_size_hint(depth),
<bool as Arbitrary>::size_hint(depth),
)
<cedar_policy_core::est::Annotations as Arbitrary>::size_hint(depth),
])
}

/// internal helper function, an alternative to the `Arbitrary` impl for
Expand Down Expand Up @@ -710,15 +710,15 @@ impl Schema {
common_types: common_types
.into_iter()
.map(|(id, ty)| {
(
Ok((
id,
CommonType {
ty,
annotations: Annotations::new(),
annotations: u.arbitrary()?,
},
)
))
})
.collect(),
.collect::<Result<BTreeMap<_, _>>>()?,
entity_types: entity_types.into(),
actions: actions.into(),
annotations: self.schema.annotations.clone(),
Expand Down Expand Up @@ -931,7 +931,7 @@ impl Schema {
u
)?)
),
annotations: Annotations::new(),
annotations: u.arbitrary()?,
},
))
})
Expand Down Expand Up @@ -1034,7 +1034,7 @@ impl Schema {
},
//TODO: Fuzz arbitrary attribute names and values.
attributes: None,
annotations: Annotations::new(),
annotations: u.arbitrary()?,
},
))
})
Expand Down Expand Up @@ -1064,7 +1064,7 @@ impl Schema {
common_types: BTreeMap::new().into(),
entity_types: entity_types.into_iter().collect(),
actions: actions.into_iter().collect(),
annotations: Annotations::new(),
annotations: u.arbitrary()?,
};
let attrsorcontexts /* : impl Iterator<Item = &AttributesOrContext> */ = nsdef.entity_types.values().map(|et| attrs_from_attrs_or_context(&nsdef, &et.shape))
.chain(nsdef.actions.iter().filter_map(|(_, action)| action.applies_to.as_ref()).map(|a| attrs_from_attrs_or_context(&nsdef, &a.context)));
Expand Down Expand Up @@ -1361,7 +1361,6 @@ impl Schema {
u: &mut Unstructured<'_>,
) -> Result<ABACPolicy> {
let id = u.arbitrary()?;
let annotations: HashMap<ast::AnyId, SmolStr> = u.arbitrary()?;
let effect = u.arbitrary()?;
let principal_constraint = self.arbitrary_principal_constraint(hierarchy, u)?;
let action_constraint = self.arbitrary_action_constraint(u, Some(3))?;
Expand All @@ -1386,7 +1385,7 @@ impl Schema {
}
Ok(ABACPolicy(GeneratedPolicy::new(
id,
annotations,
u.arbitrary()?,
effect,
principal_constraint,
action_constraint,
Expand Down

0 comments on commit 29382d6

Please sign in to comment.