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
2 changes: 1 addition & 1 deletion CedarJava/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
.factorypath
.project
.settings/

bin/
# Ignore changes to gradle.properties because we enter passwords here for releases
/gradle.properties
6 changes: 4 additions & 2 deletions CedarJava/src/main/java/com/cedarpolicy/CedarJson.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,23 @@
import com.cedarpolicy.serializer.SchemaSerializer;
import com.cedarpolicy.serializer.ValueDeserializer;
import com.cedarpolicy.serializer.ValueSerializer;
import com.cedarpolicy.serializer.EntityDeserializer;
import com.cedarpolicy.value.Value;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;

final class CedarJson {
public final class CedarJson {
private static final ObjectMapper OBJECT_MAPPER = createObjectMapper();

private CedarJson() {
throw new IllegalStateException("Utility class");
}

public static ObjectMapper objectMapper() {
return OBJECT_MAPPER;
return OBJECT_MAPPER.copy();
}

public static ObjectWriter objectWriter() {
Expand All @@ -62,6 +63,7 @@ private static ObjectMapper createObjectMapper() {
module.addSerializer(PolicySet.class, new PolicySetSerializer());
module.addSerializer(Value.class, new ValueSerializer());
module.addDeserializer(Value.class, new ValueDeserializer());
module.addDeserializer(Entity.class, new EntityDeserializer());
mapper.registerModule(module);
mapper.registerModule(new Jdk8Module());

Expand Down
76 changes: 52 additions & 24 deletions CedarJava/src/main/java/com/cedarpolicy/model/entity/Entity.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,20 @@

import com.cedarpolicy.value.EntityUID;
import com.cedarpolicy.value.Value;
import static com.cedarpolicy.CedarJson.objectReader;

import com.fasterxml.jackson.core.JsonProcessingException;

import java.io.IOException;
import java.nio.file.Files;
import java.util.*;
import java.util.stream.Collectors;
import java.nio.file.Path;

/**
* 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, zero or more parent
* entities, and zero or more tags.
* 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, zero or more parent entities, and zero or more tags.
*/
public class Entity {
private final EntityUID euid;
Expand All @@ -52,7 +57,7 @@ public Entity(EntityUID uid) {
/**
* Create an entity from an EntityUID and a set of parent EntityUIDs. It will have no attributes or tags.
*
* @param uid EUID of the Entity.
* @param uid EUID of the Entity.
* @param parentsEUIDs Set of parent entities' EUIDs.
*/
public Entity(EntityUID uid, Set<EntityUID> parentsEUIDs) {
Expand All @@ -62,8 +67,8 @@ public Entity(EntityUID uid, Set<EntityUID> parentsEUIDs) {
/**
* Create an entity from an EntityUIDs, a map of attributes, and a set of parent EntityUIDs.
*
* @param uid EUID of the Entity.
* @param attributes Key/Value map of attributes.
* @param uid EUID of the Entity.
* @param attributes Key/Value map of attributes.
* @param parentsEUIDs Set of parent entities' EUIDs.
*/
public Entity(EntityUID uid, Map<String, Value> attributes, Set<EntityUID> parentsEUIDs) {
Expand All @@ -73,10 +78,10 @@ public Entity(EntityUID uid, Map<String, Value> attributes, Set<EntityUID> paren
/**
* 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 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.
* @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);
Expand All @@ -87,8 +92,9 @@ public Entity(EntityUID uid, Map<String, Value> attributes, Set<EntityUID> paren

/**
* Get the value for the given attribute, or null if not present.
*
*
* @param attribute Attribute key
*
* @return Attribute value for the given key or null if not present
* @throws IllegalArgumentException if attribute is null
*/
Expand All @@ -103,32 +109,26 @@ public Value getAttr(String attribute) {
public String toString() {
String parentStr = "";
if (!parentsEUIDs.isEmpty()) {
List<String> parentStrs = new ArrayList<String>(parentsEUIDs.stream()
.map(euid -> euid.toString()).collect(Collectors.toList()));
List<String> parentStrs = new ArrayList<String>(
parentsEUIDs.stream().map(euid -> euid.toString()).collect(Collectors.toList()));
parentStr = "\n\tparents:\n\t\t" + String.join("\n\t\t", parentStrs);
}
String attributeStr = "";
if (!attrs.isEmpty()) {
attributeStr =
"\n\tattrs:\n\t\t"
+ attrs.entrySet().stream()
.map(e -> e.getKey() + ": " + e.getValue())
.collect(Collectors.joining("\n\t\t"));
attributeStr = "\n\tattrs:\n\t\t" + attrs.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue())
.collect(Collectors.joining("\n\t\t"));
}
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"));
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;
}


/**
* Get the entity uid
*
* @return Entity UID
*/
public EntityUID getEUID() {
Expand All @@ -137,6 +137,7 @@ public EntityUID getEUID() {

/**
* Get this Entity's parents
*
* @return the set of parent EntityUIDs
*/
public Set<EntityUID> getParents() {
Expand All @@ -145,9 +146,36 @@ public Set<EntityUID> getParents() {

/**
* Get this Entity's tags
*
* @return the map of tags
*/
public Map<String, Value> getTags() {
return tags;
}

/**
* Parse Entity from a JSON string
*
* @param jsonString The JSON string representation of an Entity
*
* @return Entity object parsed from the JSON string
* @throws JsonProcessingException if the JSON string cannot be parsed into an Entity
*/
public static Entity parse(String jsonString) throws JsonProcessingException {
return objectReader().forType(Entity.class).readValue(jsonString);
}

/**
* Parse Entity from a file containing JSON representation of an Entity
*
* @param filePath Path to the file containing Entity JSON
*
* @return Entity object parsed from the file contents
* @throws IOException if there is an error reading the file
* @throws JsonProcessingException if the file contents cannot be parsed into an Entity
*/
public static Entity parse(Path filePath) throws IOException, JsonProcessingException {
String jsonString = Files.readString(filePath);
return parse(jsonString);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Copyright Cedar Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.cedarpolicy.serializer;

import com.cedarpolicy.model.exception.InvalidValueDeserializationException;
import com.cedarpolicy.value.Value;
import com.cedarpolicy.value.EntityUID;
import com.cedarpolicy.model.entity.Entity;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
* Deserialize Json to Entity.
*/
public class EntityDeserializer extends JsonDeserializer<Entity> {

/**
* Deserializes a JSON input into an Entity object.
*
* @param parser The JsonParser providing the JSON input to deserialize
* @param context The deserialization context
*
* @return An Entity object constructed from the JSON input
* @throws IOException If there is an error reading from the JsonParser
* @throws InvalidValueDeserializationException If the JSON input is invalid or missing required fields
*/
@Override
public Entity deserialize(JsonParser parser, DeserializationContext context)
throws IOException, InvalidValueDeserializationException {
final JsonNode node = parser.getCodec().readTree(parser);
final ObjectMapper mapper = (ObjectMapper) parser.getCodec();

EntityUID euid;
if (node.has("uid")) {
JsonNode uidNode = node.get("uid");
euid = parseEntityUID(parser, uidNode);
} else {
String msg = "\"uid\" not found";
throw new InvalidValueDeserializationException(parser, msg, node.asToken(), Entity.class);
}

Map<String, Value> attrs;
if (node.has("attrs")) {
JsonNode attrsNode = node.get("attrs");
if (attrsNode.isObject()) {
attrs = parseValueMap(mapper, attrsNode);
} else {
String msg = "\"attrs\" must be a JSON object";
throw new InvalidValueDeserializationException(parser, msg, attrsNode.asToken(), Entity.class);
}
} else {
String msg = "\"attrs\" not found";
throw new InvalidValueDeserializationException(parser, msg, node.asToken(), Entity.class);
}

Set<EntityUID> parentEUIDs;
if (node.has("parents")) {
JsonNode parentsNode = node.get("parents");
if (parentsNode.isArray()) {
parentEUIDs = StreamSupport.stream(parentsNode.spliterator(), false).map(parentNode -> {
try {
return parseEntityUID(parser, parentNode);
} catch (InvalidValueDeserializationException e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toSet());
} else {
String msg = "\"parents\" field must be a JSON array";
throw new InvalidValueDeserializationException(parser, msg, parentsNode.asToken(), Entity.class);
}
} else {
String msg = "\"parents\" not found";
throw new InvalidValueDeserializationException(parser, msg, node.asToken(), Entity.class);
}

Map<String, Value> tags = new HashMap<>();
if (node.has("tags")) {
JsonNode tagsNode = node.get("tags");
if (tagsNode.isObject()) {
tags.putAll(parseValueMap(mapper, tagsNode));
} else {
String msg = "\"tags\" must be a JSON object";
throw new InvalidValueDeserializationException(parser, msg, tagsNode.asToken(), Entity.class);
}
}
return new Entity(euid, attrs, parentEUIDs, tags);
}

/**
* Parses a JSON node into an EntityUID object.
*
* @param parser The JsonParser used for error reporting
* @param entityUIDJson The JsonNode containing the entity UID data to parse. Must have "type" and "id" fields.
*
* @return An EntityUID object constructed from the JSON data
* @throws InvalidValueDeserializationException if the required fields are missing or invalid
*/
private EntityUID parseEntityUID(JsonParser parser, JsonNode entityUIDJson)
throws InvalidValueDeserializationException {
if (entityUIDJson.has("type") && entityUIDJson.has("id")) {
JsonEUID jsonEuid = new JsonEUID(entityUIDJson.get("type").asText(), entityUIDJson.get("id").asText());
return EntityUID.parseFromJson(jsonEuid).get();
} else {
String msg = "\"type\" or \"id\" not found";
throw new InvalidValueDeserializationException(parser, msg, entityUIDJson.asToken(), Entity.class);
}
}

/**
* Parses a JSON node containing key-value pairs into a Map of String to Value objects.
*
* @param mapper The ObjectMapper used to convert JSON nodes to Value objects
* @param valueMapJson The JsonNode containing the key-value pairs to parse
*
* @return A Map where keys are Strings and values are Value objects
* @throws RuntimeException if there is an error converting a JSON node to a Value
*/
private Map<String, Value> parseValueMap(ObjectMapper mapper, JsonNode valueMapJson) {
Map<String, Value> valueMap = StreamSupport
.stream(Spliterators.spliteratorUnknownSize(valueMapJson.fields(), Spliterator.ORDERED), false)
.collect(Collectors.toMap(Map.Entry::getKey, entry -> {
try {
return mapper.treeToValue(entry.getValue(), Value.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}));
return valueMap;
}
}
Loading