From 4d5ce4c96370aa95799ca4cce892e80f16964a51 Mon Sep 17 00:00:00 2001 From: amzn-mdamine Date: Mon, 18 Nov 2024 13:28:27 -0800 Subject: [PATCH] Cherry-pick #256 Signed-off-by: Mohamed Amine Ouali --- CedarJava/config/checkstyle/suppressions.xml | 2 +- .../cedarpolicy/BasicAuthorizationEngine.java | 11 +- .../model/PartialAuthorizationResponse.java | 156 +++++------------ .../PartialAuthorizationSuccessResponse.java | 159 ++++++++++++++++++ .../test/java/com/cedarpolicy/AuthTests.java | 26 +-- .../test/java/com/cedarpolicy/JSONTests.java | 4 +- 6 files changed, 228 insertions(+), 130 deletions(-) create mode 100644 CedarJava/src/main/java/com/cedarpolicy/model/PartialAuthorizationSuccessResponse.java diff --git a/CedarJava/config/checkstyle/suppressions.xml b/CedarJava/config/checkstyle/suppressions.xml index 90607ad..5d51c99 100644 --- a/CedarJava/config/checkstyle/suppressions.xml +++ b/CedarJava/config/checkstyle/suppressions.xml @@ -9,5 +9,5 @@ - + \ No newline at end of file diff --git a/CedarJava/src/main/java/com/cedarpolicy/BasicAuthorizationEngine.java b/CedarJava/src/main/java/com/cedarpolicy/BasicAuthorizationEngine.java index 2832899..2cdde63 100644 --- a/CedarJava/src/main/java/com/cedarpolicy/BasicAuthorizationEngine.java +++ b/CedarJava/src/main/java/com/cedarpolicy/BasicAuthorizationEngine.java @@ -169,13 +169,18 @@ private static class AuthorizationRequest extends com.cedarpolicy.model.Authoriz } @JsonInclude(JsonInclude.Include.NON_ABSENT) - private static final class PartialAuthorizationRequest { + private static final class PartialAuthorizationRequest extends com.cedarpolicy.model.PartialAuthorizationRequest { @JsonProperty private final PolicySet policies; @JsonProperty private final Set entities; - @JsonProperty public final com.cedarpolicy.model.PartialAuthorizationRequest request; PartialAuthorizationRequest(com.cedarpolicy.model.PartialAuthorizationRequest request, PolicySet policySet, Set entities) { - this.request = request; + super( + request.principal, + request.action, + request.resource, + request.context, + request.schema, + request.enableRequestValidation); this.policies = policySet; this.entities = entities; } diff --git a/CedarJava/src/main/java/com/cedarpolicy/model/PartialAuthorizationResponse.java b/CedarJava/src/main/java/com/cedarpolicy/model/PartialAuthorizationResponse.java index aa3e70b..29e14a9 100644 --- a/CedarJava/src/main/java/com/cedarpolicy/model/PartialAuthorizationResponse.java +++ b/CedarJava/src/main/java/com/cedarpolicy/model/PartialAuthorizationResponse.java @@ -2,137 +2,71 @@ import com.cedarpolicy.Experimental; import com.cedarpolicy.ExperimentalFeature; -import com.cedarpolicy.model.AuthorizationSuccessResponse.Decision; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableList; -import java.util.Map; -import java.util.Set; +import java.util.ArrayList; +import java.util.Optional; @Experimental(ExperimentalFeature.PARTIAL_EVALUATION) public class PartialAuthorizationResponse { - private final Decision decision; - private final ImmutableSet satisfied; - private final ImmutableSet errored; - private final ImmutableSet mayBeDetermining; - private final ImmutableSet mustBeDetermining; - private final ImmutableMap residuals; - private final ImmutableSet nontrivialResiduals; - private final ImmutableSet warnings; - - public PartialAuthorizationResponse(Decision decision, Set satisfied, Set errored, - Set mayBeDetermining, Set mustBeDetermining, Map residuals, - Set nontrivialResiduals, Set warnings) { - this.decision = decision; - // note that ImmutableSet.copyOf() attempts to avoid a full copy when possible - // see https://github.com/google/guava/wiki/ImmutableCollectionsExplained - this.satisfied = ImmutableSet.copyOf(satisfied); - this.errored = ImmutableSet.copyOf(errored); - this.mayBeDetermining = ImmutableSet.copyOf(mayBeDetermining); - this.mustBeDetermining = ImmutableSet.copyOf(mustBeDetermining); - this.residuals = ImmutableMap.copyOf(residuals); - this.nontrivialResiduals = ImmutableSet.copyOf(nontrivialResiduals); - if (warnings == null) { - this.warnings = ImmutableSet.of(); // empty - } else { - this.warnings = ImmutableSet.copyOf(warnings); - } - } - /** - * The optional decision returned by partial authorization - * - * @return a nullable reference to the decision (null means that no conclusive decision can be made) + * Is this a success or a failure response */ - public Decision getDecision() { - return this.decision; - } - + @JsonProperty("type") + public final SuccessOrFailure type; /** - * The map from policy ids to residuals - * - * @return map of residuals + * This will be present if and only if `type` is `Success`. */ - public Map getResiduals() { - return this.residuals; - } - + @JsonProperty("response") + public final Optional success; /** - * Set of policies that are satisfied by the partial request - * - * @return set of policy ids + * This will be present if and only if `type` is `Failure`. */ - public Set getSatisfied() { - return this.satisfied; - } - + @JsonProperty("errors") + public final Optional> errors; /** - * Set of policies that errored during the partial authorization - * - * @return set of policy ids + * Warnings can be produced regardless of whether we have a `Success` or `Failure`. */ - public Set getErrored() { - return this.errored; - } + @JsonProperty("warnings") + public final ImmutableList warnings; /** - * Over approximation of policies that determine the auth decision - * - * @return set of policy ids + * If `type` is `Success`, `success` should be present and `errors` empty. + * If `type` is `Failure`, `errors` should be present and `success` empty. */ - public Set getMayBeDetermining() { - return this.mayBeDetermining; - } - - /** - * Under approximation of policies that determine the auth decision - * - * @return set of policy ids - */ - public Set getMustBeDetermining() { - return this.mustBeDetermining; + @JsonCreator + public PartialAuthorizationResponse( + @JsonProperty("type") SuccessOrFailure type, + @JsonProperty("response") Optional success, + @JsonProperty("errors") Optional> errors, + @JsonProperty("warnings") ArrayList warnings + ) { + this.type = type; + this.success = success; + this.errors = errors.map((list) -> ImmutableList.copyOf(list)); + if (warnings == null) { + this.warnings = ImmutableList.of(); // empty + } else { + this.warnings = ImmutableList.copyOf(warnings); + } } - /** - * Set of non-trivial residual policies - * - * @return set of policy ids - */ - public Set getNontrivialResiduals() { - return this.nontrivialResiduals; + @Override + public String toString() { + final String warningsString = warnings.isEmpty() ? "" : "\nwith warnings: " + warnings; + if (type == SuccessOrFailure.Success) { + return "SUCCESS: " + success.get() + warningsString; + } else { + return "FAILURE: " + errors.get() + warningsString; + } } - /** - * Deserializer factory method for PartialAuthorizationResponse. - * @param nested Deserialized object for nested JSON object. - * @param decision Deserialized `decision` attribute of nested JSON object. - * @param satisfied Deserialized `satisfied` attribute of nested JSON object. - * @param errored Deserialized `errored` attribute of nested JSON object. - * @param mayBeDetermining Deserialized `mayBeDetermining` attribute of nested JSON object. - * @param mustBeDetermining Deserialized `mustBeDetermining` attribute of nested JSON object. - * @param residuals Deserialized `residual` attribute of nested JSON object. - * @param nontrivialResiduals Deserialized `nontrivialResiduals` attribute of nested JSON object. - * @param warnings Deserialized `warnings` attribute of nested JSON object. - * @return - */ - @JsonCreator - public static PartialAuthorizationResponse createPartialAuthorizationResponse( - @JsonProperty("response") PartialAuthorizationResponse nested, - @JsonProperty("decision") Decision decision, - @JsonProperty("satisfied") Set satisfied, - @JsonProperty("errored") Set errored, - @JsonProperty("mayBeDetermining") Set mayBeDetermining, - @JsonProperty("mustBeDetermining") Set mustBeDetermining, - @JsonProperty("residuals") Map residuals, - @JsonProperty("nontrivialResiduals") Set nontrivialResiduals, - @JsonProperty("warnings") Set warnings) { - if (nested != null) { - return nested; - } - return new PartialAuthorizationResponse(decision, satisfied, errored, mayBeDetermining, mustBeDetermining, - residuals, nontrivialResiduals, warnings); + public enum SuccessOrFailure { + @JsonProperty("residuals") + Success, + @JsonProperty("failure") + Failure, } } diff --git a/CedarJava/src/main/java/com/cedarpolicy/model/PartialAuthorizationSuccessResponse.java b/CedarJava/src/main/java/com/cedarpolicy/model/PartialAuthorizationSuccessResponse.java new file mode 100644 index 0000000..a6592a5 --- /dev/null +++ b/CedarJava/src/main/java/com/cedarpolicy/model/PartialAuthorizationSuccessResponse.java @@ -0,0 +1,159 @@ +/* + * 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.Map; +import java.util.Set; + +import com.cedarpolicy.Experimental; +import com.cedarpolicy.ExperimentalFeature; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +/** + * Successful partial authorization response + */ +@Experimental(ExperimentalFeature.PARTIAL_EVALUATION) +public final class PartialAuthorizationSuccessResponse { + private final AuthorizationSuccessResponse.Decision decision; + private final ImmutableSet satisfied; + private final ImmutableSet errored; + private final ImmutableSet mayBeDetermining; + private final ImmutableSet mustBeDetermining; + private final ImmutableMap residuals; + private final ImmutableSet nontrivialResiduals; + private final ImmutableSet warnings; + + public PartialAuthorizationSuccessResponse( + AuthorizationSuccessResponse.Decision decision, Set satisfied, Set errored, + Set mayBeDetermining, Set mustBeDetermining, Map residuals, + Set nontrivialResiduals, Set warnings) { + this.decision = decision; + // note that ImmutableSet.copyOf() attempts to avoid a full copy when possible + // see https://github.com/google/guava/wiki/ImmutableCollectionsExplained + this.satisfied = ImmutableSet.copyOf(satisfied); + this.errored = ImmutableSet.copyOf(errored); + this.mayBeDetermining = ImmutableSet.copyOf(mayBeDetermining); + this.mustBeDetermining = ImmutableSet.copyOf(mustBeDetermining); + this.residuals = ImmutableMap.copyOf(residuals); + this.nontrivialResiduals = ImmutableSet.copyOf(nontrivialResiduals); + if (warnings == null) { + this.warnings = ImmutableSet.of(); // empty + } else { + this.warnings = ImmutableSet.copyOf(warnings); + } + } + + /** + * Deserializer factory method for PartialAuthorizationResponse. + * + * @param nested Deserialized object for nested JSON object. + * @param decision Deserialized `decision` attribute of nested JSON object. + * @param satisfied Deserialized `satisfied` attribute of nested JSON object. + * @param errored Deserialized `errored` attribute of nested JSON object. + * @param mayBeDetermining Deserialized `mayBeDetermining` attribute of nested JSON object. + * @param mustBeDetermining Deserialized `mustBeDetermining` attribute of nested JSON object. + * @param residuals Deserialized `residual` attribute of nested JSON object. + * @param nontrivialResiduals Deserialized `nontrivialResiduals` attribute of nested JSON object. + * @param warnings Deserialized `warnings` attribute of nested JSON object. + * @return + */ + @JsonCreator + public static PartialAuthorizationSuccessResponse createPartialAuthorizationSuccessResponse( + @JsonProperty("response") PartialAuthorizationSuccessResponse nested, + @JsonProperty("decision") AuthorizationSuccessResponse.Decision decision, + @JsonProperty("satisfied") Set satisfied, + @JsonProperty("errored") Set errored, + @JsonProperty("mayBeDetermining") Set mayBeDetermining, + @JsonProperty("mustBeDetermining") Set mustBeDetermining, + @JsonProperty("residuals") Map residuals, + @JsonProperty("nontrivialResiduals") Set nontrivialResiduals, + @JsonProperty("warnings") Set warnings) { + if (nested != null) { + return nested; + } + return new PartialAuthorizationSuccessResponse(decision, satisfied, errored, mayBeDetermining, + mustBeDetermining, + residuals, nontrivialResiduals, warnings); + } + + /** + * The optional decision returned by partial authorization + * + * @return a nullable reference to the decision (null means that no conclusive decision can be made) + */ + public AuthorizationSuccessResponse.Decision getDecision() { + return this.decision; + } + + /** + * The map from policy ids to residuals + * + * @return map of residuals + */ + public Map getResiduals() { + return this.residuals; + } + + /** + * Set of policies that are satisfied by the partial request + * + * @return set of policy ids + */ + public Set getSatisfied() { + return this.satisfied; + } + + /** + * Set of policies that errored during the partial authorization + * + * @return set of policy ids + */ + public Set getErrored() { + return this.errored; + } + + /** + * Over approximation of policies that determine the auth decision + * + * @return set of policy ids + */ + public Set getMayBeDetermining() { + return this.mayBeDetermining; + } + + /** + * Under approximation of policies that determine the auth decision + * + * @return set of policy ids + */ + public Set getMustBeDetermining() { + return this.mustBeDetermining; + } + + /** + * Set of non-trivial residual policies + * + * @return set of policy ids + */ + public Set getNontrivialResiduals() { + return this.nontrivialResiduals; + } +} diff --git a/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java b/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java index 1314aca..8b4aa7d 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java +++ b/CedarJava/src/test/java/com/cedarpolicy/AuthTests.java @@ -71,9 +71,9 @@ public void concrete() { assumePartialEvaluation(() -> { try { final PartialAuthorizationResponse response = auth.isAuthorizedPartial(q, policySet, new HashSet<>()); - assertEquals(Decision.Allow, response.getDecision()); - assertEquals(response.getMustBeDetermining().iterator().next(), "p0"); - assertTrue(response.getNontrivialResiduals().isEmpty()); + 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()); } @@ -92,21 +92,21 @@ public void residual() { assumePartialEvaluation(() -> { try { final PartialAuthorizationResponse response = auth.isAuthorizedPartial(q, policySet, new HashSet<>()); - assertTrue(response.getDecision() == null); - assertEquals("p0", response.getResiduals().entrySet().iterator().next().getKey()); + assertTrue(response.success.orElseThrow().getDecision() == null); + assertEquals("p0", response.success.orElseThrow().getResiduals().entrySet().iterator().next().getKey()); } catch (Exception e) { fail("error: " + e.toString()); } }); } - private Executable assumePartialEvaluation(Executable executable) { - return () -> { - try { - executable.execute(); - } catch (MissingExperimentalFeatureException e) { - System.err.println("Skipping assertions: " + e.getMessage()); - } - }; + private void assumePartialEvaluation(Executable executable) { + try { + executable.execute(); + } catch (MissingExperimentalFeatureException e) { + System.err.println("Skipping assertions: " + e.getMessage()); + } catch (Throwable e) { + throw new RuntimeException(e); + } } } diff --git a/CedarJava/src/test/java/com/cedarpolicy/JSONTests.java b/CedarJava/src/test/java/com/cedarpolicy/JSONTests.java index 0a5e1ff..b3824e4 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/JSONTests.java +++ b/CedarJava/src/test/java/com/cedarpolicy/JSONTests.java @@ -73,7 +73,7 @@ public void testAuthConcretePartialResponse() { "{ \"response\": { \"decision\":\"allow\", \"satisfied\": [], \"errored\": [\"p0\"], \"mayBeDetermining\": [], \"mustBeDetermining\": [\"p1\"], \"residuals\": {\"p2\": 3}, \"nontrivialResiduals\": [] } }"; try { PartialAuthorizationResponse r = objectReader().forType(PartialAuthorizationResponse.class).readValue(src); - assertTrue(r.getDecision() == Decision.Allow); + assertTrue(r.success.orElseThrow().getDecision() == Decision.Allow); } catch (JsonProcessingException e) { fail(e); } @@ -85,7 +85,7 @@ public void testAuthResidualPartialResponse() { final String src = "{ \"response\": { \"decision\":\"allow\", \"satisfied\": [], \"errored\": [\"p0\"], \"mayBeDetermining\": [], \"mustBeDetermining\": [\"p1\"], \"residuals\": {\"p0\": " + policy + " }, \"nontrivialResiduals\": [] } }"; try { PartialAuthorizationResponse r = objectReader().forType(PartialAuthorizationResponse.class).readValue(src); - var residuals = r.getResiduals(); + var residuals = r.success.orElseThrow().getResiduals(); assertEquals(1, residuals.size()); assertEquals("p0", residuals.entrySet().iterator().next().getKey()); assertJSONEqual(CedarJson.objectMapper().readTree(policy),