Skip to content

Commit

Permalink
update for request validation (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
cdisselkoen authored Nov 8, 2023
1 parent 1918739 commit 12fbc53
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ private static final class AuthorizationRequest extends com.cedarpolicy.model.Au
request.actionEUID,
request.resourceEUID,
request.context,
request.schema);
request.schema,
request.enable_request_validation);
this.slice = slice;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@
* determines if the policies allow for the given principal to perform the given action against the
* given resource.
*
* <p>An optional schema can be provided, but will not be used for validation unless you call
* validate(). The schema is provided to allow parsing Entities from JSON without escape sequences
* (in general, you don't need to worry about this if you construct your entities via the EntityUID
* class).
* <p>If the (optional) schema is provided, this will inform parsing the
* `context` from JSON: for instance, it will allow `__entity` and `__extn`
* escapes to be implicit, and it will error if attributes have the wrong types
* (e.g., string instead of integer).
* If the schema is provided and `enable_request_validation` is true, then the
* schema will also be used for request validation.
*/
public class AuthorizationRequest {
/** EUID of the principal in the request. */
@JsonProperty("principal")
public final Optional<EntityUID> principalEUID;
public final Optional<EntityUID> principalEUID;
/** EUID of the action in the request. */
@JsonProperty("action")
public final EntityUID actionEUID;
Expand All @@ -50,9 +52,17 @@ public class AuthorizationRequest {
/** Key/Value map representing the context of the request. */
public final Optional<Map<String, Value>> context;

/** JSON object representing the Schema. */
/** JSON object representing the Schema. Used for schema-based parsing of
* `context`, and also (if `enable_request_validation` is `true`) for
* request validation. */
public final Optional<Schema> schema;

/** If this is `true` and a schema is provided, perform request validation.
* If this is `false`, the schema will only be used for schema-based parsing
* of `context`, and not for request validation.
* If a schema is not provided, this option has no effect. */
public final boolean enable_request_validation;

/**
* Create an authorization request from the EUIDs and Context.
*
Expand All @@ -61,13 +71,17 @@ public class AuthorizationRequest {
* @param resourceEUID Resource's EUID.
* @param context Key/Value context.
* @param schema Schema (optional).
* @param enable_request_validation Whether to use the schema for just
* schema-based parsing of `context` (false) or also for request validation
* (true). No effect if `schema` is not provided.
*/
public AuthorizationRequest(
Optional<EntityUID> principalEUID,
EntityUID actionEUID,
Optional<EntityUID> resourceEUID,
Optional<Map<String, Value>> context,
Optional<Schema> schema) {
Optional<Schema> schema,
boolean enable_request_validation) {
this.principalEUID = principalEUID;
this.actionEUID = actionEUID;
this.resourceEUID = resourceEUID;
Expand All @@ -77,10 +91,11 @@ public AuthorizationRequest(
this.context = Optional.of(new HashMap<>(context.get()));
}
this.schema = schema;
this.enable_request_validation = enable_request_validation;
}

/**
* Create a request in the empty context.
* Create a request without a schema.
*
* @param principalEUID Principal's EUID.
* @param actionEUID Action's EUID.
Expand All @@ -93,11 +108,12 @@ public AuthorizationRequest(EntityUID principalEUID, EntityUID actionEUID, Entit
actionEUID,
Optional.of(resourceEUID),
Optional.of(context),
Optional.empty());
Optional.empty(),
false);
}

/**
* Create a request without a schema.
* Create a request without a schema, using Entity objects for principal/action/resource.
*
* @param principalEUID Principal's EUID.
* @param actionEUID Action's EUID.
Expand All @@ -106,20 +122,32 @@ public AuthorizationRequest(EntityUID principalEUID, EntityUID actionEUID, Entit
*/
public AuthorizationRequest(Entity principalEUID, Entity actionEUID, Entity resourceEUID, Map<String, Value> context) {
this(
Optional.of(principalEUID.getEUID()),
principalEUID.getEUID(),
actionEUID.getEUID(),
Optional.of(resourceEUID.getEUID()),
Optional.of(context),
Optional.empty());
resourceEUID.getEUID(),
context);
}

public AuthorizationRequest(Optional<Entity> principal, Entity action, Optional<Entity> resource, Optional<Map<String, Value>> context, Optional<Schema> schema) {
/**
* Create a request from Entity objects and Context.
*
* @param principal
* @param action
* @param resource
* @param context
* @param schema
* @param enable_request_validation Whether to use the schema for just
* schema-based parsing of `context` (false) or also for request validation
* (true). No effect if `schema` is not provided.
*/
public AuthorizationRequest(Optional<Entity> principal, Entity action, Optional<Entity> resource, Optional<Map<String, Value>> context, Optional<Schema> schema, boolean enable_request_validation) {
this(
principal.map(e -> e.getEUID()),
action.getEUID(),
resource.map(e -> e.getEUID()),
context,
schema
schema,
enable_request_validation
);
}

Expand Down
5 changes: 3 additions & 2 deletions CedarJava/src/test/java/com/cedarpolicy/JSONTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import com.fasterxml.jackson.core.exc.StreamConstraintsException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
Expand Down Expand Up @@ -78,9 +79,9 @@ public void testRequest() {
var moria = new EntityUID(EntityTypeName.parse("Mines").get(), "moria");
AuthorizationRequest q = new AuthorizationRequest(gandalf, opens, moria, new HashMap<String, Value>());
ObjectNode n = JsonNodeFactory.instance.objectNode();
ObjectNode c = JsonNodeFactory.instance.objectNode();
n.set("context", c);
n.set("context", JsonNodeFactory.instance.objectNode());
n.set("schema", JsonNodeFactory.instance.nullNode());
n.set("enable_request_validation", JsonNodeFactory.instance.booleanNode(false));
n.set("principal", buildEuidObject("Wizard", "gandalf"));
n.set("action", buildEuidObject("Action", "opens"));
n.set("resource", buildEuidObject("Mines", "moria"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ private static class JsonRequest {
/** Context map used for the request. */
public Map<String, Value> context;

/** Whether to enable request validation for this request. Default true */
public boolean enable_request_validation = true;

/** The expected decision that should be returned by the authorization engine. */
public AuthorizationResponse.Decision decision;

Expand Down Expand Up @@ -294,7 +297,7 @@ private Set<Policy> loadPolicies(String policiesFile) throws IOException {
String policiesSrc = String.join("\n", Files.readAllLines(resolveIntegrationTestPath(policiesFile)));

// Get a list of the policy sources for the individual policies in the
// file by splitting the full policy source on semicolons. This will
// file by splitting the full policy source on semicolons. This will
// break if a semicolon shows up in a string, eid, or comment.
String[] policyStrings = policiesSrc.split(";");
// Some of the corpus tests contain semicolons in strings and/or eids.
Expand All @@ -305,7 +308,7 @@ private Set<Policy> loadPolicies(String policiesFile) throws IOException {
policyStrings = null;
}
}

Set<Policy> policies = new HashSet<>();
if (policyStrings == null) {
// This case will only be reached for corpus tests.
Expand Down Expand Up @@ -408,10 +411,11 @@ private void executeJsonRequestTest(
request.principal == null ? Optional.empty() : Optional.of(EntityUID.parseFromJson(request.principal).get()),
EntityUID.parseFromJson(request.action).get(),
request.resource == null ? Optional.empty() : Optional.of(EntityUID.parseFromJson(request.resource).get()),
Optional.of(request.context),
Optional.of(schema));
Optional.of(request.context),
Optional.of(schema),
request.enable_request_validation);
Slice slice = new BasicSlice(policies, entities);

try {
AuthorizationResponse response = auth.isAuthorized(authRequest, slice);
System.out.println(response.getErrors());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@

/** Integration tests. */
public class IntegrationTests {

final EntityTypeName principalType;
final EntityTypeName actionType;
final EntityTypeName resourceType;
Expand Down Expand Up @@ -368,11 +368,10 @@ public void testUnspecifiedResource() {
Map<String, Value> currentContext = new HashMap<>();
AuthorizationRequest request =
new AuthorizationRequest(
Optional.of(principal),
principal,
action,
Optional.of(resource),
Optional.of(currentContext),
Optional.empty());
resource,
currentContext);
AuthorizationEngine authEngine = new BasicAuthorizationEngine();
AuthorizationResponse response =
Assertions.assertDoesNotThrow(() -> authEngine.isAuthorized(request, slice));
Expand Down Expand Up @@ -599,7 +598,7 @@ public void testSchemaParsingDeny() {
Set<Policy> policies = new HashSet<>();
policies.add(policy);

// Schema says resource.owner is a bool, so we should get a parse failure, which causes
// Schema says resource.owner is a bool, so we should get a parse failure, which causes
// `isAuthorized()` to throw a `BadRequestException`.
Slice slice = new BasicSlice(policies, entities);
Map<String, Value> currentContext = new HashMap<>();
Expand All @@ -609,7 +608,8 @@ public void testSchemaParsingDeny() {
action,
Optional.of(resource),
Optional.of(currentContext),
Optional.of(loadSchemaResource("/schema_parsing_deny_schema.json")));
Optional.of(loadSchemaResource("/schema_parsing_deny_schema.json")),
true);
AuthorizationEngine authEngine = new BasicAuthorizationEngine();
Assertions.assertThrows(BadRequestException.class, () -> authEngine.isAuthorized(request, slice));
}
Expand Down Expand Up @@ -668,7 +668,8 @@ public void testSchemaParsingAllow() {
action,
Optional.of(resource),
Optional.of(currentContext),
Optional.of(loadSchemaResource("/schema_parsing_allow_schema.json")));
Optional.of(loadSchemaResource("/schema_parsing_allow_schema.json")),
true);
AuthorizationEngine authEngine = new BasicAuthorizationEngine();
AuthorizationResponse response =
Assertions.assertDoesNotThrow(() -> authEngine.isAuthorized(request, slice));
Expand Down

0 comments on commit 12fbc53

Please sign in to comment.