From 21b6103ac78a4dcf37b298a423724ce57476ba28 Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Tue, 19 Aug 2025 15:39:44 -0400 Subject: [PATCH 1/5] adds cedarschema support for validateEntities in FFI Signed-off-by: Mudit Chaudhary --- CedarJavaFFI/src/interface.rs | 42 +++--- CedarJavaFFI/src/tests.rs | 273 ++++++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+), 18 deletions(-) diff --git a/CedarJavaFFI/src/interface.rs b/CedarJavaFFI/src/interface.rs index 3b8e1c0..df70898 100644 --- a/CedarJavaFFI/src/interface.rs +++ b/CedarJavaFFI/src/interface.rs @@ -151,25 +151,31 @@ pub fn json_validate_entities(input: &str) -> serde_json::Result { /// returns unit value () which is null value when serialized to json. pub fn validate_entities(input: &str) -> serde_json::Result { let validate_entity_call = from_str::(&input)?; - match Schema::from_json_value(validate_entity_call.schema) { - Err(e) => Ok(Answer::fail_bad_request(vec![e.to_string()])), - Ok(schema) => { - match Entities::from_json_value(validate_entity_call.entities, Some(&schema)) { - Err(error) => { - let err_message = match error { - EntitiesError::Serialization(err) => err.to_string(), - EntitiesError::Deserialization(err) => err.to_string(), - EntitiesError::Duplicate(err) => err.to_string(), - EntitiesError::TransitiveClosureError(err) => err.to_string(), - EntitiesError::InvalidEntity(err) => err.to_string(), - }; - Ok(Answer::fail_bad_request(vec![err_message])) - } - Ok(_entities) => Ok(Answer::Success { - result: "null".to_string(), - }), - } + let schema = match validate_entity_call.schema { + Value::String(cedarschema_str) => match Schema::from_cedarschema_str(&cedarschema_str) { + Ok(s) => s.0, + Err(e) => return Ok(Answer::fail_bad_request(vec![e.to_string()])), + }, + cedarschema_json_obj => match Schema::from_json_value(cedarschema_json_obj) { + Ok(s) => s, + Err(e) => return Ok(Answer::fail_bad_request(vec![e.to_string()])), + }, + }; + + match Entities::from_json_value(validate_entity_call.entities, Some(&schema)) { + Err(error) => { + let err_message = match error { + EntitiesError::Serialization(err) => err.to_string(), + EntitiesError::Deserialization(err) => err.to_string(), + EntitiesError::Duplicate(err) => err.to_string(), + EntitiesError::TransitiveClosureError(err) => err.to_string(), + EntitiesError::InvalidEntity(err) => err.to_string(), + }; + Ok(Answer::fail_bad_request(vec![err_message])) } + Ok(_entities) => Ok(Answer::Success { + result: "null".to_string(), + }), } } diff --git a/CedarJavaFFI/src/tests.rs b/CedarJavaFFI/src/tests.rs index 4ac2b1e..2a905a1 100644 --- a/CedarJavaFFI/src/tests.rs +++ b/CedarJavaFFI/src/tests.rs @@ -459,6 +459,100 @@ mod entity_validation_tests { assert_success(&result); } + #[test] + fn validate_entities_with_cedarschema_succeeds() { + let json_data = json!( + { + "entities":[ + { + "uid": { + "type": "PhotoApp::User", + "id": "alice" + }, + "attrs": { + "userId": "897345789237492878", + "personInformation": { + "age": 25, + "name": "alice" + }, + }, + "parents": [ + { + "type": "PhotoApp::UserGroup", + "id": "alice_friends" + }, + { + "type": "PhotoApp::UserGroup", + "id": "AVTeam" + } + ] + }, + { + "uid": { + "type": "PhotoApp::Photo", + "id": "vacationPhoto.jpg" + }, + "attrs": { + "private": false, + "account": { + "__entity": { + "type": "PhotoApp::Account", + "id": "ahmad" + } + } + }, + "parents": [] + }, + { + "uid": { + "type": "PhotoApp::UserGroup", + "id": "alice_friends" + }, + "attrs": {}, + "parents": [] + }, + { + "uid": { + "type": "PhotoApp::UserGroup", + "id": "AVTeam" + }, + "attrs": {}, + "parents": [] + } + ], + "schema":r#" + namespace PhotoApp { + type ContextType = { + "authenticated": __cedar::Bool, + "ip"?: __cedar::ipaddr + }; + + type PersonType = { + "age": __cedar::Long, + "name": __cedar::String + }; + + entity Account; + + entity Album; + + entity Photo in [Album, Account] = { + "account": Account, + "private": __cedar::Bool + }; + + entity User in [UserGroup] = { + "personInformation": PersonType, + "userId": __cedar::String + }; + + entity UserGroup; + }"# + }); + let result = call_cedar("ValidateEntities", json_data.to_string().as_str()); + assert_success(&result); + } + #[test] fn validate_entities_field_missing() { let json_data = json!( @@ -609,6 +703,95 @@ mod entity_validation_tests { assert_failure(&result); } + #[test] + fn validate_entities_with_cedarschema_field_missing() { + let json_data = json!( + { + "entities":[ + { + "uid": { + "type": "PhotoApp::User", + "id": "alice" + }, + "attrs": { + "userId": "897345789237492878" + }, + "parents": [ + { + "type": "PhotoApp::UserGroup", + "id": "alice_friends" + }, + { + "type": "PhotoApp::UserGroup", + "id": "AVTeam" + } + ] + }, + { + "uid": { + "type": "PhotoApp::Photo", + "id": "vacationPhoto.jpg" + }, + "attrs": { + "private": false, + "account": { + "__entity": { + "type": "PhotoApp::Account", + "id": "ahmad" + } + } + }, + "parents": [] + }, + { + "uid": { + "type": "PhotoApp::UserGroup", + "id": "alice_friends" + }, + "attrs": {}, + "parents": [] + }, + { + "uid": { + "type": "PhotoApp::UserGroup", + "id": "AVTeam" + }, + "attrs": {}, + "parents": [] + } + ], + "schema":r#"namespace PhotoApp { + type ContextType = { + "authenticated": __cedar::Bool, + "ip"?: __cedar::ipaddr + }; + + type PersonType = { + "age": __cedar::Long, + "name": __cedar::String + }; + + entity Account; + + entity Album; + + entity Photo in [Album, Account] = { + "account": Account, + "private": __cedar::Bool + }; + + entity User in [UserGroup] = { + "personInformation": PersonType, + "userId": __cedar::String + }; + + entity UserGroup; + }"# + }); + let result = call_cedar("ValidateEntities", json_data.to_string().as_str()); + assert_failure(&result); + } + #[test] #[should_panic] fn validate_entities_invalid_json_fails() { @@ -651,6 +834,50 @@ mod entity_validation_tests { ); } + #[test] + fn validate_entities_invalid_cedarschema_fails() { + let json_data = json!( + { + "entities": [ + + ], + "schema": r#"namespace PhotoApp { + type ContextType = { + "authenticated": __cedar::Bool, + "ip"?: __cedar::ipaddr + }; + + type PersonType = { + "age": __cedar::Long, + "name": __cedar::String + }; + + entity Account; + + entity Album; + + entity Photo in [Album, Account] = { + "account": Account, + "private": __cedar::Tool + }; + + entity User in [UserGroup] = { + "personInformation": PersonType, + "userId": __cedar::String + }; + + entity UserGroup; + }"# + }); + let result = call_cedar("ValidateEntities", json_data.to_string().as_str()); + assert_failure(&result); + + assert!( + result.contains("failed to resolve type: __cedar::Tool"), + "result was `{result}`", + ); + } + #[test] fn validate_entities_detect_cycle_fails() { let json_data = json!( @@ -709,6 +936,52 @@ mod entity_validation_tests { "result was `{result}`", ); } + + #[test] + fn validate_entities_with_cedarschema_detect_cycle_fails() { + let json_data = json!( + { + "entities": [ + { + "uid": { + "type": "PhotoApp::UserGroup", + "id": "ABCTeam" + }, + "attrs": {}, + "parents": [ + { + "type": "PhotoApp::UserGroup", + "id": "AVTeam" + } + ] + }, + { + "uid": { + "type": "PhotoApp::UserGroup", + "id": "AVTeam" + }, + "attrs": {}, + "parents": [ + { + "type": "PhotoApp::UserGroup", + "id": "ABCTeam" + } + ] + } + ], + "schema": r#"namespace PhotoApp { + entity UserGroup in [UserGroup]; + } + "# + }); + let result = call_cedar("ValidateEntities", json_data.to_string().as_str()); + assert_failure(&result); + + assert!( + result.contains("input graph has a cycle containing vertex `PhotoApp::UserGroup"), + "result was `{result}`", + ); + } } #[cfg(feature = "partial-eval")] From 15fb1b8cd32ea6c3d4211e39e779942510450873 Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Wed, 20 Aug 2025 11:59:20 -0400 Subject: [PATCH 2/5] adds entity validation with cedar schema tests Signed-off-by: Mudit Chaudhary --- .../cedarpolicy/EntityValidationTests.java | 70 ++++++++++++++++--- .../test/java/com/cedarpolicy/TestUtil.java | 12 ++++ .../test/resources/role_schema.cedarschema | 1 + 3 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 CedarJava/src/test/resources/role_schema.cedarschema diff --git a/CedarJava/src/test/java/com/cedarpolicy/EntityValidationTests.java b/CedarJava/src/test/java/com/cedarpolicy/EntityValidationTests.java index 6cfc2b3..00bd159 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/EntityValidationTests.java +++ b/CedarJava/src/test/java/com/cedarpolicy/EntityValidationTests.java @@ -17,6 +17,7 @@ package com.cedarpolicy; import static com.cedarpolicy.TestUtil.loadSchemaResource; +import static com.cedarpolicy.TestUtil.loadCedarSchemaResource; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -51,10 +52,14 @@ public class EntityValidationTests { public void testValidEntity() throws AuthException { Entity entity = EntityValidationTests.entityGen.arbitraryEntity(); - EntityValidationRequest r = new EntityValidationRequest( - ROLE_SCHEMA, List.of(entity)); + EntityValidationRequest request = new EntityValidationRequest(ROLE_SCHEMA, List.of(entity)); + + engine.validateEntities(request); + + EntityValidationRequest cedarFormatRequest = + new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(entity)); - engine.validateEntities(r); + engine.validateEntities(cedarFormatRequest); } /** @@ -67,11 +72,25 @@ public void testEntityWithUnknownAttribute() throws AuthException { EntityValidationRequest request = new EntityValidationRequest(ROLE_SCHEMA, List.of(entity)); - BadRequestException exception = assertThrows(BadRequestException.class, () -> engine.validateEntities(request)); + BadRequestException exception = + assertThrows(BadRequestException.class, () -> engine.validateEntities(request)); String errMsg = exception.getErrors().get(0); - assertTrue(errMsg.matches("attribute `test` on `Role::\".*\"` should not exist according to the schema"), + assertTrue(errMsg.matches( + "attribute `test` on `Role::\".*\"` should not exist according to the schema"), + "Expected to match regex but was: '%s'".formatted(errMsg)); + + EntityValidationRequest cedarFormatRequest = + new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(entity)); + + exception = assertThrows(BadRequestException.class, + () -> engine.validateEntities(cedarFormatRequest)); + + errMsg = exception.getErrors().get(0); + assertTrue(errMsg.matches( + "attribute `test` on `Role::\".*\"` should not exist according to the schema"), "Expected to match regex but was: '%s'".formatted(errMsg)); + } /** @@ -87,13 +106,26 @@ public void testEntitiesWithCyclicParentRelationship() throws AuthException { childEntity.parentsEUIDs.add(parentEntity.getEUID()); parentEntity.parentsEUIDs.add(childEntity.getEUID()); - EntityValidationRequest request = new EntityValidationRequest(ROLE_SCHEMA, List.of(parentEntity, childEntity)); + EntityValidationRequest request = + new EntityValidationRequest(ROLE_SCHEMA, List.of(parentEntity, childEntity)); - BadRequestException exception = assertThrows(BadRequestException.class, () -> engine.validateEntities(request)); + BadRequestException exception = + assertThrows(BadRequestException.class, () -> engine.validateEntities(request)); String errMsg = exception.getErrors().get(0); assertTrue(errMsg.matches("input graph has a cycle containing vertex `Role::\".*\"`"), "Expected to match regex but was: '%s'".formatted(errMsg)); + + EntityValidationRequest cedarFormatRequest = + new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(parentEntity, childEntity)); + + exception = assertThrows(BadRequestException.class, + () -> engine.validateEntities(cedarFormatRequest)); + + errMsg = exception.getErrors().get(0); + assertTrue(errMsg.matches("input graph has a cycle containing vertex `Role::\".*\"`"), + "Expected to match regex but was: '%s'".formatted(errMsg)); + } /** @@ -106,12 +138,26 @@ public void testEntityWithUnknownTag() throws AuthException { EntityValidationRequest request = new EntityValidationRequest(ROLE_SCHEMA, List.of(entity)); - BadRequestException exception = assertThrows(BadRequestException.class, () -> engine.validateEntities(request)); + BadRequestException exception = + assertThrows(BadRequestException.class, () -> engine.validateEntities(request)); String errMsg = exception.getErrors().get(0); - assertTrue(errMsg.matches("found a tag `test` on `Role::\".*\"`, " - + "but no tags should exist on `Role::\".*\"` according to the schema"), - "Expected to match regex but was: '%s'".formatted(errMsg)); + assertTrue( + errMsg.matches("found a tag `test` on `Role::\".*\"`, " + + "but no tags should exist on `Role::\".*\"` according to the schema"), + "Expected to match regex but was: '%s'".formatted(errMsg)); + + EntityValidationRequest cedarFormatRequest = + new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(entity)); + + exception = assertThrows(BadRequestException.class, + () -> engine.validateEntities(cedarFormatRequest)); + + errMsg = exception.getErrors().get(0); + assertTrue( + errMsg.matches("found a tag `test` on `Role::\".*\"`, " + + "but no tags should exist on `Role::\".*\"` according to the schema"), + "Expected to match regex but was: '%s'".formatted(errMsg)); } @BeforeAll @@ -124,4 +170,6 @@ public static void setUp() { } private static final Schema ROLE_SCHEMA = loadSchemaResource("/role_schema.json"); + private static final Schema ROLE_SCHEMA_CEDAR = + loadCedarSchemaResource("/role_schema.cedarschema"); } diff --git a/CedarJava/src/test/java/com/cedarpolicy/TestUtil.java b/CedarJava/src/test/java/com/cedarpolicy/TestUtil.java index 28bc3ec..3e99ae7 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/TestUtil.java +++ b/CedarJava/src/test/java/com/cedarpolicy/TestUtil.java @@ -56,6 +56,18 @@ public static Schema loadSchemaResource(String schemaFile) { } } + public static Schema loadCedarSchemaResource(String schemaFile) { + try { + String text = new String(Files.readAllBytes( + Paths.get( + ValidationTests.class.getResource(schemaFile).toURI())), + StandardCharsets.UTF_8); + return new Schema(JsonOrCedar.Cedar, Optional.empty(), Optional.of(text)); + } catch (Exception e) { + throw new RuntimeException("Failed to load test schema file " + schemaFile, e); + } + } + public static PolicySet buildValidPolicySet() { EntityTypeName principalType = EntityTypeName.parse("User").get(); Set policies = new HashSet<>(); diff --git a/CedarJava/src/test/resources/role_schema.cedarschema b/CedarJava/src/test/resources/role_schema.cedarschema new file mode 100644 index 0000000..422226a --- /dev/null +++ b/CedarJava/src/test/resources/role_schema.cedarschema @@ -0,0 +1 @@ +entity Role in [Role]; \ No newline at end of file From 8a406194347e03e19fd68546284aa392f634f7a3 Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Wed, 27 Aug 2025 10:02:47 -0400 Subject: [PATCH 3/5] fixes nits Signed-off-by: Mudit Chaudhary --- .../cedarpolicy/EntityValidationTests.java | 35 +++++++------------ .../test/resources/role_schema.cedarschema | 2 +- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/CedarJava/src/test/java/com/cedarpolicy/EntityValidationTests.java b/CedarJava/src/test/java/com/cedarpolicy/EntityValidationTests.java index 00bd159..5686742 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/EntityValidationTests.java +++ b/CedarJava/src/test/java/com/cedarpolicy/EntityValidationTests.java @@ -56,8 +56,7 @@ public void testValidEntity() throws AuthException { engine.validateEntities(request); - EntityValidationRequest cedarFormatRequest = - new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(entity)); + EntityValidationRequest cedarFormatRequest = new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(entity)); engine.validateEntities(cedarFormatRequest); } @@ -80,16 +79,13 @@ public void testEntityWithUnknownAttribute() throws AuthException { "attribute `test` on `Role::\".*\"` should not exist according to the schema"), "Expected to match regex but was: '%s'".formatted(errMsg)); - EntityValidationRequest cedarFormatRequest = - new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(entity)); + EntityValidationRequest cedarFormatRequest = new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(entity)); - exception = assertThrows(BadRequestException.class, - () -> engine.validateEntities(cedarFormatRequest)); + exception = assertThrows(BadRequestException.class, () -> engine.validateEntities(cedarFormatRequest)); errMsg = exception.getErrors().get(0); - assertTrue(errMsg.matches( - "attribute `test` on `Role::\".*\"` should not exist according to the schema"), - "Expected to match regex but was: '%s'".formatted(errMsg)); + assertTrue(errMsg.matches("attribute `test` on `Role::\".*\"` should not exist according to the schema"), + "Expected to match regex but was: '%s'".formatted(errMsg)); } @@ -117,14 +113,13 @@ public void testEntitiesWithCyclicParentRelationship() throws AuthException { "Expected to match regex but was: '%s'".formatted(errMsg)); EntityValidationRequest cedarFormatRequest = - new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(parentEntity, childEntity)); + new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(parentEntity, childEntity)); - exception = assertThrows(BadRequestException.class, - () -> engine.validateEntities(cedarFormatRequest)); + exception = assertThrows(BadRequestException.class, () -> engine.validateEntities(cedarFormatRequest)); errMsg = exception.getErrors().get(0); assertTrue(errMsg.matches("input graph has a cycle containing vertex `Role::\".*\"`"), - "Expected to match regex but was: '%s'".formatted(errMsg)); + "Expected to match regex but was: '%s'".formatted(errMsg)); } @@ -147,17 +142,14 @@ public void testEntityWithUnknownTag() throws AuthException { + "but no tags should exist on `Role::\".*\"` according to the schema"), "Expected to match regex but was: '%s'".formatted(errMsg)); - EntityValidationRequest cedarFormatRequest = - new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(entity)); + EntityValidationRequest cedarFormatRequest = new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(entity)); - exception = assertThrows(BadRequestException.class, - () -> engine.validateEntities(cedarFormatRequest)); + exception = assertThrows(BadRequestException.class, () -> engine.validateEntities(cedarFormatRequest)); errMsg = exception.getErrors().get(0); - assertTrue( - errMsg.matches("found a tag `test` on `Role::\".*\"`, " + assertTrue(errMsg.matches("found a tag `test` on `Role::\".*\"`, " + "but no tags should exist on `Role::\".*\"` according to the schema"), - "Expected to match regex but was: '%s'".formatted(errMsg)); + "Expected to match regex but was: '%s'".formatted(errMsg)); } @BeforeAll @@ -170,6 +162,5 @@ public static void setUp() { } private static final Schema ROLE_SCHEMA = loadSchemaResource("/role_schema.json"); - private static final Schema ROLE_SCHEMA_CEDAR = - loadCedarSchemaResource("/role_schema.cedarschema"); + private static final Schema ROLE_SCHEMA_CEDAR = loadCedarSchemaResource("/role_schema.cedarschema"); } diff --git a/CedarJava/src/test/resources/role_schema.cedarschema b/CedarJava/src/test/resources/role_schema.cedarschema index 422226a..a8f9fa7 100644 --- a/CedarJava/src/test/resources/role_schema.cedarschema +++ b/CedarJava/src/test/resources/role_schema.cedarschema @@ -1 +1 @@ -entity Role in [Role]; \ No newline at end of file +entity Role in [Role]; From 55bd18d87a90293d05bca7fe195dafb72e1256c3 Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Wed, 27 Aug 2025 10:13:24 -0400 Subject: [PATCH 4/5] updates CHANGELOG Signed-off-by: Mudit Chaudhary --- CedarJava/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CedarJava/CHANGELOG.md b/CedarJava/CHANGELOG.md index 72fb7fd..283fbd2 100644 --- a/CedarJava/CHANGELOG.md +++ b/CedarJava/CHANGELOG.md @@ -4,6 +4,10 @@ * Added Schema conversion APIs [#325](https://github.com/cedar-policy/cedar-java/pull/325) * Added Level Validation [#327](https://github.com/cedar-policy/cedar-java/pull/327) * Added DateTime extension support [#328](https://github.com/cedar-policy/cedar-java/pull/328) +* Added Duration extension support [#331](https://github.com/cedar-policy/cedar-java/pull/331) +* Added Offset function support [#331](https://github.com/cedar-policy/cedar-java/pull/331) +* Added PolicySet to JSON conversion API [#329](https://github.com/cedar-policy/cedar-java/pull/329) +* Added Cedar Schema support for Entity Validation [#332](https://github.com/cedar-policy/cedar-java/pull/332) ## 4.3.1 ### Added From 70daa6347d26fe7b8c5d04a736c4597effd09cc5 Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Fri, 29 Aug 2025 07:07:41 -0400 Subject: [PATCH 5/5] separates Cedar Schema entity validation tests Signed-off-by: Mudit Chaudhary --- .../cedarpolicy/EntityValidationTests.java | 59 ++++++++++++++++--- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/CedarJava/src/test/java/com/cedarpolicy/EntityValidationTests.java b/CedarJava/src/test/java/com/cedarpolicy/EntityValidationTests.java index 5686742..b5a9a35 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/EntityValidationTests.java +++ b/CedarJava/src/test/java/com/cedarpolicy/EntityValidationTests.java @@ -53,9 +53,15 @@ public void testValidEntity() throws AuthException { Entity entity = EntityValidationTests.entityGen.arbitraryEntity(); EntityValidationRequest request = new EntityValidationRequest(ROLE_SCHEMA, List.of(entity)); - engine.validateEntities(request); +} + /** + * Test that a valid entity with the schema in Cedar format is accepted. + */ + @Test + public void testValidEntityWithCedarSchema() throws AuthException { + Entity entity = EntityValidationTests.entityGen.arbitraryEntity(); EntityValidationRequest cedarFormatRequest = new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(entity)); engine.validateEntities(cedarFormatRequest); @@ -78,15 +84,25 @@ public void testEntityWithUnknownAttribute() throws AuthException { assertTrue(errMsg.matches( "attribute `test` on `Role::\".*\"` should not exist according to the schema"), "Expected to match regex but was: '%s'".formatted(errMsg)); +} + +/** + * Test that an entity with an attribute not specified in the schema in Cedar format throws an + * exception. + */ +@Test +public void testEntityWithUnknownAttributeWithCedarSchema() throws AuthException { + Entity entity = EntityValidationTests.entityGen.arbitraryEntity(); + entity.attrs.put("test", new PrimBool(true)); EntityValidationRequest cedarFormatRequest = new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(entity)); - exception = assertThrows(BadRequestException.class, () -> engine.validateEntities(cedarFormatRequest)); + BadRequestException exception = + assertThrows(BadRequestException.class, () -> engine.validateEntities(cedarFormatRequest)); - errMsg = exception.getErrors().get(0); + String errMsg = exception.getErrors().get(0); assertTrue(errMsg.matches("attribute `test` on `Role::\".*\"` should not exist according to the schema"), "Expected to match regex but was: '%s'".formatted(errMsg)); - } /** @@ -111,16 +127,31 @@ public void testEntitiesWithCyclicParentRelationship() throws AuthException { String errMsg = exception.getErrors().get(0); assertTrue(errMsg.matches("input graph has a cycle containing vertex `Role::\".*\"`"), "Expected to match regex but was: '%s'".formatted(errMsg)); +} + +/** + * Test that entities with a cyclic parent relationship throw an exception with the schema in Cedar + * format. + */ +@Test +public void testEntitiesWithCyclicParentRelationshipWithCedarSchema() throws AuthException { + // Arrange + Entity childEntity = EntityValidationTests.entityGen.arbitraryEntity(); + Entity parentEntity = EntityValidationTests.entityGen.arbitraryEntity(); + + // Create a cyclic parent relationship between the entities + childEntity.parentsEUIDs.add(parentEntity.getEUID()); + parentEntity.parentsEUIDs.add(childEntity.getEUID()); EntityValidationRequest cedarFormatRequest = new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(parentEntity, childEntity)); - exception = assertThrows(BadRequestException.class, () -> engine.validateEntities(cedarFormatRequest)); + BadRequestException exception = + assertThrows(BadRequestException.class, () -> engine.validateEntities(cedarFormatRequest)); - errMsg = exception.getErrors().get(0); + String errMsg = exception.getErrors().get(0); assertTrue(errMsg.matches("input graph has a cycle containing vertex `Role::\".*\"`"), "Expected to match regex but was: '%s'".formatted(errMsg)); - } /** @@ -141,12 +172,22 @@ public void testEntityWithUnknownTag() throws AuthException { errMsg.matches("found a tag `test` on `Role::\".*\"`, " + "but no tags should exist on `Role::\".*\"` according to the schema"), "Expected to match regex but was: '%s'".formatted(errMsg)); +} + +/** + * Test that an entity with a tag not specified in the schema in Cedar format throws an exception. + */ +@Test +public void testEntityWithUnknownTagWithCedarSchema() throws AuthException { + Entity entity = EntityValidationTests.entityGen.arbitraryEntity(); + entity.tags.put("test", new PrimString("value")); EntityValidationRequest cedarFormatRequest = new EntityValidationRequest(ROLE_SCHEMA_CEDAR, List.of(entity)); - exception = assertThrows(BadRequestException.class, () -> engine.validateEntities(cedarFormatRequest)); + BadRequestException exception = + assertThrows(BadRequestException.class, () -> engine.validateEntities(cedarFormatRequest)); - errMsg = exception.getErrors().get(0); + String errMsg = exception.getErrors().get(0); assertTrue(errMsg.matches("found a tag `test` on `Role::\".*\"`, " + "but no tags should exist on `Role::\".*\"` according to the schema"), "Expected to match regex but was: '%s'".formatted(errMsg));