Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow typed unknown resource and principal #1391

Merged
merged 14 commits into from
Jan 8, 2025
Merged
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>,

B-Lorentz marked this conversation as resolved.
Show resolved Hide resolved
/// 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