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
9 changes: 8 additions & 1 deletion cedar-policy-validator/src/typecheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1865,7 +1865,14 @@ impl<'a> Typechecker<'a> {
.get_entity_type(&rhs_name)
.map(|ety| ety.descendants.contains(&lhs_name))
.unwrap_or(false);
if lhs_name == rhs_name || lhs_ty_in_rhs_ty {
// A schema may always declare that an action entity is a member of another action entity,
// regardless of their exact types (i.e., their namespaces), so we shouldn't treat it as an error.
let action_in_action = is_action_entity_type(&lhs_name)
&& is_action_entity_type(&rhs_name);
if lhs_name == rhs_name
|| action_in_action
|| lhs_ty_in_rhs_ty
{
TypecheckAnswer::success(type_of_in)
} else {
// We could actually just return `Type::False`, but this is incurs a larger Dafny proof update.
Expand Down
155 changes: 154 additions & 1 deletion cedar-policy-validator/src/typecheck/test_namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#![cfg(test)]
// GRCOV_STOP_COVERAGE

use cool_asserts::assert_matches;
use serde_json::json;
use std::str::FromStr;
use std::vec;
Expand All @@ -35,7 +36,7 @@ use super::test_utils::{
use crate::{
type_error::TypeError,
types::{EntityLUB, Type},
AttributeAccess, SchemaFragment, ValidatorSchema,
AttributeAccess, SchemaError, SchemaFragment, ValidatorSchema,
};

fn namespaced_entity_type_schema() -> SchemaFragment {
Expand Down Expand Up @@ -526,3 +527,155 @@ fn namespaced_entity_is_wrong_type_when() {
)],
);
}

#[test]
fn multi_namespace_action_eq() {
let (schema, _) = SchemaFragment::from_str_natural(
r#"
action "Action" appliesTo { context: {} };
namespace NS1 { action "Action" appliesTo { context: {} }; }
namespace NS2 { action "Action" appliesTo { context: {} }; }
"#,
)
.unwrap();

assert_policy_typechecks(
schema.clone(),
parse_policy(
None,
r#"permit(principal, action == Action::"Action", resource);"#,
)
.unwrap(),
);
assert_policy_typechecks(
schema.clone(),
parse_policy(
None,
r#"permit(principal, action == NS1::Action::"Action", resource);"#,
)
.unwrap(),
);

let policy = parse_policy(
None,
r#"permit(principal, action, resource) when { NS1::Action::"Action" == NS2::Action::"Action" };"#,
)
.unwrap();
assert_policy_typecheck_fails(
schema.clone(),
policy.clone(),
vec![TypeError::impossible_policy(policy.condition())],
);
}

#[test]
fn multi_namespace_action_in() {
let (schema, _) = SchemaFragment::from_str_natural(
r#"
namespace NS1 { action "Group"; }
namespace NS2 { action "Group" in [NS1::Action::"Group"]; }
namespace NS3 {
action "Group" in [NS2::Action::"Group"];
action "Action" in [Action::"Group"] appliesTo { context: {} };
}
namespace NS4 { action "Group"; }
"#,
)
.unwrap();

assert_policy_typechecks(
schema.clone(),
parse_policy(
None,
r#"permit(principal, action in NS1::Action::"Group", resource);"#,
)
.unwrap(),
);
assert_policy_typechecks(
schema.clone(),
parse_policy(
None,
r#"permit(principal, action in NS2::Action::"Group", resource);"#,
)
.unwrap(),
);
assert_policy_typechecks(
schema.clone(),
parse_policy(
None,
r#"permit(principal, action in NS3::Action::"Group", resource);"#,
)
.unwrap(),
);
assert_policy_typechecks(
schema.clone(),
parse_policy(
None,
r#"permit(principal, action in NS3::Action::"Action", resource);"#,
)
.unwrap(),
);

let policy = parse_policy(
None,
r#"permit(principal, action in NS4::Action::"Group", resource);"#,
)
.unwrap();
assert_policy_typecheck_fails(
schema.clone(),
policy.clone(),
vec![TypeError::impossible_policy(policy.condition())],
);
}

#[test]
fn test_cedar_policy_642() {
let (schema, _) = SchemaFragment::from_str_natural(
r#"
namespace NS1 {
entity SystemEntity2 in SystemEntity1;
entity SystemEntity1, PrincipalEntity;
action Group1;
}
namespace NS2 {
entity SystemEntity1 in NS1::SystemEntity2;
action "Group1" in NS1::Action::"Group1";
action "Action1" in Action::"Group1" appliesTo {
principal: [NS1::PrincipalEntity],
resource: [NS2::SystemEntity1],
};
}
"#,
)
.unwrap();

assert_policy_typechecks(
schema.clone(),
parse_policy(
None,
r#"
permit(
principal in NS1::PrincipalEntity::"user1",
action in NS1::Action::"Group1",
resource in NS1::SystemEntity1::"entity1"
);"#,
)
.unwrap(),
);
}

#[test]
fn multi_namespace_action_group_cycle() {
let (schema, _) = SchemaFragment::from_str_natural(
r#"
namespace A { action "Act" in C::Action::"Act"; }
namespace B { action "Act" in A::Action::"Act"; }
namespace C { action "Act" in B::Action::"Act"; }
"#,
)
.unwrap();
assert_matches!(
ValidatorSchema::try_from(schema),
Err(SchemaError::CycleInActionHierarchy(_))
)
}
3 changes: 3 additions & 0 deletions cedar-policy/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Validation for the `in` operator to no longer reports an error when comparing actions
in different namespaces. (#704, resolving #642)

## [3.1.0] - 2024-03-08
Cedar Language Version: 3.1.0

Expand Down