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
31 changes: 30 additions & 1 deletion cedar-policy-core/src/ast/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ impl<'a> arbitrary::Arbitrary<'a> for Id {
/// (It still can't contain, for instance, spaces or characters like '+'.)
//
// For now, internally, `AnyId`s are just owned `SmolString`s.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
pub struct AnyId(SmolStr);

impl AnyId {
Expand All @@ -177,6 +177,35 @@ impl AnyId {
}
}

struct AnyIdVisitor;

impl<'de> serde::de::Visitor<'de> for AnyIdVisitor {
type Value = AnyId;

fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: could it be more descriptive than any id?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not actually sure where this appears in errors. If it doesn't parse then it uses the custom error, and if it's not a string it gives a serde error that doesn't include this expecting text.

formatter.write_str("any id")
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
AnyId::from_normalized_str(value)
.map_err(|err| serde::de::Error::custom(format!("invalid id `{value}`: {err}")))
}
}

/// Deserialize an `AnyId` using `from_normalized_str`.
/// This deserialization implementation is used in the JSON policy format.
impl<'de> Deserialize<'de> for AnyId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(AnyIdVisitor)
}
}

impl AsRef<str> for AnyId {
fn as_ref(&self) -> &str {
&self.0
Expand Down
89 changes: 89 additions & 0 deletions cedar-policy-core/src/est.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4146,3 +4146,92 @@ mod issue_925 {
);
}
}

#[cfg(test)]
mod issue_994 {
use crate::{
entities::json::err::JsonDeserializationError,
est,
test_utils::{expect_err, ExpectedErrorMessageBuilder},
};
use cool_asserts::assert_matches;
use serde_json::json;

#[test]
fn empty_annotation() {
let src = json!(
{
"annotations": {"": ""},
"effect": "permit",
"principal": { "op": "All" },
"action": { "op": "All" },
"resource": { "op": "All" },
"conditions": []
}
);
assert_matches!(
serde_json::from_value::<est::Policy>(src.clone())
.map_err(|e| JsonDeserializationError::Serde(e.into())),
Err(e) => {
expect_err(
&src,
&miette::Report::new(e),
&ExpectedErrorMessageBuilder::error(r#"invalid id ``: unexpected end of input"#)
.build()
);
}
);
}

#[test]
fn annotation_with_space() {
let src = json!(
{
"annotations": {"has a space": ""},
"effect": "permit",
"principal": { "op": "All" },
"action": { "op": "All" },
"resource": { "op": "All" },
"conditions": []
}
);
assert_matches!(
serde_json::from_value::<est::Policy>(src.clone())
.map_err(|e| JsonDeserializationError::Serde(e.into())),
Err(e) => {
expect_err(
&src,
&miette::Report::new(e),
&ExpectedErrorMessageBuilder::error(r#"invalid id `has a space`: unexpected token `a`"#)
.build()
);
}
);
}

#[test]
fn special_char() {
let src = json!(
{
"annotations": {"@": ""},
"effect": "permit",
"principal": { "op": "All" },
"action": { "op": "All" },
"resource": { "op": "All" },
"conditions": []
}
);
assert_matches!(
serde_json::from_value::<est::Policy>(src.clone())
.map_err(|e| JsonDeserializationError::Serde(e.into())),
Err(e) => {
expect_err(
&src,
&miette::Report::new(e),
&ExpectedErrorMessageBuilder::error(r#"invalid id `@`: unexpected token `@`"#)
.build()
);
}
);
}
}
2 changes: 2 additions & 0 deletions cedar-policy/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- JSON format Cedar policies will now fail to parse if the action scope
constraint contains a non-action entity type, matching the behavior for
human-readable Cedar policies. (#943, resolving #925)
- JSON format Cedar policies will now fail to parse if any annotations are not
valid Cedar identifiers. (#1004, resolving #994)

## [3.2.1] - 2024-05-31

Expand Down