Skip to content

Commit

Permalink
Adding tags support (#239)
Browse files Browse the repository at this point in the history
  • Loading branch information
mark-creamer-amazon authored Oct 16, 2024
1 parent e8ad511 commit 0acff8a
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 7 deletions.
40 changes: 36 additions & 4 deletions CedarJava/src/main/java/com/cedarpolicy/model/entity/Entity.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
/**
* An entity is the kind of object about which authorization decisions are made; principals,
* actions, and resources are all a kind of entity. Each entity is defined by its entity type, a
* unique identifier (UID), zero or more attributes mapped to values, and zero or more parent
* entities.
* unique identifier (UID), zero or more attributes mapped to values, zero or more parent
* entities, and zero or more tags.
*/
public class Entity {
private final EntityUID euid;
Expand All @@ -37,6 +37,9 @@ public class Entity {
/** Set of entity EUIDs that are parents to this entity. */
public final Set<EntityUID> parentsEUIDs;

/** Tags on this entity (RFC 82) */
public final Map<String, Value> tags;

/**
* Create an entity from an EntityUIDs, a map of attributes, and a set of parent EntityUIDs.
*
Expand All @@ -45,9 +48,22 @@ public class Entity {
* @param parentsEUIDs Set of parent entities' EUIDs.
*/
public Entity(EntityUID uid, Map<String, Value> attributes, Set<EntityUID> parentsEUIDs) {
this(uid, attributes, parentsEUIDs, new HashMap<>());
}

/**
* Create an entity from an EntityUIDs, a map of attributes, a set of parent EntityUIDs, and a map of tags.
*
* @param uid EUID of the Entity.
* @param attributes Key/Value map of attributes.
* @param parentsEUIDs Set of parent entities' EUIDs.
* @param tags Key/Value map of tags.
*/
public Entity(EntityUID uid, Map<String, Value> attributes, Set<EntityUID> parentsEUIDs, Map<String, Value> tags) {
this.attrs = new HashMap<>(attributes);
this.euid = uid;
this.parentsEUIDs = parentsEUIDs;
this.tags = new HashMap<>(tags);
}

@Override
Expand All @@ -66,7 +82,15 @@ public String toString() {
.map(e -> e.getKey() + ": " + e.getValue())
.collect(Collectors.joining("\n\t\t"));
}
return euid.toString() + parentStr + attributeStr;
String tagsStr = "";
if (!tags.isEmpty()) {
tagsStr =
"\n\ttags:\n\t\t"
+ tags.entrySet().stream()
.map(e -> e.getKey() + ": " + e.getValue())
.collect(Collectors.joining("\n\t\t"));
}
return euid.toString() + parentStr + attributeStr + tagsStr;
}


Expand All @@ -79,10 +103,18 @@ public EntityUID getEUID() {
}

/**
* Get this Entities parents
* Get this Entity's parents
* @return the set of parent EntityUIDs
*/
public Set<EntityUID> getParents() {
return parentsEUIDs;
}

/**
* Get this Entity's tags
* @return the map of tags
*/
public Map<String, Value> getTags() {
return tags;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public void serialize(
jsonGenerator.writeObjectField("attrs", entity.attrs);
jsonGenerator.writeObjectField("parents",
entity.getParents().stream().map(EntityUID::asJson).collect(Collectors.toSet()));
jsonGenerator.writeObjectField("tags", entity.tags);
jsonGenerator.writeEndObject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import com.cedarpolicy.pbt.EntityGen;
import com.cedarpolicy.value.EntityTypeName;
import com.cedarpolicy.value.PrimBool;

import com.cedarpolicy.value.PrimString;

/**
* Tests for entity validator
Expand Down Expand Up @@ -96,6 +96,24 @@ public void testEntitiesWithCyclicParentRelationship() throws AuthException {
"Expected to match regex but was: '%s'".formatted(errMsg));
}

/**
* Test that an entity with a tag not specified in the schema throws an exception.
*/
@Test
public void testEntityWithUnknownTag() throws AuthException {
Entity entity = EntityValidationTests.entityGen.arbitraryEntity();
entity.tags.put("test", new PrimString("value"));

EntityValidationRequest request = new EntityValidationRequest(ROLE_SCHEMA, List.of(entity));

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));
}

@BeforeAll
public static void setUp() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import com.cedarpolicy.model.AuthorizationResponse;
import com.cedarpolicy.model.ValidationRequest;
import com.cedarpolicy.model.ValidationResponse;
import com.cedarpolicy.model.ValidationResponse.ValidationError;
import com.cedarpolicy.model.ValidationResponse.ValidationSuccessResponse;
import com.cedarpolicy.model.AuthorizationSuccessResponse.Decision;
import com.cedarpolicy.model.exception.AuthException;
import com.cedarpolicy.model.exception.BadRequestException;
Expand All @@ -51,6 +53,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -173,6 +176,12 @@ private static class JsonEntity {
value = "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD",
justification = "Initialized by Jackson.")
public List<JsonEUID> parents;

/** Entity tags, where the value string is a Cedar literal value. */
@SuppressFBWarnings(
value = "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD",
justification = "Initialized by Jackson.")
public Map<String, Value> tags;
}

/**
Expand Down Expand Up @@ -296,8 +305,10 @@ private Entity loadEntity(JsonEntity je) {
.map(euid -> EntityUID.parseFromJson(euid).get())
.collect(Collectors.toSet());

// Support tags while also supporting old JsonEntity objects that don't specify tags
Map<String, Value> tags = je.tags != null ? je.tags : new HashMap<>();

return new Entity(EntityUID.parseFromJson(je.uid).get(), je.attrs, parents);
return new Entity(EntityUID.parseFromJson(je.uid).get(), je.attrs, parents, tags);
}

/**
Expand Down Expand Up @@ -326,7 +337,13 @@ private void executeJsonValidationTest(PolicySet policies, Schema schema, Boolea
ValidationResponse result = auth.validate(validationQuery);
assertEquals(result.type, ValidationResponse.SuccessOrFailure.Success);
if (shouldValidate) {
assertTrue(result.validationPassed());
ValidationSuccessResponse validationSuccessResponse = result.success.get();

// Assemble the validation failure messages, if any
List<ValidationError> valErrList = List.copyOf(validationSuccessResponse.validationErrors);
String validationErrorMessages = valErrList.stream().map(e -> e.getError().message).collect(Collectors.joining(", "));

assertTrue(result.validationPassed(), validationErrorMessages);
}
} catch (BadRequestException e) {
// A `BadRequestException` is the results of a parsing error.
Expand Down

0 comments on commit 0acff8a

Please sign in to comment.