Skip to content

Commit 12fbc53

Browse files
authored
update for request validation (#66)
1 parent 1918739 commit 12fbc53

5 files changed

Lines changed: 67 additions & 32 deletions

File tree

CedarJava/src/main/java/com/cedarpolicy/BasicAuthorizationEngine.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ private static final class AuthorizationRequest extends com.cedarpolicy.model.Au
113113
request.actionEUID,
114114
request.resourceEUID,
115115
request.context,
116-
request.schema);
116+
request.schema,
117+
request.enable_request_validation);
117118
this.slice = slice;
118119
}
119120
}

CedarJava/src/main/java/com/cedarpolicy/model/AuthorizationRequest.java

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,17 @@
3131
* determines if the policies allow for the given principal to perform the given action against the
3232
* given resource.
3333
*
34-
* <p>An optional schema can be provided, but will not be used for validation unless you call
35-
* validate(). The schema is provided to allow parsing Entities from JSON without escape sequences
36-
* (in general, you don't need to worry about this if you construct your entities via the EntityUID
37-
* class).
34+
* <p>If the (optional) schema is provided, this will inform parsing the
35+
* `context` from JSON: for instance, it will allow `__entity` and `__extn`
36+
* escapes to be implicit, and it will error if attributes have the wrong types
37+
* (e.g., string instead of integer).
38+
* If the schema is provided and `enable_request_validation` is true, then the
39+
* schema will also be used for request validation.
3840
*/
3941
public class AuthorizationRequest {
4042
/** EUID of the principal in the request. */
4143
@JsonProperty("principal")
42-
public final Optional<EntityUID> principalEUID;
44+
public final Optional<EntityUID> principalEUID;
4345
/** EUID of the action in the request. */
4446
@JsonProperty("action")
4547
public final EntityUID actionEUID;
@@ -50,9 +52,17 @@ public class AuthorizationRequest {
5052
/** Key/Value map representing the context of the request. */
5153
public final Optional<Map<String, Value>> context;
5254

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

60+
/** If this is `true` and a schema is provided, perform request validation.
61+
* If this is `false`, the schema will only be used for schema-based parsing
62+
* of `context`, and not for request validation.
63+
* If a schema is not provided, this option has no effect. */
64+
public final boolean enable_request_validation;
65+
5666
/**
5767
* Create an authorization request from the EUIDs and Context.
5868
*
@@ -61,13 +71,17 @@ public class AuthorizationRequest {
6171
* @param resourceEUID Resource's EUID.
6272
* @param context Key/Value context.
6373
* @param schema Schema (optional).
74+
* @param enable_request_validation Whether to use the schema for just
75+
* schema-based parsing of `context` (false) or also for request validation
76+
* (true). No effect if `schema` is not provided.
6477
*/
6578
public AuthorizationRequest(
6679
Optional<EntityUID> principalEUID,
6780
EntityUID actionEUID,
6881
Optional<EntityUID> resourceEUID,
6982
Optional<Map<String, Value>> context,
70-
Optional<Schema> schema) {
83+
Optional<Schema> schema,
84+
boolean enable_request_validation) {
7185
this.principalEUID = principalEUID;
7286
this.actionEUID = actionEUID;
7387
this.resourceEUID = resourceEUID;
@@ -77,10 +91,11 @@ public AuthorizationRequest(
7791
this.context = Optional.of(new HashMap<>(context.get()));
7892
}
7993
this.schema = schema;
94+
this.enable_request_validation = enable_request_validation;
8095
}
8196

8297
/**
83-
* Create a request in the empty context.
98+
* Create a request without a schema.
8499
*
85100
* @param principalEUID Principal's EUID.
86101
* @param actionEUID Action's EUID.
@@ -93,11 +108,12 @@ public AuthorizationRequest(EntityUID principalEUID, EntityUID actionEUID, Entit
93108
actionEUID,
94109
Optional.of(resourceEUID),
95110
Optional.of(context),
96-
Optional.empty());
111+
Optional.empty(),
112+
false);
97113
}
98114

99115
/**
100-
* Create a request without a schema.
116+
* Create a request without a schema, using Entity objects for principal/action/resource.
101117
*
102118
* @param principalEUID Principal's EUID.
103119
* @param actionEUID Action's EUID.
@@ -106,20 +122,32 @@ public AuthorizationRequest(EntityUID principalEUID, EntityUID actionEUID, Entit
106122
*/
107123
public AuthorizationRequest(Entity principalEUID, Entity actionEUID, Entity resourceEUID, Map<String, Value> context) {
108124
this(
109-
Optional.of(principalEUID.getEUID()),
125+
principalEUID.getEUID(),
110126
actionEUID.getEUID(),
111-
Optional.of(resourceEUID.getEUID()),
112-
Optional.of(context),
113-
Optional.empty());
127+
resourceEUID.getEUID(),
128+
context);
114129
}
115130

116-
public AuthorizationRequest(Optional<Entity> principal, Entity action, Optional<Entity> resource, Optional<Map<String, Value>> context, Optional<Schema> schema) {
131+
/**
132+
* Create a request from Entity objects and Context.
133+
*
134+
* @param principal
135+
* @param action
136+
* @param resource
137+
* @param context
138+
* @param schema
139+
* @param enable_request_validation Whether to use the schema for just
140+
* schema-based parsing of `context` (false) or also for request validation
141+
* (true). No effect if `schema` is not provided.
142+
*/
143+
public AuthorizationRequest(Optional<Entity> principal, Entity action, Optional<Entity> resource, Optional<Map<String, Value>> context, Optional<Schema> schema, boolean enable_request_validation) {
117144
this(
118145
principal.map(e -> e.getEUID()),
119146
action.getEUID(),
120147
resource.map(e -> e.getEUID()),
121148
context,
122-
schema
149+
schema,
150+
enable_request_validation
123151
);
124152
}
125153

CedarJava/src/test/java/com/cedarpolicy/JSONTests.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import com.fasterxml.jackson.core.exc.StreamConstraintsException;
4040
import com.fasterxml.jackson.databind.JsonNode;
4141
import com.fasterxml.jackson.databind.node.ArrayNode;
42+
import com.fasterxml.jackson.databind.node.BooleanNode;
4243
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
4344
import com.fasterxml.jackson.databind.node.ObjectNode;
4445
import com.fasterxml.jackson.databind.node.TextNode;
@@ -78,9 +79,9 @@ public void testRequest() {
7879
var moria = new EntityUID(EntityTypeName.parse("Mines").get(), "moria");
7980
AuthorizationRequest q = new AuthorizationRequest(gandalf, opens, moria, new HashMap<String, Value>());
8081
ObjectNode n = JsonNodeFactory.instance.objectNode();
81-
ObjectNode c = JsonNodeFactory.instance.objectNode();
82-
n.set("context", c);
82+
n.set("context", JsonNodeFactory.instance.objectNode());
8383
n.set("schema", JsonNodeFactory.instance.nullNode());
84+
n.set("enable_request_validation", JsonNodeFactory.instance.booleanNode(false));
8485
n.set("principal", buildEuidObject("Wizard", "gandalf"));
8586
n.set("action", buildEuidObject("Action", "opens"));
8687
n.set("resource", buildEuidObject("Mines", "moria"));

CedarJava/src/test/java/com/cedarpolicy/SharedIntegrationTests.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ private static class JsonRequest {
143143
/** Context map used for the request. */
144144
public Map<String, Value> context;
145145

146+
/** Whether to enable request validation for this request. Default true */
147+
public boolean enable_request_validation = true;
148+
146149
/** The expected decision that should be returned by the authorization engine. */
147150
public AuthorizationResponse.Decision decision;
148151

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

296299
// Get a list of the policy sources for the individual policies in the
297-
// file by splitting the full policy source on semicolons. This will
300+
// file by splitting the full policy source on semicolons. This will
298301
// break if a semicolon shows up in a string, eid, or comment.
299302
String[] policyStrings = policiesSrc.split(";");
300303
// Some of the corpus tests contain semicolons in strings and/or eids.
@@ -305,7 +308,7 @@ private Set<Policy> loadPolicies(String policiesFile) throws IOException {
305308
policyStrings = null;
306309
}
307310
}
308-
311+
309312
Set<Policy> policies = new HashSet<>();
310313
if (policyStrings == null) {
311314
// This case will only be reached for corpus tests.
@@ -408,10 +411,11 @@ private void executeJsonRequestTest(
408411
request.principal == null ? Optional.empty() : Optional.of(EntityUID.parseFromJson(request.principal).get()),
409412
EntityUID.parseFromJson(request.action).get(),
410413
request.resource == null ? Optional.empty() : Optional.of(EntityUID.parseFromJson(request.resource).get()),
411-
Optional.of(request.context),
412-
Optional.of(schema));
414+
Optional.of(request.context),
415+
Optional.of(schema),
416+
request.enable_request_validation);
413417
Slice slice = new BasicSlice(policies, entities);
414-
418+
415419
try {
416420
AuthorizationResponse response = auth.isAuthorized(authRequest, slice);
417421
System.out.println(response.getErrors());

CedarJava/src/test/java/com/cedarpolicy/pbt/IntegrationTests.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252

5353
/** Integration tests. */
5454
public class IntegrationTests {
55-
55+
5656
final EntityTypeName principalType;
5757
final EntityTypeName actionType;
5858
final EntityTypeName resourceType;
@@ -368,11 +368,10 @@ public void testUnspecifiedResource() {
368368
Map<String, Value> currentContext = new HashMap<>();
369369
AuthorizationRequest request =
370370
new AuthorizationRequest(
371-
Optional.of(principal),
371+
principal,
372372
action,
373-
Optional.of(resource),
374-
Optional.of(currentContext),
375-
Optional.empty());
373+
resource,
374+
currentContext);
376375
AuthorizationEngine authEngine = new BasicAuthorizationEngine();
377376
AuthorizationResponse response =
378377
Assertions.assertDoesNotThrow(() -> authEngine.isAuthorized(request, slice));
@@ -599,7 +598,7 @@ public void testSchemaParsingDeny() {
599598
Set<Policy> policies = new HashSet<>();
600599
policies.add(policy);
601600

602-
// Schema says resource.owner is a bool, so we should get a parse failure, which causes
601+
// Schema says resource.owner is a bool, so we should get a parse failure, which causes
603602
// `isAuthorized()` to throw a `BadRequestException`.
604603
Slice slice = new BasicSlice(policies, entities);
605604
Map<String, Value> currentContext = new HashMap<>();
@@ -609,7 +608,8 @@ public void testSchemaParsingDeny() {
609608
action,
610609
Optional.of(resource),
611610
Optional.of(currentContext),
612-
Optional.of(loadSchemaResource("/schema_parsing_deny_schema.json")));
611+
Optional.of(loadSchemaResource("/schema_parsing_deny_schema.json")),
612+
true);
613613
AuthorizationEngine authEngine = new BasicAuthorizationEngine();
614614
Assertions.assertThrows(BadRequestException.class, () -> authEngine.isAuthorized(request, slice));
615615
}
@@ -668,7 +668,8 @@ public void testSchemaParsingAllow() {
668668
action,
669669
Optional.of(resource),
670670
Optional.of(currentContext),
671-
Optional.of(loadSchemaResource("/schema_parsing_allow_schema.json")));
671+
Optional.of(loadSchemaResource("/schema_parsing_allow_schema.json")),
672+
true);
672673
AuthorizationEngine authEngine = new BasicAuthorizationEngine();
673674
AuthorizationResponse response =
674675
Assertions.assertDoesNotThrow(() -> authEngine.isAuthorized(request, slice));

0 commit comments

Comments
 (0)