Skip to content

Commit ef341fd

Browse files
Adds Entities with backward compatibility (#293)
Signed-off-by: Mudit Chaudhary <chmudit@amazon.com>
1 parent 21d7f45 commit ef341fd

File tree

9 files changed

+347
-10
lines changed

9 files changed

+347
-10
lines changed

CedarJava/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ dependencies {
8585
compileOnly 'com.github.spotbugs:spotbugs-annotations:4.8.6'
8686
testImplementation 'net.jqwik:jqwik:1.9.2'
8787
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.4'
88+
testImplementation 'org.skyscreamer:jsonassert:2.0-rc1'
8889
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.11.4'
8990
}
9091

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

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@
1616

1717
package com.cedarpolicy;
1818

19-
import com.cedarpolicy.model.*;
19+
import java.util.Set;
20+
21+
import com.cedarpolicy.model.AuthorizationRequest;
22+
import com.cedarpolicy.model.AuthorizationResponse;
23+
import com.cedarpolicy.model.EntityValidationRequest;
24+
import com.cedarpolicy.model.PartialAuthorizationRequest;
25+
import com.cedarpolicy.model.PartialAuthorizationResponse;
26+
import com.cedarpolicy.model.ValidationRequest;
27+
import com.cedarpolicy.model.ValidationResponse;
28+
import com.cedarpolicy.model.entity.Entities;
29+
import com.cedarpolicy.model.entity.Entity;
2030
import com.cedarpolicy.model.exception.AuthException;
2131
import com.cedarpolicy.model.exception.BadRequestException;
22-
import com.cedarpolicy.model.entity.Entity;
2332
import com.cedarpolicy.model.policy.PolicySet;
2433

25-
import java.util.Set;
26-
2734
/**
2835
* Implementations of the AuthorizationEngine interface invoke Cedar to respond to an authorization
2936
* or validation request. For authorization, the input includes the relevant policies and entities for
@@ -51,6 +58,21 @@ public interface AuthorizationEngine {
5158
*/
5259
AuthorizationResponse isAuthorized(AuthorizationRequest request, PolicySet policySet, Set<Entity> entities) throws AuthException;
5360

61+
/**
62+
* Asks whether the given AuthorizationRequest <code>q</code> is approved by the <code>policySet</code> and
63+
* <code>entities</code> hierarchy given. Overloaded method to accept Entities object.
64+
*
65+
* @param request The request to evaluate
66+
* @param policySet The policy set to evaluate against
67+
* @param entities The entities to evaluate against
68+
* @return The result of the request evaluation
69+
* @throws BadRequestException if any errors were found in the syntax of the policies.
70+
* @throws AuthException On failure to make the authorization request. Note that errors inside the
71+
* authorization engine are included in the <code>errors</code> field on the
72+
* AuthorizationResponse.
73+
*/
74+
AuthorizationResponse isAuthorized(AuthorizationRequest request, PolicySet policySet, Entities entities) throws AuthException;
75+
5476
/**
5577
* Asks whether the given AuthorizationRequest <code>q</code> is approved by the <code>policySet</code> and
5678
* <code>entities</code> given. If information required to answer is missing, residual policies are returned.
@@ -68,6 +90,24 @@ public interface AuthorizationEngine {
6890
PartialAuthorizationResponse isAuthorizedPartial(PartialAuthorizationRequest request,
6991
PolicySet policySet, Set<Entity> entities) throws AuthException;
7092

93+
/**
94+
* Asks whether the given AuthorizationRequest <code>q</code> is approved by the <code>policySet</code> and
95+
* <code>entities</code> given. If information required to answer is missing, residual policies are returned.
96+
* Overloaded method to accept Entities object.
97+
*
98+
* @param request The request to evaluate
99+
* @param policySet The policy set to evaluate against
100+
* @param entities The entities to evaluate against
101+
* @return The result of the request evaluation
102+
* @throws BadRequestException if any errors were found in the syntax of the policies.
103+
* @throws AuthException On failure to make the authorization request. Note that errors inside the
104+
* authorization engine are included in the <code>errors</code> field on the
105+
* AuthorizationResponse.
106+
*/
107+
@Experimental(ExperimentalFeature.PARTIAL_EVALUATION)
108+
PartialAuthorizationResponse isAuthorizedPartial(PartialAuthorizationRequest request,
109+
PolicySet policySet, Entities entities) throws AuthException;
110+
71111
/**
72112
* Asks whether the policies in the given {@link ValidationRequest} <code>q</code> are correct
73113
* when validated against the schema it describes.

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

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,30 @@
2020
import static com.cedarpolicy.CedarJson.objectWriter;
2121

2222
import java.io.IOException;
23+
import java.util.List;
24+
import java.util.Set;
2325

2426
import com.cedarpolicy.loader.LibraryLoader;
25-
import com.cedarpolicy.model.*;
27+
import com.cedarpolicy.model.AuthorizationResponse;
28+
import com.cedarpolicy.model.EntityValidationRequest;
29+
import com.cedarpolicy.model.PartialAuthorizationResponse;
30+
import com.cedarpolicy.model.ValidationRequest;
31+
import com.cedarpolicy.model.ValidationResponse;
32+
import com.cedarpolicy.model.entity.Entities;
33+
import com.cedarpolicy.model.entity.Entity;
2634
import com.cedarpolicy.model.exception.AuthException;
2735
import com.cedarpolicy.model.exception.BadRequestException;
2836
import com.cedarpolicy.model.exception.InternalException;
2937
import com.cedarpolicy.model.exception.MissingExperimentalFeatureException;
30-
import com.cedarpolicy.model.entity.Entity;
3138
import com.cedarpolicy.model.policy.PolicySet;
3239
import com.fasterxml.jackson.annotation.JsonCreator;
3340
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
3441
import com.fasterxml.jackson.annotation.JsonInclude;
3542
import com.fasterxml.jackson.annotation.JsonProperty;
3643
import com.fasterxml.jackson.core.JsonProcessingException;
3744
import com.fasterxml.jackson.databind.JsonNode;
38-
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
3945

40-
import java.util.List;
41-
import java.util.Set;
46+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4247

4348
/** An authorization engine that is compiled in process. Communicated with via JNI. */
4449
public final class BasicAuthorizationEngine implements AuthorizationEngine {
@@ -57,6 +62,15 @@ public AuthorizationResponse isAuthorized(com.cedarpolicy.model.AuthorizationReq
5762
return call("AuthorizationOperation", AuthorizationResponse.class, request);
5863
}
5964

65+
/**
66+
* Overloaded method to accept Entities object
67+
*/
68+
@Override
69+
public AuthorizationResponse isAuthorized(com.cedarpolicy.model.AuthorizationRequest q,
70+
PolicySet policySet, Entities entities) throws AuthException {
71+
return isAuthorized(q, policySet, entities.getEntities());
72+
}
73+
6074
@Experimental(ExperimentalFeature.PARTIAL_EVALUATION)
6175
@Override
6276
public PartialAuthorizationResponse isAuthorizedPartial(com.cedarpolicy.model.PartialAuthorizationRequest q,
@@ -73,6 +87,16 @@ public PartialAuthorizationResponse isAuthorizedPartial(com.cedarpolicy.model.Pa
7387
}
7488
}
7589

90+
/**
91+
* Overloaded method to accept Entities object
92+
*/
93+
@Experimental(ExperimentalFeature.PARTIAL_EVALUATION)
94+
@Override
95+
public PartialAuthorizationResponse isAuthorizedPartial(com.cedarpolicy.model.PartialAuthorizationRequest q,
96+
PolicySet policySet, Entities entities) throws AuthException {
97+
return isAuthorizedPartial(q, policySet, entities.getEntities());
98+
}
99+
76100
@Override
77101
public ValidationResponse validate(ValidationRequest q) throws AuthException {
78102
return call("ValidateOperation", ValidationResponse.class, q);
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright Cedar Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.cedarpolicy.model.entity;
18+
19+
import static com.cedarpolicy.CedarJson.objectReader;
20+
21+
import com.fasterxml.jackson.core.JsonProcessingException;
22+
import com.fasterxml.jackson.core.type.TypeReference;
23+
24+
import java.io.IOException;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import java.util.Set;
28+
import java.util.HashSet;
29+
30+
/**
31+
* A class representing a collection of Cedar policy entities.
32+
*/
33+
public class Entities {
34+
private Set<Entity> entities;
35+
36+
/**
37+
* Constructs a new empty Entities collection. Creates a new HashSet to store Entity objects.
38+
*/
39+
public Entities() {
40+
this.entities = new HashSet<>();
41+
}
42+
43+
/**
44+
* Constructs a new Entities collection from a given Set of Entity objects.
45+
*
46+
* @param entities The Set of Entity objects to initialize this collection with
47+
*/
48+
public Entities(Set<Entity> entities) {
49+
this.entities = new HashSet<>(entities);
50+
}
51+
52+
/**
53+
* Returns a copy of the set of entities in this collection.
54+
*
55+
* @return A new HashSet containing all Entity objects in this collection
56+
*/
57+
public Set<Entity> getEntities() {
58+
return new HashSet<>(entities);
59+
}
60+
61+
/**
62+
* Parses a JSON string representation into an Entities collection.
63+
*
64+
* @param jsonString The JSON string containing entity data to parse
65+
*
66+
* @return A new Entities instance containing the parsed entities
67+
* @throws JsonProcessingException If the JSON string cannot be parsed into valid entities
68+
*/
69+
public static Entities parse(String jsonString) throws JsonProcessingException {
70+
return new Entities(objectReader().forType(new TypeReference<Set<Entity>>() {
71+
}).readValue(jsonString));
72+
}
73+
74+
/**
75+
* Parses a JSON file at the specified path into an Entities collection.
76+
*
77+
* @param filePath The path to the JSON file containing entity data to parse
78+
*
79+
* @return A new Entities instance containing the parsed entities
80+
* @throws IOException If there is an error reading the file
81+
* @throws JsonProcessingException If the JSON content cannot be parsed into valid entities
82+
*/
83+
public static Entities parse(Path filePath) throws IOException, JsonProcessingException {
84+
String jsonString = Files.readString(filePath);
85+
return new Entities(objectReader().forType(new TypeReference<Set<Entity>>() {
86+
}).readValue(jsonString));
87+
}
88+
89+
@Override
90+
public String toString() {
91+
return String.join("\n", this.entities.stream().map(Entity::toString).toList());
92+
}
93+
}

CedarJava/src/main/java/com/cedarpolicy/model/entity/Entity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public Entity(EntityUID uid, Map<String, Value> attributes, Set<EntityUID> paren
9494
* Get the value for the given attribute, or null if not present.
9595
*
9696
* @param attribute Attribute key
97-
*
97+
*
9898
* @return Attribute value for the given key or null if not present
9999
* @throws IllegalArgumentException if attribute is null
100100
*/

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.cedarpolicy.value.Unknown;
3535
import com.cedarpolicy.value.Value;
3636
import com.cedarpolicy.value.PrimBool;
37+
import com.cedarpolicy.model.entity.Entities;
3738

3839
import org.junit.jupiter.api.Test;
3940
import org.junit.jupiter.api.function.Executable;
@@ -47,6 +48,14 @@ public class AuthTests {
4748

4849
private void assertAllowed(AuthorizationRequest q, PolicySet policySet, Set<Entity> entities) {
4950
assertDoesNotThrow(() -> {
51+
// Using Entities object
52+
Entities entitiesObj = new Entities(entities);
53+
final var responseWithEntities = new BasicAuthorizationEngine().isAuthorized(q, policySet, entitiesObj);
54+
assertEquals(responseWithEntities.type, SuccessOrFailure.Success);
55+
final var successWithEntities = responseWithEntities.success.get();
56+
assertTrue(successWithEntities.isAllowed());
57+
58+
// Backward compatible using Set<Entities>
5059
final var response = new BasicAuthorizationEngine().isAuthorized(q, policySet, entities);
5160
assertEquals(response.type, SuccessOrFailure.Success);
5261
final var success = response.success.get();
@@ -197,6 +206,30 @@ public void partialAuthConcreteWithContextObject() {
197206
});
198207
}
199208

209+
@Test
210+
public void partialAuthConcreteWithEntitiesObject() {
211+
var auth = new BasicAuthorizationEngine();
212+
var alice = new EntityUID(EntityTypeName.parse("User").get(), "alice");
213+
var view = new EntityUID(EntityTypeName.parse("Action").get(), "view");
214+
Map<String, Value> contextMap = new HashMap<>();
215+
contextMap.put("authenticated", new PrimBool(true));
216+
Context context = new Context(contextMap);
217+
var q = PartialAuthorizationRequest.builder().principal(alice).action(view).resource(alice).context(context).build();
218+
var policies = new HashSet<Policy>();
219+
policies.add(new Policy("permit(principal == User::\"alice\",action,resource) when {context.authenticated == true};", "p0"));
220+
var policySet = new PolicySet(policies);
221+
assumePartialEvaluation(() -> {
222+
try {
223+
final PartialAuthorizationResponse response = auth.isAuthorizedPartial(q, policySet, new Entities());
224+
assertEquals(Decision.Allow, response.success.orElseThrow().getDecision());
225+
assertEquals(response.success.orElseThrow().getMustBeDetermining().iterator().next(), "p0");
226+
assertTrue(response.success.orElseThrow().getNontrivialResiduals().isEmpty());
227+
} catch (Exception e) {
228+
fail("error: " + e.toString());
229+
}
230+
});
231+
}
232+
200233
@Test
201234
public void residual() {
202235
var auth = new BasicAuthorizationEngine();

0 commit comments

Comments
 (0)