From c5a7f040d953b6afdd2becb30a63eb22d97fd52c Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Mon, 23 Jun 2025 16:34:35 -0400 Subject: [PATCH 01/23] updated slot types and included id within entity reference Signed-off-by: Alan Wang --- cedar-policy-core/src/ast/expr.rs | 4 +- cedar-policy-core/src/ast/expr_visitor.rs | 2 +- cedar-policy-core/src/ast/name.rs | 26 ++++- cedar-policy-core/src/ast/policy.rs | 95 ++++++++++-------- cedar-policy-core/src/ast/policy_set.rs | 8 +- cedar-policy-core/src/est/policy_set.rs | 7 +- .../src/est/scope_constraints.rs | 97 +++++++++++-------- cedar-policy-core/src/evaluator.rs | 4 +- cedar-policy-core/src/parser/cst_to_ast.rs | 11 ++- .../src/parser/cst_to_ast/to_ref_or_refs.rs | 13 +-- cedar-policy-core/src/validator/rbac.rs | 8 +- cedar-policy-core/src/validator/typecheck.rs | 2 +- cedar-policy/src/api.rs | 22 ++--- 13 files changed, 182 insertions(+), 117 deletions(-) diff --git a/cedar-policy-core/src/ast/expr.rs b/cedar-policy-core/src/ast/expr.rs index ee3c567ed8..e81c763c2b 100644 --- a/cedar-policy-core/src/ast/expr.rs +++ b/cedar-policy-core/src/ast/expr.rs @@ -293,7 +293,7 @@ impl Expr { self.subexpressions() .filter_map(|exp| match &exp.expr_kind { ExprKind::Slot(slotid) => Some(Slot { - id: *slotid, + id: slotid.clone(), loc: exp.source_loc().into_maybe_loc(), }), _ => None, @@ -1842,7 +1842,7 @@ mod test { let e = Expr::slot(SlotId::principal()); let p = SlotId::principal(); let r = SlotId::resource(); - let set: HashSet = HashSet::from_iter([p]); + let set: HashSet = HashSet::from_iter([p.clone()]); assert_eq!(set, e.slots().map(|slot| slot.id).collect::>()); let e = Expr::or( Expr::slot(SlotId::principal()), diff --git a/cedar-policy-core/src/ast/expr_visitor.rs b/cedar-policy-core/src/ast/expr_visitor.rs index ee6acd3f26..c5adb9b353 100644 --- a/cedar-policy-core/src/ast/expr_visitor.rs +++ b/cedar-policy-core/src/ast/expr_visitor.rs @@ -55,7 +55,7 @@ pub trait ExprVisitor { match expr.expr_kind() { ExprKind::Lit(lit) => self.visit_literal(lit, loc), ExprKind::Var(var) => self.visit_var(*var, loc), - ExprKind::Slot(slot) => self.visit_slot(*slot, loc), + ExprKind::Slot(slot) => self.visit_slot(slot.clone(), loc), ExprKind::Unknown(unknown) => self.visit_unknown(unknown, loc), ExprKind::If { test_expr, diff --git a/cedar-policy-core/src/ast/name.rs b/cedar-policy-core/src/ast/name.rs index 20f2ae7cf0..735b137f37 100644 --- a/cedar-policy-core/src/ast/name.rs +++ b/cedar-policy-core/src/ast/name.rs @@ -283,7 +283,7 @@ impl<'de> Deserialize<'de> for InternalName { /// Clone is O(1). // This simply wraps a separate enum -- currently [`ValidSlotId`] -- in case we // want to generalize later -#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(transparent)] pub struct SlotId(pub(crate) ValidSlotId); @@ -298,6 +298,11 @@ impl SlotId { Self(ValidSlotId::Resource) } + /// Create a `generalized slot` + pub fn generalized_slot(id: Id) -> Self { + Self(ValidSlotId::GeneralizedSlot(id)) + } + /// Check if a slot represents a principal pub fn is_principal(&self) -> bool { matches!(self, Self(ValidSlotId::Principal)) @@ -307,6 +312,19 @@ impl SlotId { pub fn is_resource(&self) -> bool { matches!(self, Self(ValidSlotId::Resource)) } + + /// Check if a slot represents a generalized slot + pub fn is_generalized_slot(&self) -> bool { + matches!(self, Self(ValidSlotId::GeneralizedSlot(_))) + } + + /// Returns the id if the slot is a `generalized slot` + pub fn extract_id_out_of_generalized_slot(&self) -> Option { + match self { + Self(ValidSlotId::GeneralizedSlot(id)) => Some(id.clone()), + _ => None, + } + } } impl From for SlotId { @@ -324,13 +342,14 @@ impl std::fmt::Display for SlotId { } } -/// Two possible variants for Slots -#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +/// Three possible variants for Slots +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub(crate) enum ValidSlotId { #[serde(rename = "?principal")] Principal, #[serde(rename = "?resource")] Resource, + GeneralizedSlot(Id), } impl std::fmt::Display for ValidSlotId { @@ -338,6 +357,7 @@ impl std::fmt::Display for ValidSlotId { let s = match self { ValidSlotId::Principal => "principal", ValidSlotId::Resource => "resource", + ValidSlotId::GeneralizedSlot(id) => &id.to_smolstr(), }; write!(f, "?{s}") } diff --git a/cedar-policy-core/src/ast/policy.rs b/cedar-policy-core/src/ast/policy.rs index cce906c09b..e8744b943b 100644 --- a/cedar-policy-core/src/ast/policy.rs +++ b/cedar-policy-core/src/ast/policy.rs @@ -293,8 +293,8 @@ impl Template { Ok(()) } else { Err(LinkingError::from_unbound_and_extras( - unbound.into_iter().map(|slot| slot.id), - extra.into_iter().copied(), + unbound.into_iter().map(|slot| slot.id.clone()), + extra.into_iter().cloned(), )) } } @@ -1331,9 +1331,9 @@ impl PrincipalConstraint { } /// Constrained to be equal to a slot - pub fn is_eq_slot() -> Self { + pub fn is_eq_slot(id: Option) -> Self { Self { - constraint: PrincipalOrResourceConstraint::is_eq_slot(), + constraint: PrincipalOrResourceConstraint::is_eq_slot(id), } } @@ -1345,16 +1345,16 @@ impl PrincipalConstraint { } /// Hierarchical constraint to Slot - pub fn is_in_slot() -> Self { + pub fn is_in_slot(id: Option) -> Self { Self { - constraint: PrincipalOrResourceConstraint::is_in_slot(), + constraint: PrincipalOrResourceConstraint::is_in_slot(id), } } /// Type constraint additionally constrained to be in a slot. - pub fn is_entity_type_in_slot(entity_type: Arc) -> Self { + pub fn is_entity_type_in_slot(entity_type: Arc, id: Option) -> Self { Self { - constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type), + constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type, id), } } @@ -1375,10 +1375,10 @@ impl PrincipalConstraint { /// Fill in the Slot, if any, with the given EUID pub fn with_filled_slot(self, euid: Arc) -> Self { match self.constraint { - PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self { + PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_, _)) => Self { constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)), }, - PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self { + PrincipalOrResourceConstraint::In(EntityReference::Slot(_, _)) => Self { constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)), }, _ => self, @@ -1438,16 +1438,16 @@ impl ResourceConstraint { } /// Constrained to equal a slot. - pub fn is_eq_slot() -> Self { + pub fn is_eq_slot(id: Option) -> Self { Self { - constraint: PrincipalOrResourceConstraint::is_eq_slot(), + constraint: PrincipalOrResourceConstraint::is_eq_slot(id), } } /// Constrained to be in a slot - pub fn is_in_slot() -> Self { + pub fn is_in_slot(id: Option) -> Self { Self { - constraint: PrincipalOrResourceConstraint::is_in_slot(), + constraint: PrincipalOrResourceConstraint::is_in_slot(id), } } @@ -1459,9 +1459,9 @@ impl ResourceConstraint { } /// Type constraint additionally constrained to be in a slot. - pub fn is_entity_type_in_slot(entity_type: Arc) -> Self { + pub fn is_entity_type_in_slot(entity_type: Arc, id: Option) -> Self { Self { - constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type), + constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type, id), } } @@ -1482,10 +1482,10 @@ impl ResourceConstraint { /// Fill in the Slot, if any, with the given EUID pub fn with_filled_slot(self, euid: Arc) -> Self { match self.constraint { - PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self { + PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_, _)) => Self { constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)), }, - PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self { + PrincipalOrResourceConstraint::In(EntityReference::Slot(_, _)) => Self { constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)), }, _ => self, @@ -1511,6 +1511,7 @@ pub enum EntityReference { EUID(Arc), /// Template Slot Slot( + Option, // If a slot stores None then it is a principal/resource, otherwise it is a generalized slot #[educe(PartialEq(ignore))] #[educe(PartialOrd(ignore))] #[educe(Hash(ignore))] @@ -1532,7 +1533,13 @@ impl EntityReference { pub fn into_expr(&self, slot: SlotId) -> Expr { match self { EntityReference::EUID(euid) => Expr::val(euid.clone()), - EntityReference::Slot(loc) => Expr::slot(slot).with_maybe_source_loc(loc.clone()), + EntityReference::Slot(id, loc) => { + let slot = match id { + Some(id) => SlotId::generalized_slot(id.clone()), + None => slot, + }; + Expr::slot(slot).with_maybe_source_loc(loc.clone()) + } } } } @@ -1628,13 +1635,13 @@ impl PrincipalOrResourceConstraint { } /// Constrained to equal a slot - pub fn is_eq_slot() -> Self { - PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)) + pub fn is_eq_slot(id: Option) -> Self { + PrincipalOrResourceConstraint::Eq(EntityReference::Slot(id, None)) } /// Constrained to be in a slot - pub fn is_in_slot() -> Self { - PrincipalOrResourceConstraint::In(EntityReference::Slot(None)) + pub fn is_in_slot(id: Option) -> Self { + PrincipalOrResourceConstraint::In(EntityReference::Slot(id, None)) } /// Hierarchical constraint. @@ -1643,8 +1650,8 @@ impl PrincipalOrResourceConstraint { } /// Type constraint additionally constrained to be in a slot. - pub fn is_entity_type_in_slot(entity_type: Arc) -> Self { - PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(None)) + pub fn is_entity_type_in_slot(entity_type: Arc, id: Option) -> Self { + PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(id, None)) } /// Type constraint with a hierarchical constraint. @@ -1705,11 +1712,11 @@ impl PrincipalOrResourceConstraint { match self { PrincipalOrResourceConstraint::Any => None, PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => Some(euid), - PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => None, + PrincipalOrResourceConstraint::In(EntityReference::Slot(_, _)) => None, PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => Some(euid), - PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => None, + PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_, _)) => None, PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(euid)) => Some(euid), - PrincipalOrResourceConstraint::IsIn(_, EntityReference::Slot(_)) => None, + PrincipalOrResourceConstraint::IsIn(_, EntityReference::Slot(_, _)) => None, PrincipalOrResourceConstraint::Is(_) => None, } } @@ -1966,9 +1973,9 @@ pub(crate) mod test_generators { let v = vec![ PrincipalOrResourceConstraint::any(), PrincipalOrResourceConstraint::is_eq(euid.clone()), - PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)), + PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None, None)), PrincipalOrResourceConstraint::is_in(euid), - PrincipalOrResourceConstraint::In(EntityReference::Slot(None)), + PrincipalOrResourceConstraint::In(EntityReference::Slot(None, None)), ]; v.into_iter() @@ -2057,7 +2064,7 @@ mod test { let t = Arc::new(template); let env = t .slots() - .map(|slot| (slot.id, EntityUID::with_eid("eid"))) + .map(|slot| (slot.id.clone(), EntityUID::with_eid("eid"))) .collect(); let _ = Template::link(t, PolicyID::from_string("id"), env).expect("Linking failed"); } @@ -2110,7 +2117,7 @@ mod test { None, Annotations::new(), Effect::Forbid, - PrincipalConstraint::is_eq_slot(), + PrincipalConstraint::is_eq_slot(None), ActionConstraint::Any, ResourceConstraint::any(), Expr::val(true), @@ -2132,9 +2139,9 @@ mod test { None, Annotations::new(), Effect::Forbid, - PrincipalConstraint::is_eq_slot(), + PrincipalConstraint::is_eq_slot(None), ActionConstraint::Any, - ResourceConstraint::is_in_slot(), + ResourceConstraint::is_in_slot(None), Expr::val(true), )); assert_matches!(Template::link(t.clone(), iid.clone(), HashMap::new()), Err(LinkingError::ArityError { unbound_values, extra_values }) => { @@ -2158,9 +2165,9 @@ mod test { None, Annotations::new(), Effect::Permit, - PrincipalConstraint::is_in_slot(), + PrincipalConstraint::is_in_slot(None), ActionConstraint::any(), - ResourceConstraint::is_eq_slot(), + ResourceConstraint::is_eq_slot(None), Expr::val(true), )); @@ -2231,7 +2238,7 @@ mod test { Some(&e) ); assert_eq!( - PrincipalOrResourceConstraint::In(EntityReference::Slot(None)).get_euid(), + PrincipalOrResourceConstraint::In(EntityReference::Slot(None, None)).get_euid(), None ); assert_eq!( @@ -2239,7 +2246,7 @@ mod test { Some(&e) ); assert_eq!( - PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)).get_euid(), + PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None, None)).get_euid(), None ); assert_eq!( @@ -2257,7 +2264,7 @@ mod test { assert_eq!( PrincipalOrResourceConstraint::IsIn( Arc::new("T".parse().unwrap()), - EntityReference::Slot(None) + EntityReference::Slot(None, None) ) .get_euid(), None @@ -2318,7 +2325,7 @@ mod test { #[test] fn euid_into_expr() { - let e = EntityReference::Slot(None); + let e = EntityReference::Slot(None, None); assert_eq!( e.into_expr(SlotId::principal()), Expr::slot(SlotId::principal()) @@ -2332,7 +2339,7 @@ mod test { #[test] fn por_constraint_display() { - let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)); + let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None, None)); let s = t.display(PrincipalOrResource::Principal); assert_eq!(s, "principal == ?principal"); let t = PrincipalOrResourceConstraint::Eq(EntityReference::euid(Arc::new( @@ -2340,6 +2347,12 @@ mod test { ))); let s = t.display(PrincipalOrResource::Principal); assert_eq!(s, "principal == test_entity_type::\"test\""); + let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot( + "generalized".parse().ok(), + None, + )); + let s = t.display(PrincipalOrResource::Principal); + assert_eq!(s, "principal == ?generalized"); } #[test] diff --git a/cedar-policy-core/src/ast/policy_set.rs b/cedar-policy-core/src/ast/policy_set.rs index c7d67b6a35..eaa5ad1603 100644 --- a/cedar-policy-core/src/ast/policy_set.rs +++ b/cedar-policy-core/src/ast/policy_set.rs @@ -952,9 +952,9 @@ mod test { None, Annotations::new(), Effect::Permit, - PrincipalConstraint::is_eq_slot(), + PrincipalConstraint::is_eq_slot(None), ActionConstraint::any(), - ResourceConstraint::is_in_slot(), + ResourceConstraint::is_in_slot(None), Expr::val(true), ); @@ -1017,7 +1017,7 @@ mod test { None, Annotations::new(), Effect::Forbid, - PrincipalConstraint::is_eq_slot(), + PrincipalConstraint::is_eq_slot(None), ActionConstraint::any(), ResourceConstraint::any(), Expr::val(true), @@ -1098,7 +1098,7 @@ mod test { None, Annotations::new(), Effect::Permit, - PrincipalConstraint::is_eq_slot(), + PrincipalConstraint::is_eq_slot(None), ActionConstraint::any(), ResourceConstraint::any(), Expr::val(true), diff --git a/cedar-policy-core/src/est/policy_set.rs b/cedar-policy-core/src/est/policy_set.rs index 899cd1a719..0d6910cc49 100644 --- a/cedar-policy-core/src/est/policy_set.rs +++ b/cedar-policy-core/src/est/policy_set.rs @@ -56,8 +56,11 @@ impl PolicySet { .filter_map(|link| { if &link.new_id == id { self.get_template(&link.template_id).and_then(|template| { - let unwrapped_est_vals: HashMap = - link.values.iter().map(|(k, v)| (*k, v.into())).collect(); + let unwrapped_est_vals: HashMap = link + .values + .iter() + .map(|(k, v)| (k.clone(), v.into())) + .collect(); template.link(&unwrapped_est_vals).ok() }) } else { diff --git a/cedar-policy-core/src/est/scope_constraints.rs b/cedar-policy-core/src/est/scope_constraints.rs index df4fb7fe15..821c01c6f0 100644 --- a/cedar-policy-core/src/est/scope_constraints.rs +++ b/cedar-policy-core/src/est/scope_constraints.rs @@ -15,8 +15,8 @@ */ use super::{FromJsonError, LinkingError}; -use crate::ast; use crate::ast::EntityUID; +use crate::ast::{self, SlotId}; use crate::entities::json::{ err::JsonDeserializationError, err::JsonDeserializationErrorContext, EntityUidJson, }; @@ -655,20 +655,24 @@ impl From for PrincipalConstraint { entity: EntityUidJson::ImplicitEntityEscape((&*e).into()), }) } - ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot(_)) => { - PrincipalConstraint::Eq(EqConstraint::Slot { - slot: ast::SlotId::principal(), - }) + ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot(id, _)) => { + let slot = match id { + Some(id) => SlotId::generalized_slot(id), + None => ast::SlotId::principal(), + }; + PrincipalConstraint::Eq(EqConstraint::Slot { slot }) } ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(e)) => { PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity: EntityUidJson::ImplicitEntityEscape((&*e).into()), }) } - ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot(_)) => { - PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { - slot: ast::SlotId::principal(), - }) + ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot(id, _)) => { + let slot = match id { + Some(id) => SlotId::generalized_slot(id), + None => ast::SlotId::principal(), + }; + PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) } ast::PrincipalOrResourceConstraint::IsIn(entity_type, euid) => { PrincipalConstraint::Is(PrincipalOrResourceIsConstraint { @@ -677,9 +681,13 @@ impl From for PrincipalConstraint { ast::EntityReference::EUID(e) => PrincipalOrResourceInConstraint::Entity { entity: EntityUidJson::ImplicitEntityEscape((&*e).into()), }, - ast::EntityReference::Slot(_) => PrincipalOrResourceInConstraint::Slot { - slot: ast::SlotId::principal(), - }, + ast::EntityReference::Slot(id, _) => { + let slot = match id { + Some(id) => SlotId::generalized_slot(id), + None => ast::SlotId::principal(), + }; + PrincipalOrResourceInConstraint::Slot { slot } + } }), }) } @@ -702,20 +710,24 @@ impl From for ResourceConstraint { entity: EntityUidJson::ImplicitEntityEscape((&*e).into()), }) } - ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot(_)) => { - ResourceConstraint::Eq(EqConstraint::Slot { - slot: ast::SlotId::resource(), - }) + ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot(id, _)) => { + let slot = match id { + Some(id) => SlotId::generalized_slot(id), + None => SlotId::resource(), + }; + ResourceConstraint::Eq(EqConstraint::Slot { slot }) } ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(e)) => { ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity: EntityUidJson::ImplicitEntityEscape((&*e).into()), }) } - ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot(_)) => { - ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { - slot: ast::SlotId::resource(), - }) + ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot(id, _)) => { + let slot = match id { + Some(id) => SlotId::generalized_slot(id), + None => SlotId::resource(), + }; + ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) } ast::PrincipalOrResourceConstraint::IsIn(entity_type, euid) => { ResourceConstraint::Is(PrincipalOrResourceIsConstraint { @@ -724,9 +736,14 @@ impl From for ResourceConstraint { ast::EntityReference::EUID(e) => PrincipalOrResourceInConstraint::Entity { entity: EntityUidJson::ImplicitEntityEscape((&*e).into()), }, - ast::EntityReference::Slot(_) => PrincipalOrResourceInConstraint::Slot { - slot: ast::SlotId::resource(), - }, + ast::EntityReference::Slot(id, _) => { + let slot = match id { + Some(id) => SlotId::generalized_slot(id), + None => SlotId::resource(), + }; + + PrincipalOrResourceInConstraint::Slot { slot } + } }), }) } @@ -753,9 +770,9 @@ impl TryFrom for ast::PrincipalOrResourceConstraint { ))), ), PrincipalConstraint::Eq(EqConstraint::Slot { slot }) => { - if slot == ast::SlotId::principal() { + if slot == ast::SlotId::principal() || slot.is_generalized_slot() { Ok(ast::PrincipalOrResourceConstraint::Eq( - ast::EntityReference::Slot(None), + ast::EntityReference::Slot(slot.extract_id_out_of_generalized_slot(), None), )) } else { Err(Self::Error::InvalidSlotName) @@ -767,9 +784,9 @@ impl TryFrom for ast::PrincipalOrResourceConstraint { ))), ), PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => { - if slot == ast::SlotId::principal() { + if slot == ast::SlotId::principal() || slot.is_generalized_slot() { Ok(ast::PrincipalOrResourceConstraint::In( - ast::EntityReference::Slot(None), + ast::EntityReference::Slot(slot.extract_id_out_of_generalized_slot(), None), )) } else { Err(Self::Error::InvalidSlotName) @@ -794,10 +811,11 @@ impl TryFrom for ast::PrincipalOrResourceConstraint { ), ) } - Some(PrincipalOrResourceInConstraint::Slot { .. }) => { - ast::PrincipalOrResourceConstraint::is_entity_type_in_slot(Arc::new( - entity_type, - )) + Some(PrincipalOrResourceInConstraint::Slot { slot }) => { + ast::PrincipalOrResourceConstraint::is_entity_type_in_slot( + Arc::new(entity_type), + slot.extract_id_out_of_generalized_slot(), + ) } }) }), @@ -818,9 +836,9 @@ impl TryFrom for ast::PrincipalOrResourceConstraint { ))), ), ResourceConstraint::Eq(EqConstraint::Slot { slot }) => { - if slot == ast::SlotId::resource() { + if slot == ast::SlotId::resource() || slot.is_generalized_slot() { Ok(ast::PrincipalOrResourceConstraint::Eq( - ast::EntityReference::Slot(None), + ast::EntityReference::Slot(slot.extract_id_out_of_generalized_slot(), None), )) } else { Err(Self::Error::InvalidSlotName) @@ -832,9 +850,9 @@ impl TryFrom for ast::PrincipalOrResourceConstraint { ))), ), ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => { - if slot == ast::SlotId::resource() { + if slot == ast::SlotId::resource() || slot.is_generalized_slot() { Ok(ast::PrincipalOrResourceConstraint::In( - ast::EntityReference::Slot(None), + ast::EntityReference::Slot(slot.extract_id_out_of_generalized_slot(), None), )) } else { Err(Self::Error::InvalidSlotName) @@ -859,10 +877,11 @@ impl TryFrom for ast::PrincipalOrResourceConstraint { ), ) } - Some(PrincipalOrResourceInConstraint::Slot { .. }) => { - ast::PrincipalOrResourceConstraint::is_entity_type_in_slot(Arc::new( - entity_type, - )) + Some(PrincipalOrResourceInConstraint::Slot { slot }) => { + ast::PrincipalOrResourceConstraint::is_entity_type_in_slot( + Arc::new(entity_type), + slot.extract_id_out_of_generalized_slot(), + ) } }) }), diff --git a/cedar-policy-core/src/evaluator.rs b/cedar-policy-core/src/evaluator.rs index b885fb08b1..06c0fc14f6 100644 --- a/cedar-policy-core/src/evaluator.rs +++ b/cedar-policy-core/src/evaluator.rs @@ -453,7 +453,9 @@ impl<'e> Evaluator<'e> { ExprKind::Lit(lit) => Ok(lit.clone().into()), ExprKind::Slot(id) => slots .get(id) - .ok_or_else(|| err::EvaluationError::unlinked_slot(*id, loc.into_maybe_loc())) + .ok_or_else(|| { + err::EvaluationError::unlinked_slot(id.clone(), loc.into_maybe_loc()) + }) .map(|euid| PartialValue::from(euid.clone())), ExprKind::Var(v) => match v { Var::Principal => Ok(self.principal.evaluate(*v)), diff --git a/cedar-policy-core/src/parser/cst_to_ast.rs b/cedar-policy-core/src/parser/cst_to_ast.rs index 7b1dc9fb60..2d4d2f0d7a 100644 --- a/cedar-policy-core/src/parser/cst_to_ast.rs +++ b/cedar-policy-core/src/parser/cst_to_ast.rs @@ -2128,6 +2128,7 @@ impl From for cst::Slot { match slot { ast::SlotId(ast::ValidSlotId::Principal) => cst::Slot::Principal, ast::SlotId(ast::ValidSlotId::Resource) => cst::Slot::Resource, + ast::SlotId(ast::ValidSlotId::GeneralizedSlot(id)) => cst::Slot::Other(id.to_smolstr()), } } } @@ -4260,7 +4261,10 @@ mod tests { ), ( r#"permit(principal is User in ?principal, action, resource);"#, - PrincipalConstraint::is_entity_type_in_slot(Arc::new("User".parse().unwrap())), + PrincipalConstraint::is_entity_type_in_slot( + Arc::new("User".parse().unwrap()), + None, + ), ActionConstraint::any(), ResourceConstraint::any(), ), @@ -4283,7 +4287,10 @@ mod tests { r#"permit(principal, action, resource is Folder in ?resource);"#, PrincipalConstraint::any(), ActionConstraint::any(), - ResourceConstraint::is_entity_type_in_slot(Arc::new("Folder".parse().unwrap())), + ResourceConstraint::is_entity_type_in_slot( + Arc::new("Folder".parse().unwrap()), + None, + ), ), ] { let policy = parse_policy_or_template(None, src).unwrap(); diff --git a/cedar-policy-core/src/parser/cst_to_ast/to_ref_or_refs.rs b/cedar-policy-core/src/parser/cst_to_ast/to_ref_or_refs.rs index 07add9b4c4..a4409fa347 100644 --- a/cedar-policy-core/src/parser/cst_to_ast/to_ref_or_refs.rs +++ b/cedar-policy-core/src/parser/cst_to_ast/to_ref_or_refs.rs @@ -20,6 +20,7 @@ use super::Result; use crate::ast; use crate::ast::EntityReference; use crate::ast::EntityUID; +use crate::ast::Id; use crate::parser::{ cst::{self, Literal}, err::{self, ParseErrors, ToASTError, ToASTErrorKind}, @@ -35,7 +36,7 @@ trait RefKind: Sized { fn err_str() -> &'static str; fn create_single_ref(e: EntityUID) -> Result; fn create_multiple_refs(loc: Option<&Loc>) -> Result) -> Self>; - fn create_slot(loc: Option<&Loc>) -> Result; + fn create_slot(id: Option, loc: Option<&Loc>) -> Result; #[cfg(feature = "tolerant-ast")] fn error_node() -> Self; } @@ -61,7 +62,7 @@ impl RefKind for SingleEntity { .into()) } - fn create_slot(loc: Option<&Loc>) -> Result { + fn create_slot(_id: Option, loc: Option<&Loc>) -> Result { Err(ToASTError::new( ToASTErrorKind::wrong_entity_argument_one_expected( err::parse_errors::Ref::Single, @@ -82,8 +83,8 @@ impl RefKind for EntityReference { "an entity uid or matching template slot" } - fn create_slot(loc: Option<&Loc>) -> Result { - Ok(EntityReference::Slot(loc.into_maybe_loc())) + fn create_slot(id: Option, loc: Option<&Loc>) -> Result { + Ok(EntityReference::Slot(id, loc.into_maybe_loc())) } fn create_single_ref(e: EntityUID) -> Result { @@ -119,7 +120,7 @@ impl RefKind for OneOrMultipleRefs { "an entity uid or set of entity uids" } - fn create_slot(loc: Option<&Loc>) -> Result { + fn create_slot(_id: Option, loc: Option<&Loc>) -> Result { Err(ToASTError::new( ToASTErrorKind::wrong_entity_argument_two_expected( err::parse_errors::Ref::Single, @@ -240,7 +241,7 @@ impl Node> { // it's the wrong slot. This avoids getting an error // `found ?action instead of ?action` when `action` doesn't // support slots. - let slot_ref = T::create_slot(self.loc.as_loc_ref())?; + let slot_ref = T::create_slot(None, self.loc.as_loc_ref())?; // TODO: Currently the only allowed slots are principal & resource, however this will change with generalized slots let slot = s.try_as_inner()?; if slot.matches(var) { Ok(slot_ref) diff --git a/cedar-policy-core/src/validator/rbac.rs b/cedar-policy-core/src/validator/rbac.rs index 5ce0c8bda8..92cf8cc080 100644 --- a/cedar-policy-core/src/validator/rbac.rs +++ b/cedar-policy-core/src/validator/rbac.rs @@ -397,12 +397,12 @@ impl Validator { PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => { Box::new(self.schema.get_entity_types_in(euid.as_ref()).into_iter()) } - PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) - | PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => { + PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_, _)) + | PrincipalOrResourceConstraint::In(EntityReference::Slot(_, _)) => { Box::new(self.schema.entity_type_names()) } PrincipalOrResourceConstraint::Is(entity_type) - | PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => { + | PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_, _)) => { Box::new( if self.schema.is_known_entity_type(entity_type) { Some(entity_type.as_ref()) @@ -655,7 +655,7 @@ mod test { [], ); let schema = schema_file.try_into().unwrap(); - let principal_constraint = PrincipalConstraint::is_eq_slot(); + let principal_constraint = PrincipalConstraint::is_eq_slot(None); let validator = Validator::new(schema); let entities = validator .get_principals_satisfying_constraint(&principal_constraint) diff --git a/cedar-policy-core/src/validator/typecheck.rs b/cedar-policy-core/src/validator/typecheck.rs index b7bf989fe5..2e851c0db8 100644 --- a/cedar-policy-core/src/validator/typecheck.rs +++ b/cedar-policy-core/src/validator/typecheck.rs @@ -398,7 +398,7 @@ impl<'a> SingleEnvTypechecker<'a> { Type::any_entity_reference() })) .with_same_source_loc(e) - .slot(*slotid), + .slot(slotid.clone()), ), // Literal booleans get singleton type according to their value. diff --git a/cedar-policy/src/api.rs b/cedar-policy/src/api.rs index a5ba07f990..53d6d37a23 100644 --- a/cedar-policy/src/api.rs +++ b/cedar-policy/src/api.rs @@ -2768,7 +2768,7 @@ impl PolicySet { let linked_lossless = template .lossless .clone() - .link(unwrapped_vals.iter().map(|(k, v)| (*k, v))) + .link(unwrapped_vals.iter().map(|(k, v)| (k.clone(), v))) // The only error case for `lossless.link()` is a template with // slots which are not filled by the provided values. `ast.link()` // will have already errored if there are any unfilled slots in the @@ -2893,7 +2893,7 @@ fn is_static_or_link( .ast .env() .iter() - .map(|(id, euid)| (*id, euid.clone())) + .map(|(id, euid)| (id.clone(), euid.clone())) .collect(); Ok(Either::Right(TemplateLink { new_id: id.into(), @@ -3130,13 +3130,13 @@ impl Template { ast::PrincipalOrResourceConstraint::In(eref) => { TemplatePrincipalConstraint::In(match eref { ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()), - ast::EntityReference::Slot(_) => None, + ast::EntityReference::Slot(_, _) => None, }) } ast::PrincipalOrResourceConstraint::Eq(eref) => { TemplatePrincipalConstraint::Eq(match eref { ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()), - ast::EntityReference::Slot(_) => None, + ast::EntityReference::Slot(_, _) => None, }) } ast::PrincipalOrResourceConstraint::Is(entity_type) => { @@ -3147,7 +3147,7 @@ impl Template { entity_type.as_ref().clone().into(), match eref { ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()), - ast::EntityReference::Slot(_) => None, + ast::EntityReference::Slot(_, _) => None, }, ) } @@ -3181,13 +3181,13 @@ impl Template { ast::PrincipalOrResourceConstraint::In(eref) => { TemplateResourceConstraint::In(match eref { ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()), - ast::EntityReference::Slot(_) => None, + ast::EntityReference::Slot(_, _) => None, }) } ast::PrincipalOrResourceConstraint::Eq(eref) => { TemplateResourceConstraint::Eq(match eref { ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()), - ast::EntityReference::Slot(_) => None, + ast::EntityReference::Slot(_, _) => None, }) } ast::PrincipalOrResourceConstraint::Is(entity_type) => { @@ -3198,7 +3198,7 @@ impl Template { entity_type.as_ref().clone().into(), match eref { ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()), - ast::EntityReference::Slot(_) => None, + ast::EntityReference::Slot(_, _) => None, }, ) } @@ -3429,7 +3429,7 @@ impl Policy { .ast .env() .iter() - .map(|(key, value)| ((*key).into(), value.clone().into())) + .map(|(key, value)| ((key.clone()).into(), value.clone().into())) .collect(); Some(wrapped_vals) } @@ -3562,7 +3562,7 @@ impl Policy { ast::EntityReference::EUID(euid) => EntityUid::ref_cast(euid), // PANIC SAFETY: This `unwrap` here is safe due the invariant (values total map) on policies. #[allow(clippy::unwrap_used)] - ast::EntityReference::Slot(_) => { + ast::EntityReference::Slot(_, _) => { EntityUid::ref_cast(self.ast.env().get(&slot).unwrap()) } } @@ -3889,7 +3889,7 @@ impl LosslessPolicy { if slots.is_empty() { Ok(est) } else { - let unwrapped_vals = slots.iter().map(|(k, v)| (*k, v.into())).collect(); + let unwrapped_vals = slots.iter().map(|(k, v)| (k.clone(), v.into())).collect(); Ok(est.link(&unwrapped_vals)?) } } From cf5f7a1505d50cd2ee04e2612a1cdf04034cbe15 Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Mon, 23 Jun 2025 16:39:38 -0400 Subject: [PATCH 02/23] make imports consistent Signed-off-by: Alan Wang --- .../src/est/scope_constraints.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cedar-policy-core/src/est/scope_constraints.rs b/cedar-policy-core/src/est/scope_constraints.rs index 821c01c6f0..d6d63f88b2 100644 --- a/cedar-policy-core/src/est/scope_constraints.rs +++ b/cedar-policy-core/src/est/scope_constraints.rs @@ -15,8 +15,8 @@ */ use super::{FromJsonError, LinkingError}; +use crate::ast; use crate::ast::EntityUID; -use crate::ast::{self, SlotId}; use crate::entities::json::{ err::JsonDeserializationError, err::JsonDeserializationErrorContext, EntityUidJson, }; @@ -657,7 +657,7 @@ impl From for PrincipalConstraint { } ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot(id, _)) => { let slot = match id { - Some(id) => SlotId::generalized_slot(id), + Some(id) => ast::SlotId::generalized_slot(id), None => ast::SlotId::principal(), }; PrincipalConstraint::Eq(EqConstraint::Slot { slot }) @@ -669,7 +669,7 @@ impl From for PrincipalConstraint { } ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot(id, _)) => { let slot = match id { - Some(id) => SlotId::generalized_slot(id), + Some(id) => ast::SlotId::generalized_slot(id), None => ast::SlotId::principal(), }; PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) @@ -683,7 +683,7 @@ impl From for PrincipalConstraint { }, ast::EntityReference::Slot(id, _) => { let slot = match id { - Some(id) => SlotId::generalized_slot(id), + Some(id) => ast::SlotId::generalized_slot(id), None => ast::SlotId::principal(), }; PrincipalOrResourceInConstraint::Slot { slot } @@ -712,8 +712,8 @@ impl From for ResourceConstraint { } ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot(id, _)) => { let slot = match id { - Some(id) => SlotId::generalized_slot(id), - None => SlotId::resource(), + Some(id) => ast::SlotId::generalized_slot(id), + None => ast::SlotId::resource(), }; ResourceConstraint::Eq(EqConstraint::Slot { slot }) } @@ -724,8 +724,8 @@ impl From for ResourceConstraint { } ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot(id, _)) => { let slot = match id { - Some(id) => SlotId::generalized_slot(id), - None => SlotId::resource(), + Some(id) => ast::SlotId::generalized_slot(id), + None => ast::SlotId::resource(), }; ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) } @@ -738,8 +738,8 @@ impl From for ResourceConstraint { }, ast::EntityReference::Slot(id, _) => { let slot = match id { - Some(id) => SlotId::generalized_slot(id), - None => SlotId::resource(), + Some(id) => ast::SlotId::generalized_slot(id), + None => ast::SlotId::resource(), }; PrincipalOrResourceInConstraint::Slot { slot } From 92db238a65ad0f7f012359e807c6ea3af34570dd Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Mon, 23 Jun 2025 16:42:00 -0400 Subject: [PATCH 03/23] added comment Signed-off-by: Alan Wang --- cedar-policy/src/api.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cedar-policy/src/api.rs b/cedar-policy/src/api.rs index 53d6d37a23..7e603c3714 100644 --- a/cedar-policy/src/api.rs +++ b/cedar-policy/src/api.rs @@ -3562,7 +3562,8 @@ impl Policy { ast::EntityReference::EUID(euid) => EntityUid::ref_cast(euid), // PANIC SAFETY: This `unwrap` here is safe due the invariant (values total map) on policies. #[allow(clippy::unwrap_used)] - ast::EntityReference::Slot(_, _) => { + ast::EntityReference::Slot(_id, _) => { + // TODO: When id is Some, it is a generalized slot and we must handle it differently EntityUid::ref_cast(self.ast.env().get(&slot).unwrap()) } } From 6e439ceb35dbc20d0532cdeb2fb086c2e29052f2 Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Mon, 23 Jun 2025 22:26:47 -0400 Subject: [PATCH 04/23] updates to proto policy.rs, currently doesn't account for generalized slots in protobuf format Signed-off-by: Alan Wang --- cedar-policy/src/proto/policy.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cedar-policy/src/proto/policy.rs b/cedar-policy/src/proto/policy.rs index c37fc891c1..13070d7c16 100644 --- a/cedar-policy/src/proto/policy.rs +++ b/cedar-policy/src/proto/policy.rs @@ -226,7 +226,7 @@ impl From<&models::EntityReference> for ast::EntityReference { match models::entity_reference::Slot::try_from(*slot) .expect("decode should succeed") { - models::entity_reference::Slot::Unit => ast::EntityReference::Slot(None), + models::entity_reference::Slot::Unit => ast::EntityReference::Slot(None, None), } } models::entity_reference::Data::Euid(euid) => { @@ -244,7 +244,7 @@ impl From<&ast::EntityReference> for models::EntityReference { models::EntityUid::from(euid.as_ref()), )), }, - ast::EntityReference::Slot(_) => Self { + ast::EntityReference::Slot(_, _) => Self { data: Some(models::entity_reference::Data::Slot( models::entity_reference::Slot::Unit.into(), )), @@ -546,9 +546,9 @@ mod test { ast::EntityReference::from(&models::EntityReference::from(&er1)) ); assert_eq!( - ast::EntityReference::Slot(None), + ast::EntityReference::Slot(None, None), ast::EntityReference::from(&models::EntityReference::from( - &ast::EntityReference::Slot(None) + &ast::EntityReference::Slot(None, None) )) ); @@ -709,7 +709,7 @@ mod test { }, )]), ast::Effect::Permit, - ast::PrincipalConstraint::is_eq_slot(), + ast::PrincipalConstraint::is_eq_slot(None), ast::ActionConstraint::Eq( ast::EntityUID::with_eid_and_type("Action", "read") .unwrap() @@ -779,7 +779,7 @@ mod test { }, )]), ast::Effect::Permit, - ast::PrincipalConstraint::is_eq_slot(), + ast::PrincipalConstraint::is_eq_slot(None), ast::ActionConstraint::Eq( ast::EntityUID::with_eid_and_type("Action", "read") .unwrap() From 0a9a0003e6c60bce8e68f50f3d57b35ba22e38c1 Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Tue, 24 Jun 2025 10:39:29 -0400 Subject: [PATCH 05/23] added comments & edited function to return a pointer Signed-off-by: Alan Wang --- cedar-policy-core/src/ast/name.rs | 6 ++--- .../src/est/scope_constraints.rs | 24 ++++++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/cedar-policy-core/src/ast/name.rs b/cedar-policy-core/src/ast/name.rs index 735b137f37..8520655ef1 100644 --- a/cedar-policy-core/src/ast/name.rs +++ b/cedar-policy-core/src/ast/name.rs @@ -319,9 +319,9 @@ impl SlotId { } /// Returns the id if the slot is a `generalized slot` - pub fn extract_id_out_of_generalized_slot(&self) -> Option { + pub fn extract_id_out_of_generalized_slot(&self) -> Option<&Id> { match self { - Self(ValidSlotId::GeneralizedSlot(id)) => Some(id.clone()), + Self(ValidSlotId::GeneralizedSlot(id)) => Some(id), _ => None, } } @@ -349,7 +349,7 @@ pub(crate) enum ValidSlotId { Principal, #[serde(rename = "?resource")] Resource, - GeneralizedSlot(Id), + GeneralizedSlot(Id), // Slots for generalized templates, for more info see [RFC 98](https://github.com/cedar-policy/rfcs/pull/98). } impl std::fmt::Display for ValidSlotId { diff --git a/cedar-policy-core/src/est/scope_constraints.rs b/cedar-policy-core/src/est/scope_constraints.rs index d6d63f88b2..91a62cfbee 100644 --- a/cedar-policy-core/src/est/scope_constraints.rs +++ b/cedar-policy-core/src/est/scope_constraints.rs @@ -772,7 +772,10 @@ impl TryFrom for ast::PrincipalOrResourceConstraint { PrincipalConstraint::Eq(EqConstraint::Slot { slot }) => { if slot == ast::SlotId::principal() || slot.is_generalized_slot() { Ok(ast::PrincipalOrResourceConstraint::Eq( - ast::EntityReference::Slot(slot.extract_id_out_of_generalized_slot(), None), + ast::EntityReference::Slot( + slot.extract_id_out_of_generalized_slot().cloned(), + None, + ), )) } else { Err(Self::Error::InvalidSlotName) @@ -786,7 +789,10 @@ impl TryFrom for ast::PrincipalOrResourceConstraint { PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => { if slot == ast::SlotId::principal() || slot.is_generalized_slot() { Ok(ast::PrincipalOrResourceConstraint::In( - ast::EntityReference::Slot(slot.extract_id_out_of_generalized_slot(), None), + ast::EntityReference::Slot( + slot.extract_id_out_of_generalized_slot().cloned(), + None, + ), )) } else { Err(Self::Error::InvalidSlotName) @@ -814,7 +820,7 @@ impl TryFrom for ast::PrincipalOrResourceConstraint { Some(PrincipalOrResourceInConstraint::Slot { slot }) => { ast::PrincipalOrResourceConstraint::is_entity_type_in_slot( Arc::new(entity_type), - slot.extract_id_out_of_generalized_slot(), + slot.extract_id_out_of_generalized_slot().cloned(), ) } }) @@ -838,7 +844,10 @@ impl TryFrom for ast::PrincipalOrResourceConstraint { ResourceConstraint::Eq(EqConstraint::Slot { slot }) => { if slot == ast::SlotId::resource() || slot.is_generalized_slot() { Ok(ast::PrincipalOrResourceConstraint::Eq( - ast::EntityReference::Slot(slot.extract_id_out_of_generalized_slot(), None), + ast::EntityReference::Slot( + slot.extract_id_out_of_generalized_slot().cloned(), + None, + ), )) } else { Err(Self::Error::InvalidSlotName) @@ -852,7 +861,10 @@ impl TryFrom for ast::PrincipalOrResourceConstraint { ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => { if slot == ast::SlotId::resource() || slot.is_generalized_slot() { Ok(ast::PrincipalOrResourceConstraint::In( - ast::EntityReference::Slot(slot.extract_id_out_of_generalized_slot(), None), + ast::EntityReference::Slot( + slot.extract_id_out_of_generalized_slot().cloned(), + None, + ), )) } else { Err(Self::Error::InvalidSlotName) @@ -880,7 +892,7 @@ impl TryFrom for ast::PrincipalOrResourceConstraint { Some(PrincipalOrResourceInConstraint::Slot { slot }) => { ast::PrincipalOrResourceConstraint::is_entity_type_in_slot( Arc::new(entity_type), - slot.extract_id_out_of_generalized_slot(), + slot.extract_id_out_of_generalized_slot().cloned(), ) } }) From 6dc4a832b24cadba4c4a81941217eafb4da7dbd7 Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Tue, 24 Jun 2025 15:10:53 -0400 Subject: [PATCH 06/23] changed to using as_ref Signed-off-by: Alan Wang --- cedar-policy-core/src/ast/name.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cedar-policy-core/src/ast/name.rs b/cedar-policy-core/src/ast/name.rs index 8520655ef1..7412eb4bf3 100644 --- a/cedar-policy-core/src/ast/name.rs +++ b/cedar-policy-core/src/ast/name.rs @@ -357,7 +357,7 @@ impl std::fmt::Display for ValidSlotId { let s = match self { ValidSlotId::Principal => "principal", ValidSlotId::Resource => "resource", - ValidSlotId::GeneralizedSlot(id) => &id.to_smolstr(), + ValidSlotId::GeneralizedSlot(id) => id.as_ref(), }; write!(f, "?{s}") } From 687984e1db1d6c18a3c206f6f1f2339537722e04 Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Fri, 27 Jun 2025 13:09:37 -0400 Subject: [PATCH 07/23] updates to Cedar's templates & policy structures to now store generalized template annotations and values associated with generalized slots Signed-off-by: Alan Wang --- cedar-policy-core/src/ast.rs | 2 + .../src/ast/generalized_slots_annotation.rs | 85 ++++ cedar-policy-core/src/ast/policy.rs | 260 +++++++++- cedar-policy-core/src/ast/policy_set.rs | 191 ++++++- cedar-policy-core/src/authorizer.rs | 2 + cedar-policy-core/src/est.rs | 31 +- cedar-policy-core/src/est/policy_set.rs | 2 +- cedar-policy-core/src/evaluator.rs | 479 +++++++++++++----- cedar-policy-core/src/parser/cst_to_ast.rs | 45 +- cedar-policy-core/src/parser/util.rs | 15 + cedar-policy-core/src/validator.rs | 3 + .../src/validator/json_schema.rs | 8 +- cedar-policy-core/src/validator/rbac.rs | 6 + cedar-policy/protobuf_schema/core.proto | 50 +- cedar-policy/src/api.rs | 7 +- cedar-policy/src/proto/ast.rs | 52 +- cedar-policy/src/proto/policy.rs | 273 +++++++++- cedar-policy/src/proto/validator.rs | 109 +++- cedar-testing/src/cedar_test_impl.rs | 2 +- 19 files changed, 1417 insertions(+), 205 deletions(-) create mode 100644 cedar-policy-core/src/ast/generalized_slots_annotation.rs diff --git a/cedar-policy-core/src/ast.rs b/cedar-policy-core/src/ast.rs index e8367a647d..020c32f31b 100644 --- a/cedar-policy-core/src/ast.rs +++ b/cedar-policy-core/src/ast.rs @@ -54,5 +54,7 @@ mod expr_iterator; pub use expr_iterator::*; mod annotation; pub use annotation::*; +mod generalized_slots_annotation; +pub use generalized_slots_annotation::*; mod expr_visitor; pub use expr_visitor::*; diff --git a/cedar-policy-core/src/ast/generalized_slots_annotation.rs b/cedar-policy-core/src/ast/generalized_slots_annotation.rs new file mode 100644 index 0000000000..ff0a8417ad --- /dev/null +++ b/cedar-policy-core/src/ast/generalized_slots_annotation.rs @@ -0,0 +1,85 @@ +/* + * Copyright Cedar Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::collections::BTreeMap; + +use crate::ast::SlotId; +use crate::validator::{json_schema::Type, RawName}; +use serde::{Deserialize, Serialize}; + +/// Struct which holds the type & position of a generalized slot +#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Hash, Serialize, Deserialize)] +pub struct GeneralizedSlotsAnnotation(BTreeMap); + +impl GeneralizedSlotsAnnotation { + /// Create a new empty `GeneralizedSlotsAnnotation` (with no slots) + pub fn new() -> Self { + Self(BTreeMap::new()) + } + + /// Get an GeneralizedSlotsAnnotation by key + pub fn get(&self, key: &SlotId) -> Option<&SlotTypePosition> { + self.0.get(key) + } + + /// Iterate over all GeneralizedSlotsAnnotation + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + /// Tell if it's empty + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl Default for GeneralizedSlotsAnnotation { + fn default() -> Self { + Self::new() + } +} + +impl FromIterator<(SlotId, SlotTypePosition)> for GeneralizedSlotsAnnotation { + fn from_iter>(iter: T) -> Self { + Self(BTreeMap::from_iter(iter)) + } +} + +impl From> for GeneralizedSlotsAnnotation { + fn from(value: BTreeMap) -> Self { + Self(value) + } +} + +/// Enum for scope position of generalized slots +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +pub enum ScopePosition { + /// Principal position in scope + Principal, + /// Resource position in scope + Resource, +} + +/// Stores the position and type for generalized slots +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize, Hash)] +pub enum SlotTypePosition { + /// Type & Position of a slot + TyPosition(Type, ScopePosition), + /// Type of a slot + Ty(Type), + /// Position of a slot + Position(ScopePosition), +} diff --git a/cedar-policy-core/src/ast/policy.rs b/cedar-policy-core/src/ast/policy.rs index e8744b943b..f2078ba60b 100644 --- a/cedar-policy-core/src/ast/policy.rs +++ b/cedar-policy-core/src/ast/policy.rs @@ -18,6 +18,7 @@ use crate::ast::*; use crate::parser::{AsLocRef, IntoMaybeLoc, Loc, MaybeLoc}; use annotation::{Annotation, Annotations}; use educe::Educe; +use generalized_slots_annotation::{GeneralizedSlotsAnnotation, SlotTypePosition}; use itertools::Itertools; use miette::Diagnostic; use nonempty::{nonempty, NonEmpty}; @@ -53,6 +54,9 @@ cfg_tolerant_ast! { static DEFAULT_ANNOTATIONS: std::sync::LazyLock> = std::sync::LazyLock::new(|| Arc::new(Annotations::default())); + static DEFAULT_GENERALIZED_SLOTS_ANNOTATION: std::sync::LazyLock> = + std::sync::LazyLock::new(|| Arc::new(GeneralizedSlotsAnnotation::default())); + static DEFAULT_PRINCIPAL_CONSTRAINT: std::sync::LazyLock = std::sync::LazyLock::new(PrincipalConstraint::any); @@ -120,6 +124,7 @@ impl Template { id: PolicyID, loc: MaybeLoc, annotations: Annotations, + generalized_slots_annotation: GeneralizedSlotsAnnotation, effect: Effect, principal_constraint: PrincipalConstraint, action_constraint: ActionConstraint, @@ -130,6 +135,7 @@ impl Template { id, loc, annotations, + generalized_slots_annotation, effect, principal_constraint, action_constraint, @@ -154,6 +160,7 @@ impl Template { id: PolicyID, loc: MaybeLoc, annotations: Arc, + generalized_slots_annotation: Arc, effect: Effect, principal_constraint: PrincipalConstraint, action_constraint: ActionConstraint, @@ -164,6 +171,7 @@ impl Template { id, loc, annotations, + generalized_slots_annotation, effect, principal_constraint, action_constraint, @@ -238,6 +246,13 @@ impl Template { self.body.annotations_arc() } + /// Get all generalized_slots_annotation data. + pub fn generalized_slots_annotation( + &self, + ) -> impl Iterator { + self.body.generalized_slots_annotation() + } + /// Get the condition expression of this template. /// /// This will be a conjunction of the template's scope constraints (on @@ -264,17 +279,36 @@ impl Template { /// and that no extra values are bound in values /// This upholds invariant (values total map) pub fn check_binding( + // Should we enforce that the keyspace between values and generalized_values are disjoint here? template: &Template, values: &HashMap, + generalized_values: &HashMap, ) -> Result<(), LinkingError> { // Verify all slots bound let unbound = template .slots .iter() - .filter(|slot| !values.contains_key(&slot.id)) + .filter(|slot| { + !values.contains_key(&slot.id) && !generalized_values.contains_key(&slot.id) + }) + .collect::>(); + + let extra_values = values + .iter() + .filter_map(|(slot, _)| { + if !template + .slots + .iter() + .any(|template_slot| template_slot.id == *slot) + { + Some(slot) + } else { + None + } + }) .collect::>(); - let extra = values + let extra_generalized_values = generalized_values .iter() .filter_map(|(slot, _)| { if !template @@ -289,12 +323,50 @@ impl Template { }) .collect::>(); - if unbound.is_empty() && extra.is_empty() { + let invalid_keys_in_values = values + .iter() + .filter_map(|(slot, _)| { + if !(*slot == (SlotId::principal()) || *slot == (SlotId::resource())) { + Some(slot) + } else { + None + } + }) + .collect::>(); + + let invalid_keys_in_generalized_values = generalized_values + .iter() + .filter_map(|(slot, _)| { + if *slot == (SlotId::principal()) || *slot == (SlotId::resource()) { + Some(slot) + } else { + None + } + }) + .collect::>(); + + if unbound.is_empty() + && extra_values.is_empty() + && extra_generalized_values.is_empty() + && invalid_keys_in_values.is_empty() + && invalid_keys_in_generalized_values.is_empty() + { Ok(()) + } else if !(invalid_keys_in_values.is_empty()) { + Err(LinkingError::from_invalid_env( + invalid_keys_in_values.into_iter().cloned(), + )) + } else if !(invalid_keys_in_generalized_values.is_empty()) { + Err(LinkingError::from_invalid_generalized_env( + invalid_keys_in_generalized_values.into_iter().cloned(), + )) } else { Err(LinkingError::from_unbound_and_extras( unbound.into_iter().map(|slot| slot.id.clone()), - extra.into_iter().cloned(), + extra_values + .into_iter() + .cloned() + .chain(extra_generalized_values.into_iter().cloned()), )) } } @@ -306,10 +378,12 @@ impl Template { template: Arc