From 80f2a3b4ebe972a2d8ac31f9ff0540a8b10225ca Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Mon, 27 Jan 2025 19:45:41 +0000 Subject: [PATCH 01/15] Add skeleton Context Signed-off-by: Mudit Chaudhary --- .../java/com/cedarpolicy/model/Context.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 CedarJava/src/main/java/com/cedarpolicy/model/Context.java diff --git a/CedarJava/src/main/java/com/cedarpolicy/model/Context.java b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java new file mode 100644 index 00000000..0d6f543e --- /dev/null +++ b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java @@ -0,0 +1,82 @@ +/* +* 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.model; + +import java.util.HashMap; +import java.util.Collections; +import java.util.Collections.singletonMap; +import java.util.Map; +import java.util.List; +import com.cedarpolicy.value.Value; + + +public class Context { + + private Map context; + + /** + * Counterpart to empty() in CedarRust + */ + public Context() { + this.context = Collections.emptyMap(); + } + + /** + * Counterpart to pairs() in CedarRust + * @param contextList + */ + public Context(Iterable> contextList) { + this.context = new HashMap<>(); + fromIterable(); + } + + /** + * Create a context object using a Map + * + * @param contextMap + */ + public Context(Map contextMap) { + this.context = new HashMap<>(); + this.context.putAll(contextMap); + } + + def getContextMap() { + return this.context.copy(); + } + + def merge(Context contextToMerge) { + this.context.putAll(contextToMerge.getContextMap()); + } + + /** + * Merges multiple maps of context values into this context + * @param contextMaps Iterator of Map to merge + */ + public void merge(Iterable> contextMaps) { + fromIterable(contextMaps); + } + + private fromIterable(Iterable> contextIterator) { + contextIterator.forEach(map -> { + if (!this.context.containsKey(map.keySet())) { + this.context.putAll(map); + } else { + throw new IllegalArgumentException("Duplicate key found in context"); + } + });} + + } From 54f56810ee2e6d9d7cec9af2d1e291c9e4e3721e Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Tue, 28 Jan 2025 18:17:29 +0000 Subject: [PATCH 02/15] Adds context methods Signed-off-by: Mudit Chaudhary --- .../java/com/cedarpolicy/model/Context.java | 116 +++++++++++++----- 1 file changed, 87 insertions(+), 29 deletions(-) diff --git a/CedarJava/src/main/java/com/cedarpolicy/model/Context.java b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java index 0d6f543e..d8ddc4ba 100644 --- a/CedarJava/src/main/java/com/cedarpolicy/model/Context.java +++ b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java @@ -18,10 +18,11 @@ import java.util.HashMap; import java.util.Collections; -import java.util.Collections.singletonMap; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import java.util.Map; -import java.util.List; import com.cedarpolicy.value.Value; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; public class Context { @@ -29,54 +30,111 @@ public class Context { private Map context; /** - * Counterpart to empty() in CedarRust + * Constructs a new empty Context with no key-value pairs. + * Initializes the internal context map as an empty immutable map. */ public Context() { - this.context = Collections.emptyMap(); + context = Collections.emptyMap(); } /** - * Counterpart to pairs() in CedarRust - * @param contextList + * Constructs a new Context from an Iterable of key-value pairs. + * Creates a new HashMap and populates it with the provided entries. + * Equivalent to from_pairs in Cedar Rust. + * + * @param contextList An Iterable containing key-value pairs to initialize this context with + * @throws IllegalStateException if a duplicate key is found within the iterable + * @throws IllegalArgumentException if the contextList parameter is null */ - public Context(Iterable> contextList) { - this.context = new HashMap<>(); - fromIterable(); + @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") + public Context(Iterable> contextList) { + context = new HashMap<>(); + fromIterable(contextList); } /** - * Create a context object using a Map - * - * @param contextMap + * Constructs a new Context with the provided map of key-value pairs. + * Creates a defensive copy of the input map to maintain immutability. + * + * @param contextMap The map of key-value pairs to initialize this context with + * @throws IllegalArgumentException if the contextMap parameter is null */ public Context(Map contextMap) { - this.context = new HashMap<>(); - this.context.putAll(contextMap); + context = new HashMap<>(); + context.putAll(contextMap); } - def getContextMap() { - return this.context.copy(); + /** + * Returns a defensive copy of the internal context map. + * + * @return A new HashMap containing all key-value pairs from the internal context + */ + public Map getContextMap() { + return new HashMap<>(context); } - def merge(Context contextToMerge) { - this.context.putAll(contextToMerge.getContextMap()); + /** + * Merges another Context object into the current context. + * + * @param contextToMerge The Context object to merge into this context + * @throws IllegalStateException if a duplicate key is found while merging the context + * @throws IllegalArgumentException if the contextToMerge parameter is null + */ + public void merge(Context contextToMerge) throws IllegalStateException, IllegalArgumentException { + fromIterable(contextToMerge.getContextMap().entrySet()); } /** - * Merges multiple maps of context values into this context - * @param contextMaps Iterator of Map to merge + * Merges the provided key-value pairs into the current context. + * + * @param contextMaps An Iterable containing key-value pairs to merge into this context + * @throws IllegalStateException if a duplicate key is found in the existing context or duplicate key found within the iterable + * @throws IllegalArgumentException if the contextMaps parameter is null */ - public void merge(Iterable> contextMaps) { + public void merge(Iterable> contextMaps) throws IllegalStateException, IllegalArgumentException { fromIterable(contextMaps); } - private fromIterable(Iterable> contextIterator) { - contextIterator.forEach(map -> { - if (!this.context.containsKey(map.keySet())) { - this.context.putAll(map); - } else { - throw new IllegalArgumentException("Duplicate key found in context"); - } - });} + + /** + * Retrieves the Value associated with the specified key from the context. + * + * @param key The key whose associated Value is to be returned + * @return The Value associated with the specified key, or null if the key is not found replicating Cedar Rust behavior + * @throws IllegalArgumentException if the key parameter is null + */ + public Value get(String key) { + if (key == null) { + throw new IllegalArgumentException("Key cannot be null"); + } + return context.getOrDefault(key, null); + } + + /** + * Processes an Iterable of Map entries and adds them to the context. + * + * @param contextIterator The Iterable containing key-value pairs to add to the context + * @throws IllegalStateException if a duplicate key is found in the existing context or duplicate key found within the iterable + * @throws IllegalArgumentException if the contextIterator is null + */ + private void fromIterable(Iterable> contextIterator) throws IllegalStateException, IllegalArgumentException { + if (contextIterator == null) { + throw new IllegalArgumentException("Context iterator cannot be null"); + } + + Map newEntries = StreamSupport.stream(contextIterator.spliterator(), false) + .peek(entry -> { + if (context.containsKey(entry.getKey())) { + throw new IllegalStateException( + String.format("Duplicate key '%s' in existing context", entry.getKey()) + ); + } + }) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue + )); + context.putAll(newEntries); } +} From 2d3c257772be8ae476854c19943b98d237ce31b2 Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Tue, 28 Jan 2025 18:53:37 +0000 Subject: [PATCH 03/15] adds bin/ to .gitignore Signed-off-by: Mudit Chaudhary --- CedarJava/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CedarJava/.gitignore b/CedarJava/.gitignore index c5959e38..e23adf0b 100644 --- a/CedarJava/.gitignore +++ b/CedarJava/.gitignore @@ -13,6 +13,6 @@ .factorypath .project .settings/ - +bin/ # Ignore changes to gradle.properties because we enter passwords here for releases /gradle.properties From 6cc055d5bae0c9eb5c0d041c3316ee5e5dcc3de4 Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Tue, 28 Jan 2025 18:54:24 +0000 Subject: [PATCH 04/15] adds toString and isEmpty method to Context Signed-off-by: Mudit Chaudhary --- .../main/java/com/cedarpolicy/model/Context.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CedarJava/src/main/java/com/cedarpolicy/model/Context.java b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java index d8ddc4ba..b05bab1f 100644 --- a/CedarJava/src/main/java/com/cedarpolicy/model/Context.java +++ b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java @@ -37,6 +37,10 @@ public Context() { context = Collections.emptyMap(); } + public boolean isEmpty() { + return context.isEmpty(); + } + /** * Constructs a new Context from an Iterable of key-value pairs. * Creates a new HashMap and populates it with the provided entries. @@ -69,7 +73,7 @@ public Context(Map contextMap) { * * @return A new HashMap containing all key-value pairs from the internal context */ - public Map getContextMap() { + public Map getContext() { return new HashMap<>(context); } @@ -81,7 +85,7 @@ public Map getContextMap() { * @throws IllegalArgumentException if the contextToMerge parameter is null */ public void merge(Context contextToMerge) throws IllegalStateException, IllegalArgumentException { - fromIterable(contextToMerge.getContextMap().entrySet()); + fromIterable(contextToMerge.getContext().entrySet()); } /** @@ -137,4 +141,10 @@ private void fromIterable(Iterable> contextIterator) th )); context.putAll(newEntries); } + + /** Readable string representation. */ + @Override + public String toString() { + return context.toString(); + } } From 930ce2dc0b54bed05b296c3de6d9792ded593312 Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Tue, 28 Jan 2025 18:55:21 +0000 Subject: [PATCH 05/15] adds overloaded constructors to AuthorizationRequest Signed-off-by: Mudit Chaudhary --- .../model/AuthorizationRequest.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/CedarJava/src/main/java/com/cedarpolicy/model/AuthorizationRequest.java b/CedarJava/src/main/java/com/cedarpolicy/model/AuthorizationRequest.java index 8ab9021a..3d434ffb 100644 --- a/CedarJava/src/main/java/com/cedarpolicy/model/AuthorizationRequest.java +++ b/CedarJava/src/main/java/com/cedarpolicy/model/AuthorizationRequest.java @@ -95,6 +95,34 @@ public AuthorizationRequest( this.enableRequestValidation = enableRequestValidation; } + /** + * Create an authorization request from the EUIDs and Context. + * Constructor overloading to support Context object while preserving backward compatability. + * checked + * @param principalEUID Principal's EUID. + * @param actionEUID Action's EUID. + * @param resourceEUID Resource's EUID. + * @param context Context object. + * @param schema Schema (optional). + * @param enableRequestValidation 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( + EntityUID principalEUID, + EntityUID actionEUID, + EntityUID resourceEUID, + Context context, + Optional schema, + boolean enableRequestValidation) { + this.principalEUID = principalEUID; + this.actionEUID = actionEUID; + this.resourceEUID = resourceEUID; + this.context = Optional.of(context.getContext()); + this.schema = schema; + this.enableRequestValidation = enableRequestValidation; +} + /** * Create a request without a schema. * @@ -113,6 +141,25 @@ public AuthorizationRequest(EntityUID principalEUID, EntityUID actionEUID, Entit false); } + /** + * Create a request without a schema. + * Constructor overloading to support Context object while preserving backward compatability. + * Checked + * @param principalEUID Principal's EUID. + * @param actionEUID Action's EUID. + * @param resourceEUID Resource's EUID. + * @param context Key/Value context. + */ + public AuthorizationRequest(EntityUID principalEUID, EntityUID actionEUID, EntityUID resourceEUID, Context context) { + this( + principalEUID, + actionEUID, + resourceEUID, + context, + Optional.empty(), + false); + } + /** * Create a request without a schema, using Entity objects for principal/action/resource. * @@ -129,6 +176,24 @@ public AuthorizationRequest(Entity principalEUID, Entity actionEUID, Entity reso context); } + /** + * Create a request without a schema, using Entity objects for principal/action/resource. + * Constructor overloading to support Context object while preserving backward compatability. + * checked + * @param principalEUID Principal's EUID. + * @param actionEUID Action's EUID. + * @param resourceEUID Resource's EUID. + * @param context Key/Value context. + */ + public AuthorizationRequest(Entity principalEUID, Entity actionEUID, Entity resourceEUID, Context context) { + this( + principalEUID.getEUID(), + actionEUID.getEUID(), + resourceEUID.getEUID(), + context); + } + + /** * Create a request from Entity objects and Context. * @@ -154,6 +219,31 @@ public AuthorizationRequest(Entity principal, Entity action, Entity resource, ); } + /** + * Create a request from Entity objects and Context. + * Constructor overloading to support Context object while preserving backward compatability. + * checked + * @param principal + * @param action + * @param resource + * @param context + * @param schema + * @param enableRequestValidation 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(Entity principal, Entity action, Entity resource, + Context context, Optional schema, boolean enableRequestValidation) { + this( + principal.getEUID(), + action.getEUID(), + resource.getEUID(), + context, + schema, + enableRequestValidation + ); + } /** Readable string representation. */ @Override From 4aa13637d3f07fa633ba0ddad8f1270449dcc1a8 Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Tue, 28 Jan 2025 19:05:24 +0000 Subject: [PATCH 06/15] adds overloaded builder method to accept Context object for PartialAuthorizationRequest Signed-off-by: Mudit Chaudhary --- .../com/cedarpolicy/model/PartialAuthorizationRequest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CedarJava/src/main/java/com/cedarpolicy/model/PartialAuthorizationRequest.java b/CedarJava/src/main/java/com/cedarpolicy/model/PartialAuthorizationRequest.java index c235daad..9520592d 100644 --- a/CedarJava/src/main/java/com/cedarpolicy/model/PartialAuthorizationRequest.java +++ b/CedarJava/src/main/java/com/cedarpolicy/model/PartialAuthorizationRequest.java @@ -160,6 +160,11 @@ public Builder context(Map context) { return this; } + public Builder context(Context context) { + this.context = Optional.of(ImmutableMap.copyOf(context.getContext())); + return this; + } + /** * Set the context to be empty, not unknown * @return The builder. From dac4502afe039a8138d28fe687c3290aafad7cf8 Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Tue, 28 Jan 2025 21:04:02 +0000 Subject: [PATCH 07/15] adds unit testsfor Context Signed-off-by: Mudit Chaudhary --- .../java/com/cedarpolicy/ContextTests.java | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 CedarJava/src/test/java/com/cedarpolicy/ContextTests.java diff --git a/CedarJava/src/test/java/com/cedarpolicy/ContextTests.java b/CedarJava/src/test/java/com/cedarpolicy/ContextTests.java new file mode 100644 index 00000000..0251994d --- /dev/null +++ b/CedarJava/src/test/java/com/cedarpolicy/ContextTests.java @@ -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; + +import com.cedarpolicy.value.PrimBool; +import com.cedarpolicy.value.PrimLong; +import com.cedarpolicy.value.PrimString; +import com.cedarpolicy.value.Value; +import com.cedarpolicy.model.Context; + +import org.junit.jupiter.api.Test; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ContextTests { + + private Map getValidMap() { + Map expectedContextMap = new HashMap<>(); + expectedContextMap.put("key1", new PrimString("value1")); + expectedContextMap.put("key2", new PrimLong(999)); + expectedContextMap.put("key3", new PrimBool(true)); + + return expectedContextMap; + } + + private Map getValidMergedMap() { + Map expectedContextMap = new HashMap<>(); + expectedContextMap.put("key1", new PrimString("value1")); + expectedContextMap.put("key2", new PrimLong(999)); + expectedContextMap.put("key3", new PrimBool(true)); + expectedContextMap.put("key4", new PrimString("value1")); + expectedContextMap.put("key5", new PrimLong(999)); + + return expectedContextMap; + } + + private Iterable> getValidIterable() { + Set> iterableSet = new HashSet<>(); + iterableSet.add(new AbstractMap.SimpleEntry<>("key1", new PrimString("value1"))); + iterableSet.add(new AbstractMap.SimpleEntry<>("key2", new PrimLong(999))); + iterableSet.add(new AbstractMap.SimpleEntry<>("key3", new PrimBool(true))); + + return iterableSet; + } + + private Iterable> getInvalidIterable() { + Set> iterableSet = new HashSet<>(); + iterableSet.add(new AbstractMap.SimpleEntry<>("key1", new PrimString("value1"))); + iterableSet.add(new AbstractMap.SimpleEntry<>("key1", new PrimLong(999))); + iterableSet.add(new AbstractMap.SimpleEntry<>("key3", new PrimBool(true))); + + return iterableSet; + } + + + @Test + public void givenValidIterableConstructorConstructs() throws IllegalStateException { + Context validContext = new Context(getValidIterable()); + assertEquals(getValidMap(), validContext.getContext()); + } + + @Test + public void givenDuplicateKeyIterableConstructorThrows() throws IllegalStateException { + assertThrows(IllegalStateException.class, () -> { + Context validContext = new Context(getInvalidIterable()); + }); + } + + @Test + public void givenValidMapConstructorConstructs() throws IllegalStateException { + Context validContext = new Context(getValidMap()); + + assertEquals(getValidMap(), validContext.getContext()); + } + + @Test + public void givenValidIterableMergeMerges() throws IllegalStateException { + Context validContext = new Context(getValidMap()); + Map contextToMergeMap = new HashMap<>(); + contextToMergeMap.put("key4", new PrimString("value1")); + contextToMergeMap.put("key5", new PrimLong(999)); + validContext.merge(contextToMergeMap.entrySet()); + + assertEquals(getValidMergedMap(), validContext.getContext()); + } + + @Test + public void givenExistingKeyIterableMergeThrows() throws IllegalStateException { + Context context = new Context(getValidMap()); + Map contextToMergeMap = new HashMap<>(); + contextToMergeMap.put("key3", new PrimString("value1")); + contextToMergeMap.put("key5", new PrimLong(999)); + + assertThrows(IllegalStateException.class, () -> { + context.merge(contextToMergeMap.entrySet()); + }); + } + + @Test + public void givenValidContextMergeMerges() throws IllegalStateException { + Context validContext = new Context(getValidMap()); + Map contextToMergeMap = new HashMap<>(); + contextToMergeMap.put("key4", new PrimString("value1")); + contextToMergeMap.put("key5", new PrimLong(999)); + Context contextToMerge = new Context(contextToMergeMap); + validContext.merge(contextToMerge); + + assertEquals(getValidMergedMap(), validContext.getContext()); + } + + @Test + public void givenExistingKeyContextMergeThrows() throws IllegalStateException { + Context validContext = new Context(getValidMap()); + Map contextToMergeMap = new HashMap<>(); + contextToMergeMap.put("key3", new PrimString("value1")); + contextToMergeMap.put("key5", new PrimLong(999)); + Context contextToMerge = new Context(contextToMergeMap); + + assertThrows(IllegalStateException.class, () -> { + validContext.merge(contextToMerge); + }); + } + + @Test + public void givenValidKeyGetReturnsValue() { + Context validContext = new Context(getValidMap()); + + assertEquals(getValidMap().get("key1"), validContext.get("key1")); + } + + @Test + public void givenInvalidKeyGetReturnsNull() { + Context validContext = new Context(getValidMap()); + + assertEquals(null, validContext.get("invalidKey")); + } +} From 4112cdfe77732579f83810282b3487fbc81e88b4 Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Tue, 28 Jan 2025 21:04:45 +0000 Subject: [PATCH 08/15] fixes styleCheck warnings Signed-off-by: Mudit Chaudhary --- .../main/java/com/cedarpolicy/model/Context.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/CedarJava/src/main/java/com/cedarpolicy/model/Context.java b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java index b05bab1f..d1bbe897 100644 --- a/CedarJava/src/main/java/com/cedarpolicy/model/Context.java +++ b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java @@ -50,7 +50,7 @@ public boolean isEmpty() { * @throws IllegalStateException if a duplicate key is found within the iterable * @throws IllegalArgumentException if the contextList parameter is null */ - @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") + @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") public Context(Iterable> contextList) { context = new HashMap<>(); fromIterable(contextList); @@ -70,7 +70,7 @@ public Context(Map contextMap) { /** * Returns a defensive copy of the internal context map. - * + * * @return A new HashMap containing all key-value pairs from the internal context */ public Map getContext() { @@ -79,7 +79,7 @@ public Map getContext() { /** * Merges another Context object into the current context. - * + * * @param contextToMerge The Context object to merge into this context * @throws IllegalStateException if a duplicate key is found while merging the context * @throws IllegalArgumentException if the contextToMerge parameter is null @@ -90,7 +90,7 @@ public void merge(Context contextToMerge) throws IllegalStateException, IllegalA /** * Merges the provided key-value pairs into the current context. - * + * * @param contextMaps An Iterable containing key-value pairs to merge into this context * @throws IllegalStateException if a duplicate key is found in the existing context or duplicate key found within the iterable * @throws IllegalArgumentException if the contextMaps parameter is null @@ -99,11 +99,9 @@ public void merge(Iterable> contextMaps) throws Illegal fromIterable(contextMaps); } - - /** * Retrieves the Value associated with the specified key from the context. - * + * * @param key The key whose associated Value is to be returned * @return The Value associated with the specified key, or null if the key is not found replicating Cedar Rust behavior * @throws IllegalArgumentException if the key parameter is null @@ -117,7 +115,7 @@ public Value get(String key) { /** * Processes an Iterable of Map entries and adds them to the context. - * + * * @param contextIterator The Iterable containing key-value pairs to add to the context * @throws IllegalStateException if a duplicate key is found in the existing context or duplicate key found within the iterable * @throws IllegalArgumentException if the contextIterator is null From 317c807b51dc7b4751492de0b4eaa5c4431a0a62 Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Tue, 28 Jan 2025 21:55:50 +0000 Subject: [PATCH 09/15] adds tests for backward compatible Context for isAuthorized Signed-off-by: Mudit Chaudhary --- .../test/java/com/cedarpolicy/AuthTests.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java b/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java index 703f6baf..2301647e 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java +++ b/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java @@ -27,11 +27,13 @@ import com.cedarpolicy.model.exception.MissingExperimentalFeatureException; import com.cedarpolicy.model.entity.Entity; import com.cedarpolicy.model.policy.Policy; +import com.cedarpolicy.model.Context; import com.cedarpolicy.model.policy.PolicySet; import com.cedarpolicy.value.EntityTypeName; import com.cedarpolicy.value.EntityUID; import com.cedarpolicy.value.Unknown; import com.cedarpolicy.value.Value; +import com.cedarpolicy.value.PrimBool; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; @@ -52,6 +54,7 @@ private void assertAllowed(AuthorizationRequest q, PolicySet policySet, Set()); } + private Set buildEntitiesForContextTests() { + EntityTypeName principalType = EntityTypeName.parse("User").get(); + EntityTypeName actionType = EntityTypeName.parse("Action").get(); + EntityTypeName albumResourceType = EntityTypeName.parse("Album").get(); + EntityTypeName photoResourceType = EntityTypeName.parse("Photo").get(); + + Set parents = new HashSet<>(); + Entity album = new Entity(albumResourceType.of("Vacation"), new HashMap<>(), new HashSet<>()); + parents.add(album.getEUID()); + Entity photo = new Entity(photoResourceType.of("pic01"), new HashMap<>(), parents); + + Set entities = new HashSet<>(); + entities.add(photo); + entities.add(album); + entities.add(new Entity(principalType.of("Alice"), new HashMap<>(), new HashSet<>())); + entities.add(new Entity(actionType.of("View_Photo"), new HashMap<>(), new HashSet<>())); + + return entities; + } + + private PolicySet buildPolicySetForContextTests() { + Set ps = new HashSet<>(); + String fullPolicy = + "permit(principal == User::\"Alice\", action == Action::\"View_Photo\", resource in Album::\"Vacation\")" + + "when {context.authenticated == true};"; + + Policy newPolicy = new Policy(fullPolicy, "p1"); + ps.add(newPolicy); + return new PolicySet(ps); + } + + @Test + public void authWithBackwardCompatibleContext() { + EntityUID principal = new EntityUID(EntityTypeName.parse("User").get(), "Alice"); + EntityUID action = new EntityUID(EntityTypeName.parse("Action").get(), "View_Photo"); + EntityUID resource = new EntityUID(EntityTypeName.parse("Photo").get(), "pic01"); + Set entities = buildEntitiesForContextTests(); + PolicySet policySet = buildPolicySetForContextTests(); + Map context = new HashMap<>(); + context.put("authenticated", new PrimBool(true)); + AuthorizationRequest r = new AuthorizationRequest(principal, action, resource, context); + + assertAllowed(r, policySet, entities); + } + + @Test + public void authWithContextObject() { + EntityUID principal = new EntityUID(EntityTypeName.parse("User").get(), "Alice"); + EntityUID action = new EntityUID(EntityTypeName.parse("Action").get(), "View_Photo"); + EntityUID resource = new EntityUID(EntityTypeName.parse("Photo").get(), "pic01"); + Set entities = buildEntitiesForContextTests(); + PolicySet policySet = buildPolicySetForContextTests(); + Map contextMap = new HashMap<>(); + contextMap.put("authenticated", new PrimBool(true)); + Context context = new Context(contextMap); + AuthorizationRequest r = new AuthorizationRequest(principal, action, resource, context); + + assertAllowed(r, policySet, entities); + } + @Test public void concrete() { var auth = new BasicAuthorizationEngine(); From 18c1b73c1842a61a0e1e1560cb6917602edbaa82 Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Wed, 29 Jan 2025 00:02:05 +0000 Subject: [PATCH 10/15] adds unit tests for Context for PartialAuthorization Signed-off-by: Mudit Chaudhary --- .../test/java/com/cedarpolicy/AuthTests.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java b/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java index 2301647e..e1bedc64 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java +++ b/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java @@ -147,6 +147,53 @@ public void concrete() { }); } + @Test + public void partialAuthConcreteWithBackwardCompatibleContext() { + var auth = new BasicAuthorizationEngine(); + var alice = new EntityUID(EntityTypeName.parse("User").get(), "alice"); + var view = new EntityUID(EntityTypeName.parse("Action").get(), "view"); + Map context = new HashMap<>(); + context.put("authenticated", new PrimBool(true)); + var q = PartialAuthorizationRequest.builder().principal(alice).action(view).resource(alice).context(context).build(); + var policies = new HashSet(); + policies.add(new Policy("permit(principal == User::\"alice\",action,resource) when {context.authenticated == true};", "p0")); + var policySet = new PolicySet(policies); + assumePartialEvaluation(() -> { + try { + final PartialAuthorizationResponse response = auth.isAuthorizedPartial(q, policySet, new HashSet<>()); + assertEquals(Decision.Allow, response.success.orElseThrow().getDecision()); + assertEquals(response.success.orElseThrow().getMustBeDetermining().iterator().next(), "p0"); + assertTrue(response.success.orElseThrow().getNontrivialResiduals().isEmpty()); + } catch (Exception e) { + fail("error: " + e.toString()); + } + }); + } + + @Test + public void partialAuthConcreteWithContextObject() { + var auth = new BasicAuthorizationEngine(); + var alice = new EntityUID(EntityTypeName.parse("User").get(), "alice"); + var view = new EntityUID(EntityTypeName.parse("Action").get(), "view"); + Map contextMap = new HashMap<>(); + contextMap.put("authenticated", new PrimBool(true)); + Context context = new Context(contextMap); + var q = PartialAuthorizationRequest.builder().principal(alice).action(view).resource(alice).context(context).build(); + var policies = new HashSet(); + policies.add(new Policy("permit(principal == User::\"alice\",action,resource) when {context.authenticated == true};", "p0")); + var policySet = new PolicySet(policies); + assumePartialEvaluation(() -> { + try { + final PartialAuthorizationResponse response = auth.isAuthorizedPartial(q, policySet, new HashSet<>()); + assertEquals(Decision.Allow, response.success.orElseThrow().getDecision()); + assertEquals(response.success.orElseThrow().getMustBeDetermining().iterator().next(), "p0"); + assertTrue(response.success.orElseThrow().getNontrivialResiduals().isEmpty()); + } catch (Exception e) { + fail("error: " + e.toString()); + } + }); + } + @Test public void residual() { var auth = new BasicAuthorizationEngine(); From 1af17803053433d06f7a884d9d4647005d78dd7d Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Wed, 29 Jan 2025 00:04:13 +0000 Subject: [PATCH 11/15] cleans up comments Signed-off-by: Mudit Chaudhary --- .../java/com/cedarpolicy/model/AuthorizationRequest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CedarJava/src/main/java/com/cedarpolicy/model/AuthorizationRequest.java b/CedarJava/src/main/java/com/cedarpolicy/model/AuthorizationRequest.java index 3d434ffb..6176a9d7 100644 --- a/CedarJava/src/main/java/com/cedarpolicy/model/AuthorizationRequest.java +++ b/CedarJava/src/main/java/com/cedarpolicy/model/AuthorizationRequest.java @@ -98,7 +98,7 @@ public AuthorizationRequest( /** * Create an authorization request from the EUIDs and Context. * Constructor overloading to support Context object while preserving backward compatability. - * checked + * * @param principalEUID Principal's EUID. * @param actionEUID Action's EUID. * @param resourceEUID Resource's EUID. @@ -144,7 +144,7 @@ public AuthorizationRequest(EntityUID principalEUID, EntityUID actionEUID, Entit /** * Create a request without a schema. * Constructor overloading to support Context object while preserving backward compatability. - * Checked + * * @param principalEUID Principal's EUID. * @param actionEUID Action's EUID. * @param resourceEUID Resource's EUID. @@ -179,7 +179,7 @@ public AuthorizationRequest(Entity principalEUID, Entity actionEUID, Entity reso /** * Create a request without a schema, using Entity objects for principal/action/resource. * Constructor overloading to support Context object while preserving backward compatability. - * checked + * * @param principalEUID Principal's EUID. * @param actionEUID Action's EUID. * @param resourceEUID Resource's EUID. @@ -222,7 +222,7 @@ public AuthorizationRequest(Entity principal, Entity action, Entity resource, /** * Create a request from Entity objects and Context. * Constructor overloading to support Context object while preserving backward compatability. - * checked + * * @param principal * @param action * @param resource From 582f9219eedf32bd145affd00dc2bf0c0874276d Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Tue, 11 Feb 2025 19:12:44 +0000 Subject: [PATCH 12/15] fixes nits Signed-off-by: Mudit Chaudhary --- CedarJava/src/test/java/com/cedarpolicy/AuthTests.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java b/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java index e1bedc64..6127264a 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java +++ b/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java @@ -54,7 +54,6 @@ private void assertAllowed(AuthorizationRequest q, PolicySet policySet, Set context = new HashMap<>(); context.put("authenticated", new PrimBool(true)); + var q = PartialAuthorizationRequest.builder().principal(alice).action(view).resource(alice).context(context).build(); + var policies = new HashSet(); policies.add(new Policy("permit(principal == User::\"alice\",action,resource) when {context.authenticated == true};", "p0")); var policySet = new PolicySet(policies); + assumePartialEvaluation(() -> { try { final PartialAuthorizationResponse response = auth.isAuthorizedPartial(q, policySet, new HashSet<>()); From d557bdb72b940bcd7d8e0d8b292064a7f716885e Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Tue, 11 Feb 2025 20:06:16 +0000 Subject: [PATCH 13/15] refactors Context Signed-off-by: Mudit Chaudhary --- .../java/com/cedarpolicy/model/Context.java | 59 +++++++++---------- .../test/java/com/cedarpolicy/AuthTests.java | 6 +- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/CedarJava/src/main/java/com/cedarpolicy/model/Context.java b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java index d1bbe897..0c161459 100644 --- a/CedarJava/src/main/java/com/cedarpolicy/model/Context.java +++ b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java @@ -24,14 +24,13 @@ import com.cedarpolicy.value.Value; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - public class Context { private Map context; /** - * Constructs a new empty Context with no key-value pairs. - * Initializes the internal context map as an empty immutable map. + * Constructs a new empty Context with no key-value pairs. Initializes the internal context map as an empty + * immutable map. */ public Context() { context = Collections.emptyMap(); @@ -42,23 +41,22 @@ public boolean isEmpty() { } /** - * Constructs a new Context from an Iterable of key-value pairs. - * Creates a new HashMap and populates it with the provided entries. - * Equivalent to from_pairs in Cedar Rust. + * Constructs a new Context from an Iterable of key-value pairs. Creates a new HashMap and populates it with the + * provided entries. Equivalent to from_pairs in Cedar Rust. * * @param contextList An Iterable containing key-value pairs to initialize this context with - * @throws IllegalStateException if a duplicate key is found within the iterable + * @throws IllegalStateException if a duplicate key is found within the iterable * @throws IllegalArgumentException if the contextList parameter is null */ @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") public Context(Iterable> contextList) { context = new HashMap<>(); - fromIterable(contextList); + mergeContextfromIterable(contextList); } /** - * Constructs a new Context with the provided map of key-value pairs. - * Creates a defensive copy of the input map to maintain immutability. + * Constructs a new Context with the provided map of key-value pairs. Creates a defensive copy of the input map to + * maintain immutability. * * @param contextMap The map of key-value pairs to initialize this context with * @throws IllegalArgumentException if the contextMap parameter is null @@ -73,7 +71,7 @@ public Context(Map contextMap) { * * @return A new HashMap containing all key-value pairs from the internal context */ - public Map getContext() { + public Map getContext() { return new HashMap<>(context); } @@ -81,29 +79,32 @@ public Map getContext() { * Merges another Context object into the current context. * * @param contextToMerge The Context object to merge into this context - * @throws IllegalStateException if a duplicate key is found while merging the context + * @throws IllegalStateException if a duplicate key is found while merging the context * @throws IllegalArgumentException if the contextToMerge parameter is null */ public void merge(Context contextToMerge) throws IllegalStateException, IllegalArgumentException { - fromIterable(contextToMerge.getContext().entrySet()); + mergeContextfromIterable(contextToMerge.getContext().entrySet()); } /** * Merges the provided key-value pairs into the current context. * * @param contextMaps An Iterable containing key-value pairs to merge into this context - * @throws IllegalStateException if a duplicate key is found in the existing context or duplicate key found within the iterable + * @throws IllegalStateException if a duplicate key is found in the existing context or duplicate key found + * within the iterable * @throws IllegalArgumentException if the contextMaps parameter is null */ - public void merge(Iterable> contextMaps) throws IllegalStateException, IllegalArgumentException { - fromIterable(contextMaps); + public void merge(Iterable> contextMaps) + throws IllegalStateException, IllegalArgumentException { + mergeContextfromIterable(contextMaps); } /** * Retrieves the Value associated with the specified key from the context. * * @param key The key whose associated Value is to be returned - * @return The Value associated with the specified key, or null if the key is not found replicating Cedar Rust behavior + * @return The Value associated with the specified key, or null if the key is not found replicating Cedar Rust + * behavior * @throws IllegalArgumentException if the key parameter is null */ public Value get(String key) { @@ -117,26 +118,22 @@ public Value get(String key) { * Processes an Iterable of Map entries and adds them to the context. * * @param contextIterator The Iterable containing key-value pairs to add to the context - * @throws IllegalStateException if a duplicate key is found in the existing context or duplicate key found within the iterable + * @throws IllegalStateException if a duplicate key is found in the existing context or duplicate key found + * within the iterable * @throws IllegalArgumentException if the contextIterator is null */ - private void fromIterable(Iterable> contextIterator) throws IllegalStateException, IllegalArgumentException { + private void mergeContextfromIterable(Iterable> contextIterator) + throws IllegalStateException, IllegalArgumentException { if (contextIterator == null) { throw new IllegalArgumentException("Context iterator cannot be null"); } - Map newEntries = StreamSupport.stream(contextIterator.spliterator(), false) - .peek(entry -> { - if (context.containsKey(entry.getKey())) { - throw new IllegalStateException( - String.format("Duplicate key '%s' in existing context", entry.getKey()) - ); - } - }) - .collect(Collectors.toMap( - Map.Entry::getKey, - Map.Entry::getValue - )); + Map newEntries = StreamSupport.stream(contextIterator.spliterator(), false).peek(entry -> { + if (context.containsKey(entry.getKey())) { + throw new IllegalStateException( + String.format("Duplicate key '%s' in existing context", entry.getKey())); + } + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); context.putAll(newEntries); } diff --git a/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java b/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java index 6127264a..7b288b7c 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java +++ b/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java @@ -121,7 +121,7 @@ public void authWithContextObject() { contextMap.put("authenticated", new PrimBool(true)); Context context = new Context(contextMap); AuthorizationRequest r = new AuthorizationRequest(principal, action, resource, context); - + assertAllowed(r, policySet, entities); } @@ -156,7 +156,7 @@ public void partialAuthConcreteWithBackwardCompatibleContext() { context.put("authenticated", new PrimBool(true)); var q = PartialAuthorizationRequest.builder().principal(alice).action(view).resource(alice).context(context).build(); - + var policies = new HashSet(); policies.add(new Policy("permit(principal == User::\"alice\",action,resource) when {context.authenticated == true};", "p0")); var policySet = new PolicySet(policies); @@ -180,7 +180,7 @@ public void partialAuthConcreteWithContextObject() { var view = new EntityUID(EntityTypeName.parse("Action").get(), "view"); Map contextMap = new HashMap<>(); contextMap.put("authenticated", new PrimBool(true)); - Context context = new Context(contextMap); + Context context = new Context(contextMap); var q = PartialAuthorizationRequest.builder().principal(alice).action(view).resource(alice).context(context).build(); var policies = new HashSet(); policies.add(new Policy("permit(principal == User::\"alice\",action,resource) when {context.authenticated == true};", "p0")); From 94f23a97571fdad9e638c51e1c12dbabe589b16d Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Tue, 11 Feb 2025 20:07:25 +0000 Subject: [PATCH 14/15] refactors Context Signed-off-by: Mudit Chaudhary --- CedarJava/src/main/java/com/cedarpolicy/model/Context.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CedarJava/src/main/java/com/cedarpolicy/model/Context.java b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java index 0c161459..c34ae69f 100644 --- a/CedarJava/src/main/java/com/cedarpolicy/model/Context.java +++ b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java @@ -122,7 +122,7 @@ public Value get(String key) { * within the iterable * @throws IllegalArgumentException if the contextIterator is null */ - private void mergeContextfromIterable(Iterable> contextIterator) + private void mergeContextFromIterable(Iterable> contextIterator) throws IllegalStateException, IllegalArgumentException { if (contextIterator == null) { throw new IllegalArgumentException("Context iterator cannot be null"); From 4ba2a1ac871e7b91e56010d700293c149fe1c28c Mon Sep 17 00:00:00 2001 From: Mudit Chaudhary Date: Tue, 11 Feb 2025 20:10:32 +0000 Subject: [PATCH 15/15] refactors Context to fix tests Signed-off-by: Mudit Chaudhary --- CedarJava/src/main/java/com/cedarpolicy/model/Context.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CedarJava/src/main/java/com/cedarpolicy/model/Context.java b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java index c34ae69f..3250a64b 100644 --- a/CedarJava/src/main/java/com/cedarpolicy/model/Context.java +++ b/CedarJava/src/main/java/com/cedarpolicy/model/Context.java @@ -51,7 +51,7 @@ public boolean isEmpty() { @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") public Context(Iterable> contextList) { context = new HashMap<>(); - mergeContextfromIterable(contextList); + mergeContextFromIterable(contextList); } /** @@ -83,7 +83,7 @@ public Map getContext() { * @throws IllegalArgumentException if the contextToMerge parameter is null */ public void merge(Context contextToMerge) throws IllegalStateException, IllegalArgumentException { - mergeContextfromIterable(contextToMerge.getContext().entrySet()); + mergeContextFromIterable(contextToMerge.getContext().entrySet()); } /** @@ -96,7 +96,7 @@ public void merge(Context contextToMerge) throws IllegalStateException, IllegalA */ public void merge(Iterable> contextMaps) throws IllegalStateException, IllegalArgumentException { - mergeContextfromIterable(contextMaps); + mergeContextFromIterable(contextMaps); } /**