Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 46 additions & 5 deletions cedar-policy-core/src/ast/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ pub enum EntityUIDEntry {
},
/// An EntityUID left as unknown for partial evaluation
Unknown {
/// The type of the unknown EntityUID, if known.
ty: Option<EntityType>,

/// Source location associated with the `EntityUIDEntry`, if any
loc: Option<Loc>,
},
Expand All @@ -92,9 +95,22 @@ impl EntityUIDEntry {
EntityUIDEntry::Known { euid, loc } => {
Value::new(Arc::unwrap_or_clone(Arc::clone(euid)), loc.clone()).into()
}
EntityUIDEntry::Unknown { loc } => Expr::unknown(Unknown::new_untyped(var.to_string()))
.with_maybe_source_loc(loc.clone())
.into(),
EntityUIDEntry::Unknown { ty: None, loc } => {
Expr::unknown(Unknown::new_untyped(var.to_string()))
.with_maybe_source_loc(loc.clone())
.into()
}
EntityUIDEntry::Unknown {
ty: Some(known_type),
loc,
} => Expr::unknown(Unknown::new_with_type(
var.to_string(),
super::Type::Entity {
ty: known_type.clone(),
},
))
.with_maybe_source_loc(loc.clone())
.into(),
}
}

Expand All @@ -106,13 +122,34 @@ impl EntityUIDEntry {
}
}

/// Create an entry with an entirely unknown EntityUID
pub fn unknown() -> Self {
Self::Unknown {
ty: None,
loc: None,
}
}

/// Create an entry with an unknown EntityUID but known EntityType
pub fn unknown_with_type(ty: EntityType, loc: Option<Loc>) -> Self {
Self::Unknown { ty: Some(ty), loc }
}

/// Get the UID of the entry, or `None` if it is unknown (partial evaluation)
pub fn uid(&self) -> Option<&EntityUID> {
match self {
Self::Known { euid, .. } => Some(euid),
Self::Unknown { .. } => None,
}
}

/// Get the type of the entry, or `None` if it is unknown (partial evaluation with no type annotation)
pub fn get_type(&self) -> Option<&EntityType> {
match self {
Self::Known { euid, .. } => Some(euid.entity_type()),
Self::Unknown { ty, .. } => ty.as_ref(),
}
}
}

#[cfg(feature = "protobufs")]
Expand All @@ -133,7 +170,7 @@ impl From<&EntityUIDEntry> for proto::EntityUidEntry {
#[allow(clippy::unimplemented)]
fn from(v: &EntityUIDEntry) -> Self {
match v {
EntityUIDEntry::Unknown { loc: _ } => {
EntityUIDEntry::Unknown { .. } => {
unimplemented!(
"Unknown EntityUID is not currently supported by the Protobuf interface"
);
Expand Down Expand Up @@ -250,7 +287,11 @@ impl std::fmt::Display for Request {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let display_euid = |maybe_euid: &EntityUIDEntry| match maybe_euid {
EntityUIDEntry::Known { euid, .. } => format!("{euid}"),
EntityUIDEntry::Unknown { .. } => "unknown".to_string(),
EntityUIDEntry::Unknown { ty: None, .. } => "unknown".to_string(),
EntityUIDEntry::Unknown {
ty: Some(known_type),
..
} => format!("unknown of type {}", known_type),
};
write!(
f,
Expand Down
10 changes: 10 additions & 0 deletions cedar-policy-core/src/authorizer/err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ pub enum ConcretizationError {
/// The provided value
given_value: Value,
},
/// Errors that occur when binding variables with known values
#[error("concretizing existing but unknown entity value of type {existing_value} of {id} with value {given_value}")]
EntityTypeConfictError {
/// String representation of PARC
id: SmolStr,
/// Existing value of PARC
existing_value: EntityType,
/// The provided value
given_value: Value,
},
/// Errors that occur when evaluating partial values
#[error(transparent)]
#[diagnostic(transparent)]
Expand Down
131 changes: 57 additions & 74 deletions cedar-policy-core/src/authorizer/partial_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,79 +335,25 @@ impl PartialResponse {
&self,
mapping: &HashMap<SmolStr, Value>,
) -> Result<Request, ConcretizationError> {
let mut principal = self.request.principal.clone();
let mut action = self.request.action.clone();
let mut resource = self.request.resource.clone();
let mut context = self.request.context.clone();

if let Some((key, val)) = mapping.get_key_value("principal") {
if let Ok(uid) = val.get_as_entity() {
match self.request.principal() {
EntityUIDEntry::Known { euid, .. } => {
return Err(ConcretizationError::VarConfictError {
id: key.to_owned(),
existing_value: euid.as_ref().clone().into(),
given_value: val.clone(),
});
}
EntityUIDEntry::Unknown { .. } => {
principal = EntityUIDEntry::known(uid.clone(), None);
}
}
} else {
return Err(ConcretizationError::ValueError {
id: key.to_owned(),
expected_type: "entity",
given_value: val.to_owned(),
});
}
}
let principal = if let Some((key, val)) = mapping.get_key_value("principal") {
self.request.principal().concretize(key, val)?
} else {
self.request.principal().clone()
};

if let Some((key, val)) = mapping.get_key_value("action") {
if let Ok(uid) = val.get_as_entity() {
match self.request.action() {
EntityUIDEntry::Known { euid, .. } => {
return Err(ConcretizationError::VarConfictError {
id: key.to_owned(),
existing_value: euid.as_ref().clone().into(),
given_value: val.clone(),
});
}
EntityUIDEntry::Unknown { .. } => {
action = EntityUIDEntry::known(uid.clone(), None);
}
}
} else {
return Err(ConcretizationError::ValueError {
id: key.to_owned(),
expected_type: "entity",
given_value: val.to_owned(),
});
}
}
let action = if let Some((key, val)) = mapping.get_key_value("action") {
self.request.action().concretize(key, val)?
} else {
self.request.action().clone()
};

if let Some((key, val)) = mapping.get_key_value("resource") {
if let Ok(uid) = val.get_as_entity() {
match self.request.resource() {
EntityUIDEntry::Known { euid, .. } => {
return Err(ConcretizationError::VarConfictError {
id: key.to_owned(),
existing_value: euid.as_ref().clone().into(),
given_value: val.clone(),
});
}
EntityUIDEntry::Unknown { .. } => {
resource = EntityUIDEntry::known(uid.clone(), None);
}
}
} else {
return Err(ConcretizationError::ValueError {
id: key.to_owned(),
expected_type: "entity",
given_value: val.to_owned(),
});
}
}
let resource = if let Some((key, val)) = mapping.get_key_value("resource") {
self.request.resource().concretize(key, val)?
} else {
self.request.resource().clone()
};

if let Some((key, val)) = mapping.get_key_value("context") {
if let Ok(attrs) = val.get_as_record() {
Expand Down Expand Up @@ -459,6 +405,43 @@ impl PartialResponse {
}
}

impl EntityUIDEntry {
fn concretize(&self, key: &SmolStr, val: &Value) -> Result<Self, ConcretizationError> {
if let Ok(uid) = val.get_as_entity() {
match self {
EntityUIDEntry::Known { euid, .. } => Err(ConcretizationError::VarConfictError {
id: key.to_owned(),
existing_value: euid.as_ref().clone().into(),
given_value: val.clone(),
}),
EntityUIDEntry::Unknown { ty: None, .. } => {
Ok(EntityUIDEntry::known(uid.clone(), None))
}
EntityUIDEntry::Unknown {
ty: Some(type_of_unknown),
..
} => {
if type_of_unknown == uid.entity_type() {
Ok(EntityUIDEntry::known(uid.clone(), None))
} else {
Err(ConcretizationError::EntityTypeConfictError {
id: key.to_owned(),
existing_value: type_of_unknown.clone(),
given_value: val.to_owned(),
})
}
}
}
} else {
Err(ConcretizationError::ValueError {
id: key.to_owned(),
expected_type: "entity",
given_value: val.to_owned(),
})
}
}
}

impl From<PartialResponse> for Response {
fn from(p: PartialResponse) -> Self {
let decision = if !p.satisfied_permits.is_empty() && p.satisfied_forbids.is_empty() {
Expand Down Expand Up @@ -624,9 +607,9 @@ mod test {
h,
errs,
Arc::new(Request::new_unchecked(
EntityUIDEntry::Unknown { loc: None },
EntityUIDEntry::Unknown { loc: None },
EntityUIDEntry::Unknown { loc: None },
EntityUIDEntry::unknown(),
EntityUIDEntry::unknown(),
EntityUIDEntry::unknown(),
Some(Context::empty()),
)),
);
Expand Down Expand Up @@ -807,8 +790,8 @@ mod test {

let partial_request = Request {
principal: EntityUIDEntry::known(r#"NS::"a""#.parse().unwrap(), None),
action: EntityUIDEntry::Unknown { loc: None },
resource: EntityUIDEntry::Unknown { loc: None },
action: EntityUIDEntry::unknown(),
resource: EntityUIDEntry::unknown(),
context: Some(context_unknown),
};

Expand Down
Loading
Loading