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

Adding support for entity tags #238

Merged
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
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