diff --git a/.gitignore b/.gitignore index 1b6985c..d2870b5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ # Ignore Gradle build output directory build + + +.idea \ No newline at end of file diff --git a/opa-async-java-client/src/test/java/com/contentgrid/opa/client/OpaClientIntegrationTests.java b/opa-async-java-client/src/test/java/com/contentgrid/opa/client/OpaClientIntegrationTests.java index 21e5ea1..d89171a 100644 --- a/opa-async-java-client/src/test/java/com/contentgrid/opa/client/OpaClientIntegrationTests.java +++ b/opa-async-java-client/src/test/java/com/contentgrid/opa/client/OpaClientIntegrationTests.java @@ -6,14 +6,16 @@ import com.contentgrid.opa.client.api.CompileApi; import com.contentgrid.opa.client.api.CompileApi.PartialEvaluationRequest; -import com.contentgrid.opa.rego.ast.Query; +import com.contentgrid.opa.client.api.DataApi; +import com.contentgrid.opa.client.api.DataApi.GetDataResponse; +import com.contentgrid.opa.client.api.PolicyApi.ListPoliciesResponse; import com.contentgrid.opa.client.rest.http.HttpStatusException; import com.contentgrid.opa.rego.ast.Expression; +import com.contentgrid.opa.rego.ast.Query; import com.contentgrid.opa.rego.ast.Term; import com.contentgrid.opa.rego.ast.Term.Ref; -import com.contentgrid.opa.client.api.DataApi; -import com.contentgrid.opa.client.api.DataApi.GetDataResponse; -import com.contentgrid.opa.client.api.PolicyApi.ListPoliciesResponse; +import com.contentgrid.opa.rego.ast.Term.Text; +import com.contentgrid.opa.rego.ast.Term.Var; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; @@ -40,12 +42,12 @@ @Testcontainers class OpaClientIntegrationTests { - private static final DockerImageName OPA_IMAGE = DockerImageName.parse("openpolicyagent/opa:0.70.0"); + private static final DockerImageName OPA_IMAGE = DockerImageName.parse("openpolicyagent/opa:1.9.0"); private static final int OPA_EXPOSED_PORT = 8181; @Container private static final GenericContainer opaContainer = new GenericContainer<>(OPA_IMAGE) - .withCommand(String.format("run --server --addr :%d", OPA_EXPOSED_PORT)) + .withCommand(String.format("run --server --addr :%d --v0-compatible", OPA_EXPOSED_PORT)) .withExposedPorts(OPA_EXPOSED_PORT) .waitingFor(Wait.forHttp("/")); @@ -68,8 +70,8 @@ void opaIsRunningInDocker() { @Nested class PolicyApiTests { - private static final String PATH_EXAMPLE_1 = "fixtures/openpolicyagent.org/docs/policy/policies-example-1.txt"; - private static final String PATH_EXAMPLE_2 = "fixtures/openpolicyagent.org/docs/policy/policies-example-2.txt"; + private static final String PATH_EXAMPLE_1 = "fixtures/openpolicyagent.org/docs/policy/policies-example-1.rego"; + private static final String PATH_EXAMPLE_2 = "fixtures/openpolicyagent.org/docs/policy/policies-example-2.rego"; @Test @Order(1) @@ -195,7 +197,7 @@ void adhocQuery() throws IOException { @Nested class CompileApiTests { - public static final String POLICY_PATH = "fixtures/openpolicyagent.org/docs/compile/compile-api-policy.txt"; + public static final String POLICY_PATH = "fixtures/openpolicyagent.org/docs/compile/compile-api-policy.rego"; @Test void unknownInputX_largerThan0() { @@ -284,6 +286,76 @@ void compileExample() { }))); } + @Test + void abacExample() { + opaClient.upsertPolicy("abac-example", loadResourceAsString( + "fixtures/openpolicyagent.org/docs/compile/abac-policy.rego")).join(); + + var request = new PartialEvaluationRequest( + "data.abac.example.allow == true", + Map.of("request", + Map.of("headers", Map.of("content-type", "application/json"), + "method", "GET", + "path", List.of("tests") + ), + "auth", Map.of( + "authenticated", true, + "principal", Map.of( + "kind", "user", + "contentgrid:att1", List.of("att1v1", "att1v2"), + "contentgrid:att2", List.of("att2v1", "att2v2") + ) + ) + ), List.of("input.entity")); + + var result = opaClient.compile(request).join(); + assertThat(result).isNotNull(); + assertThat(result.getResult()).isNotNull(); + + assertThat(result.getResult().getQueries()) + .isNotNull() + // 4 queries are generated, because there are 4 combinations of the 2 attributes in the policy + .hasSize(4) + .allSatisfy( + query -> assertThat(query) + // There are 2 expressions per query, one for every user attribute in the policy + .hasSize(2) + .allSatisfy(expr -> { + // each expression has 3 terms (the 'eq' operator, the attribute value and the input value) + assertThat(expr.getTerms()) + .hasSize(3) + .anySatisfy( + term -> { + assertThat(term).isInstanceOfSatisfying(Ref.class, ref -> { + assertThat(ref.getValue()).singleElement().satisfies( + t -> assertThat(t).hasToString("eq") + ); + }); + } + ); + }) + ) + .anySatisfy(query -> { + assertThat(query).anySatisfy( + expr -> assertThat(expr.getTerms()) + .isEqualTo(List.of( + new Ref(List.of(new Var("eq"))), + new Text("att1v1"), + new Ref(List.of( + new Var("input"), + new Text("entity"), + new Text("a") + ) + ) + ) + + ) + ); + + }) + ; + } + @Test void example_alwaysTrue() { opaClient.upsertPolicy("compile-example", loadResourceAsString(POLICY_PATH)).join(); @@ -343,7 +415,7 @@ class Scenarios { */ @Test void partialEval_getApiDocuments() { - final String POLICY_PATH = "fixtures/scenarios/api-documents-policy.txt"; + final String POLICY_PATH = "fixtures/scenarios/api-documents-policy.rego"; opaClient.upsertPolicy("test", loadResourceAsString(POLICY_PATH)).join(); var result = opaClient.compile( diff --git a/opa-async-java-client/src/test/java/com/contentgrid/opa/client/api/PolicyApiTest.java b/opa-async-java-client/src/test/java/com/contentgrid/opa/client/api/PolicyApiTest.java new file mode 100644 index 0000000..aa4e374 --- /dev/null +++ b/opa-async-java-client/src/test/java/com/contentgrid/opa/client/api/PolicyApiTest.java @@ -0,0 +1,37 @@ +package com.contentgrid.opa.client.api; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import com.contentgrid.opa.client.api.PolicyApi.ListPoliciesResponse; +import com.contentgrid.opa.rego.ast.Term; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.junit.jupiter.api.Test; + +class PolicyApiTest { + + @Test + void testPoliciesResponseObjectMapping() { + var objectMapper = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build() + .registerModule(new JavaTimeModule()); + + // read listpoliciesresponse.json into ListPoliciesResponse + try (var is = getClass().getClassLoader().getResourceAsStream( + "com/contentgrid/opa/client/api/policyapi/listpoliciesresponse.json")) { + var response = objectMapper.readValue(is, ListPoliciesResponse.class); + assertNotNull(response); + assertThat(response.getResult().get(2).getAst().getRules().get(1).getBody()).singleElement().satisfies(expression -> { + assertThat(expression.getTerms()).singleElement().isInstanceOfSatisfying(Term.Bool.class, boolTerm -> { + assertThat(boolTerm.getValue()).isTrue(); + }); + }); + } catch (Exception e) { + fail("Failed to deserialize ListPoliciesResponse", e); + } + } + +} \ No newline at end of file diff --git a/opa-async-java-client/src/test/resources/com/contentgrid/opa/client/api/policyapi/listpoliciesresponse.json b/opa-async-java-client/src/test/resources/com/contentgrid/opa/client/api/policyapi/listpoliciesresponse.json new file mode 100644 index 0000000..d87f1f9 --- /dev/null +++ b/opa-async-java-client/src/test/resources/com/contentgrid/opa/client/api/policyapi/listpoliciesresponse.json @@ -0,0 +1,2433 @@ +{ + "result": [ + { + "id": "test", + "raw": "package test\n\nallow {\n input.method == \"GET\"\n input.path[0] == \"api\"\n input.path[1] == \"documents\"\n input.user.group[_] == data.documents[_].group\n}", + "ast": { + "package": { + "path": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "test" + } + ] + }, + "rules": [ + { + "body": [ + { + "index": 0, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "method" + } + ] + }, + { + "type": "string", + "value": "GET" + } + ] + }, + { + "index": 1, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "path" + }, + { + "type": "number", + "value": 0 + } + ] + }, + { + "type": "string", + "value": "api" + } + ] + }, + { + "index": 2, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "path" + }, + { + "type": "number", + "value": 1 + } + ] + }, + { + "type": "string", + "value": "documents" + } + ] + }, + { + "index": 3, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "user" + }, + { + "type": "string", + "value": "group" + }, + { + "type": "var", + "value": "$0" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "documents" + }, + { + "type": "var", + "value": "$1" + }, + { + "type": "string", + "value": "group" + } + ] + } + ] + } + ], + "head": { + "name": "allow", + "value": { + "type": "boolean", + "value": true + }, + "ref": [ + { + "type": "var", + "value": "allow" + } + ] + } + } + ] + } + }, + { + "id": "compile-example", + "raw": "package compile.example\n\nimport future.keywords.in\n\nallow {\n input.subject.clearance_level \u003e= data.reports[_].clearance_level\n}\n\nallow {\n input.break_glass == true\n}\n\ntest_set_in {\n num := input.entity.number\n num in {\"one\", \"two\", \"three\"}\n}", + "ast": { + "package": { + "path": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "compile" + }, + { + "type": "string", + "value": "example" + } + ] + }, + "rules": [ + { + "body": [ + { + "generated": true, + "index": 0, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local31__" + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "subject" + }, + { + "type": "string", + "value": "clearance_level" + } + ] + } + ] + }, + { + "generated": true, + "index": 1, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local32__" + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "reports" + }, + { + "type": "var", + "value": "$0" + }, + { + "type": "string", + "value": "clearance_level" + } + ] + } + ] + }, + { + "index": 2, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "gte" + } + ] + }, + { + "type": "var", + "value": "__local31__" + }, + { + "type": "var", + "value": "__local32__" + } + ] + } + ], + "head": { + "name": "allow", + "value": { + "type": "boolean", + "value": true + }, + "ref": [ + { + "type": "var", + "value": "allow" + } + ] + } + }, + { + "body": [ + { + "index": 0, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "break_glass" + } + ] + }, + { + "type": "boolean", + "value": true + } + ] + } + ], + "head": { + "name": "allow", + "value": { + "type": "boolean", + "value": true + }, + "ref": [ + { + "type": "var", + "value": "allow" + } + ] + } + }, + { + "body": [ + { + "index": 0, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local10__" + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "entity" + }, + { + "type": "string", + "value": "number" + } + ] + } + ] + }, + { + "index": 1, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "internal" + }, + { + "type": "string", + "value": "member_2" + } + ] + }, + { + "type": "var", + "value": "__local10__" + }, + { + "type": "set", + "value": [ + { + "type": "string", + "value": "one" + }, + { + "type": "string", + "value": "three" + }, + { + "type": "string", + "value": "two" + } + ] + } + ] + } + ], + "head": { + "name": "test_set_in", + "value": { + "type": "boolean", + "value": true + }, + "ref": [ + { + "type": "var", + "value": "test_set_in" + } + ] + } + } + ] + } + }, + { + "id": "abac-example", + "raw": "package abac.example\n\nutil.extract_content_type(header) := content_type {\n\tmime_type := trim_space(split(header, \";\")[0])\n\tcontent_type := lower(mime_type)\n}\ndefault util.content_type_in(headers, accepted_content_types) := false\n\nutil.content_type_in(headers, accepted_content_types) {\n\tcount(headers) == 1\n\textracted_mime_type := util.extract_content_type(headers[0])\n\textracted_mime_type == accepted_content_types[_]\n}\ndefault util.request.content_type_in(content_types) := false\n\nutil.request.content_type_in(content_types) {\n\tutil.content_type_in(input.request.headers[\"content-type\"], content_types)\n}\ndefault can_read_test := false\n\n# - input.entity is type 'test'\ncan_read_test {\n\tinput.auth.authenticated == true\n\tinput.auth.principal.kind == \"user\"\n\tinput.entity.a == input.auth.principal[\"contentgrid:att1\"][_]\n\tinput.entity.b == input.auth.principal[\"contentgrid:att2\"][_]\n}\n\n# End policy bprnvz6ldw4q\n# Policy nfgusztjwpba\n# - input.entity is type 'test'\ncan_read_test {\n\tinput.auth.authenticated == true\n\tinput.auth.principal.kind == \"user\"\n\tinput.auth.principal[\"contentgrid:admin\"] == true\n}\n\n# End policy nfgusztjwpba\ndefault allow := false\n\n# Static definition Application Root\nallow {\n\tinput.request.method == [\"HEAD\", \"GET\"][_]\n\t# Path /\n\tcount(input.request.path) == 0\n}\nallow {\n\tinput.request.method == [\"HEAD\", \"GET\"][_]\n\t# Path /tests\n\tcount(input.request.path) == 1\n\tinput.request.path[0] == \"tests\"\n\tcan_read_test == true\n}\nallow {\n\tinput.request.method == [\"HEAD\", \"GET\"][_]\n\t# Path /tests/{id}\n\tcount(input.request.path) == 2\n\tinput.request.path[0] == \"tests\"\n\t# variable component {id}\n\tcan_read_test == true\n}\n", + "ast": { + "package": { + "path": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "abac" + }, + { + "type": "string", + "value": "example" + } + ] + }, + "rules": [ + { + "body": [ + { + "generated": true, + "index": 0, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "split" + } + ] + }, + { + "type": "var", + "value": "__local0__" + }, + { + "type": "string", + "value": ";" + }, + { + "type": "var", + "value": "__local14__" + } + ] + }, + { + "generated": true, + "index": 1, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local25__" + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "__local14__" + }, + { + "type": "number", + "value": 0 + } + ] + } + ] + }, + { + "generated": true, + "index": 2, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "trim_space" + } + ] + }, + { + "type": "var", + "value": "__local25__" + }, + { + "type": "var", + "value": "__local15__" + } + ] + }, + { + "index": 3, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local1__" + }, + { + "type": "var", + "value": "__local15__" + } + ] + }, + { + "generated": true, + "index": 4, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "lower" + } + ] + }, + { + "type": "var", + "value": "__local1__" + }, + { + "type": "var", + "value": "__local16__" + } + ] + }, + { + "index": 5, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local2__" + }, + { + "type": "var", + "value": "__local16__" + } + ] + } + ], + "head": { + "args": [ + { + "type": "var", + "value": "__local0__" + } + ], + "value": { + "type": "var", + "value": "__local2__" + }, + "assign": true, + "ref": [ + { + "type": "var", + "value": "util" + }, + { + "type": "string", + "value": "extract_content_type" + } + ] + } + }, + { + "body": [ + { + "index": 0, + "terms": { + "type": "boolean", + "value": true + } + } + ], + "default": true, + "head": { + "args": [ + { + "type": "var", + "value": "__local3__" + }, + { + "type": "var", + "value": "__local4__" + } + ], + "value": { + "type": "boolean", + "value": false + }, + "assign": true, + "ref": [ + { + "type": "var", + "value": "util" + }, + { + "type": "string", + "value": "content_type_in" + } + ] + } + }, + { + "body": [ + { + "generated": true, + "index": 0, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "count" + } + ] + }, + { + "type": "var", + "value": "__local5__" + }, + { + "type": "var", + "value": "__local17__" + } + ] + }, + { + "index": 1, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local17__" + }, + { + "type": "number", + "value": 1 + } + ] + }, + { + "generated": true, + "index": 2, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local26__" + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "__local5__" + }, + { + "type": "number", + "value": 0 + } + ] + } + ] + }, + { + "generated": true, + "index": 3, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "abac" + }, + { + "type": "string", + "value": "example" + }, + { + "type": "string", + "value": "util" + }, + { + "type": "string", + "value": "extract_content_type" + } + ] + }, + { + "type": "var", + "value": "__local26__" + }, + { + "type": "var", + "value": "__local18__" + } + ] + }, + { + "index": 4, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local7__" + }, + { + "type": "var", + "value": "__local18__" + } + ] + }, + { + "index": 5, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local7__" + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "__local6__" + }, + { + "type": "var", + "value": "$0" + } + ] + } + ] + } + ], + "head": { + "args": [ + { + "type": "var", + "value": "__local5__" + }, + { + "type": "var", + "value": "__local6__" + } + ], + "value": { + "type": "boolean", + "value": true + }, + "ref": [ + { + "type": "var", + "value": "util" + }, + { + "type": "string", + "value": "content_type_in" + } + ] + } + }, + { + "body": [ + { + "index": 0, + "terms": { + "type": "boolean", + "value": true + } + } + ], + "default": true, + "head": { + "args": [ + { + "type": "var", + "value": "__local8__" + } + ], + "value": { + "type": "boolean", + "value": false + }, + "assign": true, + "ref": [ + { + "type": "var", + "value": "util" + }, + { + "type": "string", + "value": "request" + }, + { + "type": "string", + "value": "content_type_in" + } + ] + } + }, + { + "body": [ + { + "generated": true, + "index": 0, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local27__" + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "request" + }, + { + "type": "string", + "value": "headers" + }, + { + "type": "string", + "value": "content-type" + } + ] + } + ] + }, + { + "index": 1, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "abac" + }, + { + "type": "string", + "value": "example" + }, + { + "type": "string", + "value": "util" + }, + { + "type": "string", + "value": "content_type_in" + } + ] + }, + { + "type": "var", + "value": "__local27__" + }, + { + "type": "var", + "value": "__local9__" + } + ] + } + ], + "head": { + "args": [ + { + "type": "var", + "value": "__local9__" + } + ], + "value": { + "type": "boolean", + "value": true + }, + "ref": [ + { + "type": "var", + "value": "util" + }, + { + "type": "string", + "value": "request" + }, + { + "type": "string", + "value": "content_type_in" + } + ] + } + }, + { + "body": [ + { + "index": 0, + "terms": { + "type": "boolean", + "value": true + } + } + ], + "default": true, + "head": { + "name": "can_read_test", + "value": { + "type": "boolean", + "value": false + }, + "assign": true, + "ref": [ + { + "type": "var", + "value": "can_read_test" + } + ] + } + }, + { + "body": [ + { + "index": 0, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "auth" + }, + { + "type": "string", + "value": "authenticated" + } + ] + }, + { + "type": "boolean", + "value": true + } + ] + }, + { + "index": 1, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "auth" + }, + { + "type": "string", + "value": "principal" + }, + { + "type": "string", + "value": "kind" + } + ] + }, + { + "type": "string", + "value": "user" + } + ] + }, + { + "index": 2, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "entity" + }, + { + "type": "string", + "value": "a" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "auth" + }, + { + "type": "string", + "value": "principal" + }, + { + "type": "string", + "value": "contentgrid:att1" + }, + { + "type": "var", + "value": "$1" + } + ] + } + ] + }, + { + "index": 3, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "entity" + }, + { + "type": "string", + "value": "b" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "auth" + }, + { + "type": "string", + "value": "principal" + }, + { + "type": "string", + "value": "contentgrid:att2" + }, + { + "type": "var", + "value": "$2" + } + ] + } + ] + } + ], + "head": { + "name": "can_read_test", + "value": { + "type": "boolean", + "value": true + }, + "ref": [ + { + "type": "var", + "value": "can_read_test" + } + ] + } + }, + { + "body": [ + { + "index": 0, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "auth" + }, + { + "type": "string", + "value": "authenticated" + } + ] + }, + { + "type": "boolean", + "value": true + } + ] + }, + { + "index": 1, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "auth" + }, + { + "type": "string", + "value": "principal" + }, + { + "type": "string", + "value": "kind" + } + ] + }, + { + "type": "string", + "value": "user" + } + ] + }, + { + "index": 2, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "auth" + }, + { + "type": "string", + "value": "principal" + }, + { + "type": "string", + "value": "contentgrid:admin" + } + ] + }, + { + "type": "boolean", + "value": true + } + ] + } + ], + "head": { + "name": "can_read_test", + "value": { + "type": "boolean", + "value": true + }, + "ref": [ + { + "type": "var", + "value": "can_read_test" + } + ] + } + }, + { + "body": [ + { + "index": 0, + "terms": { + "type": "boolean", + "value": true + } + } + ], + "default": true, + "head": { + "name": "allow", + "value": { + "type": "boolean", + "value": false + }, + "assign": true, + "ref": [ + { + "type": "var", + "value": "allow" + } + ] + } + }, + { + "body": [ + { + "generated": true, + "index": 0, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local19__" + }, + { + "type": "array", + "value": [ + { + "type": "string", + "value": "HEAD" + }, + { + "type": "string", + "value": "GET" + } + ] + } + ] + }, + { + "index": 1, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "request" + }, + { + "type": "string", + "value": "method" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "__local19__" + }, + { + "type": "var", + "value": "$3" + } + ] + } + ] + }, + { + "generated": true, + "index": 2, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local28__" + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "request" + }, + { + "type": "string", + "value": "path" + } + ] + } + ] + }, + { + "generated": true, + "index": 3, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "count" + } + ] + }, + { + "type": "var", + "value": "__local28__" + }, + { + "type": "var", + "value": "__local20__" + } + ] + }, + { + "index": 4, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local20__" + }, + { + "type": "number", + "value": 0 + } + ] + } + ], + "head": { + "name": "allow", + "value": { + "type": "boolean", + "value": true + }, + "ref": [ + { + "type": "var", + "value": "allow" + } + ] + } + }, + { + "body": [ + { + "generated": true, + "index": 0, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local21__" + }, + { + "type": "array", + "value": [ + { + "type": "string", + "value": "HEAD" + }, + { + "type": "string", + "value": "GET" + } + ] + } + ] + }, + { + "index": 1, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "request" + }, + { + "type": "string", + "value": "method" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "__local21__" + }, + { + "type": "var", + "value": "$4" + } + ] + } + ] + }, + { + "generated": true, + "index": 2, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local29__" + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "request" + }, + { + "type": "string", + "value": "path" + } + ] + } + ] + }, + { + "generated": true, + "index": 3, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "count" + } + ] + }, + { + "type": "var", + "value": "__local29__" + }, + { + "type": "var", + "value": "__local22__" + } + ] + }, + { + "index": 4, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local22__" + }, + { + "type": "number", + "value": 1 + } + ] + }, + { + "index": 5, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "request" + }, + { + "type": "string", + "value": "path" + }, + { + "type": "number", + "value": 0 + } + ] + }, + { + "type": "string", + "value": "tests" + } + ] + }, + { + "index": 6, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "abac" + }, + { + "type": "string", + "value": "example" + }, + { + "type": "string", + "value": "can_read_test" + } + ] + }, + { + "type": "boolean", + "value": true + } + ] + } + ], + "head": { + "name": "allow", + "value": { + "type": "boolean", + "value": true + }, + "ref": [ + { + "type": "var", + "value": "allow" + } + ] + } + }, + { + "body": [ + { + "generated": true, + "index": 0, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local23__" + }, + { + "type": "array", + "value": [ + { + "type": "string", + "value": "HEAD" + }, + { + "type": "string", + "value": "GET" + } + ] + } + ] + }, + { + "index": 1, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "request" + }, + { + "type": "string", + "value": "method" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "__local23__" + }, + { + "type": "var", + "value": "$5" + } + ] + } + ] + }, + { + "generated": true, + "index": 2, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local30__" + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "request" + }, + { + "type": "string", + "value": "path" + } + ] + } + ] + }, + { + "generated": true, + "index": 3, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "count" + } + ] + }, + { + "type": "var", + "value": "__local30__" + }, + { + "type": "var", + "value": "__local24__" + } + ] + }, + { + "index": 4, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local24__" + }, + { + "type": "number", + "value": 2 + } + ] + }, + { + "index": 5, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "input" + }, + { + "type": "string", + "value": "request" + }, + { + "type": "string", + "value": "path" + }, + { + "type": "number", + "value": 0 + } + ] + }, + { + "type": "string", + "value": "tests" + } + ] + }, + { + "index": 6, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "abac" + }, + { + "type": "string", + "value": "example" + }, + { + "type": "string", + "value": "can_read_test" + } + ] + }, + { + "type": "boolean", + "value": true + } + ] + } + ], + "head": { + "name": "allow", + "value": { + "type": "boolean", + "value": true + }, + "ref": [ + { + "type": "var", + "value": "allow" + } + ] + } + } + ], + "comments": [ + { + "Text": "IC0gaW5wdXQuZW50aXR5IGlzIHR5cGUgJ3Rlc3Qn", + "Location": { + "file": "abac-example", + "row": 21, + "col": 1 + } + }, + { + "Text": "IEVuZCBwb2xpY3kgYnBybnZ6NmxkdzRx", + "Location": { + "file": "abac-example", + "row": 29, + "col": 1 + } + }, + { + "Text": "IFBvbGljeSBuZmd1c3p0andwYmE=", + "Location": { + "file": "abac-example", + "row": 30, + "col": 1 + } + }, + { + "Text": "IC0gaW5wdXQuZW50aXR5IGlzIHR5cGUgJ3Rlc3Qn", + "Location": { + "file": "abac-example", + "row": 31, + "col": 1 + } + }, + { + "Text": "IEVuZCBwb2xpY3kgbmZndXN6dGp3cGJh", + "Location": { + "file": "abac-example", + "row": 38, + "col": 1 + } + }, + { + "Text": "IFN0YXRpYyBkZWZpbml0aW9uIEFwcGxpY2F0aW9uIFJvb3Q=", + "Location": { + "file": "abac-example", + "row": 41, + "col": 1 + } + }, + { + "Text": "IFBhdGggLw==", + "Location": { + "file": "abac-example", + "row": 44, + "col": 2 + } + }, + { + "Text": "IFBhdGggL3Rlc3Rz", + "Location": { + "file": "abac-example", + "row": 49, + "col": 2 + } + }, + { + "Text": "IFBhdGggL3Rlc3RzL3tpZH0=", + "Location": { + "file": "abac-example", + "row": 56, + "col": 2 + } + }, + { + "Text": "IHZhcmlhYmxlIGNvbXBvbmVudCB7aWR9", + "Location": { + "file": "abac-example", + "row": 59, + "col": 2 + } + } + ] + } + }, + { + "id": "example1", + "raw": "package opa.examples\n\nimport data.servers\nimport data.networks\nimport data.ports\n\npublic_servers[server] {\n some k, m\n server := servers[_]\n server.ports[_] == ports[k].id\n ports[k].networks[_] == networks[m].id\n networks[m].public == true\n}", + "ast": { + "package": { + "path": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "opa" + }, + { + "type": "string", + "value": "examples" + } + ] + }, + "rules": [ + { + "body": [ + { + "index": 0, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "var", + "value": "__local13__" + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "servers" + }, + { + "type": "var", + "value": "$0" + } + ] + } + ] + }, + { + "index": 1, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "__local13__" + }, + { + "type": "string", + "value": "ports" + }, + { + "type": "var", + "value": "$1" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "ports" + }, + { + "type": "var", + "value": "__local11__" + }, + { + "type": "string", + "value": "id" + } + ] + } + ] + }, + { + "index": 2, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "ports" + }, + { + "type": "var", + "value": "__local11__" + }, + { + "type": "string", + "value": "networks" + }, + { + "type": "var", + "value": "$2" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "networks" + }, + { + "type": "var", + "value": "__local12__" + }, + { + "type": "string", + "value": "id" + } + ] + } + ] + }, + { + "index": 3, + "terms": [ + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "eq" + } + ] + }, + { + "type": "ref", + "value": [ + { + "type": "var", + "value": "data" + }, + { + "type": "string", + "value": "networks" + }, + { + "type": "var", + "value": "__local12__" + }, + { + "type": "string", + "value": "public" + } + ] + }, + { + "type": "boolean", + "value": true + } + ] + } + ], + "head": { + "name": "public_servers", + "key": { + "type": "var", + "value": "__local13__" + }, + "ref": [ + { + "type": "var", + "value": "public_servers" + } + ] + } + } + ] + } + } + ] +} diff --git a/opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/compile/abac-policy.rego b/opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/compile/abac-policy.rego new file mode 100644 index 0000000..e2748e8 --- /dev/null +++ b/opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/compile/abac-policy.rego @@ -0,0 +1,61 @@ +package abac.example + +util.extract_content_type(header) := content_type { + mime_type := trim_space(split(header, ";")[0]) + content_type := lower(mime_type) +} +default util.content_type_in(headers, accepted_content_types) := false + +util.content_type_in(headers, accepted_content_types) { + count(headers) == 1 + extracted_mime_type := util.extract_content_type(headers[0]) + extracted_mime_type == accepted_content_types[_] +} +default util.request.content_type_in(content_types) := false + +util.request.content_type_in(content_types) { + util.content_type_in(input.request.headers["content-type"], content_types) +} +default can_read_test := false + +# - input.entity is type 'test' +can_read_test { + input.auth.authenticated == true + input.auth.principal.kind == "user" + input.entity.a == input.auth.principal["contentgrid:att1"][_] + input.entity.b == input.auth.principal["contentgrid:att2"][_] +} + +# End policy bprnvz6ldw4q +# Policy nfgusztjwpba +# - input.entity is type 'test' +can_read_test { + input.auth.authenticated == true + input.auth.principal.kind == "user" + input.auth.principal["contentgrid:admin"] == true +} + +# End policy nfgusztjwpba +default allow := false + +# Static definition Application Root +allow { + input.request.method == ["HEAD", "GET"][_] + # Path / + count(input.request.path) == 0 +} +allow { + input.request.method == ["HEAD", "GET"][_] + # Path /tests + count(input.request.path) == 1 + input.request.path[0] == "tests" + can_read_test == true +} +allow { + input.request.method == ["HEAD", "GET"][_] + # Path /tests/{id} + count(input.request.path) == 2 + input.request.path[0] == "tests" + # variable component {id} + can_read_test == true +} diff --git a/opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/compile/compile-api-policy.txt b/opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/compile/compile-api-policy.rego similarity index 100% rename from opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/compile/compile-api-policy.txt rename to opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/compile/compile-api-policy.rego diff --git a/opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/policy/policies-example-1.txt b/opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/policy/policies-example-1.rego similarity index 100% rename from opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/policy/policies-example-1.txt rename to opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/policy/policies-example-1.rego diff --git a/opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/policy/policies-example-2.txt b/opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/policy/policies-example-2.rego similarity index 100% rename from opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/policy/policies-example-2.txt rename to opa-async-java-client/src/test/resources/fixtures/openpolicyagent.org/docs/policy/policies-example-2.rego diff --git a/opa-async-java-client/src/test/resources/fixtures/scenarios/api-documents-policy.txt b/opa-async-java-client/src/test/resources/fixtures/scenarios/api-documents-policy.rego similarity index 100% rename from opa-async-java-client/src/test/resources/fixtures/scenarios/api-documents-policy.txt rename to opa-async-java-client/src/test/resources/fixtures/scenarios/api-documents-policy.rego diff --git a/rego-java/src/main/java/com/contentgrid/opa/rego/ast/Expression.java b/rego-java/src/main/java/com/contentgrid/opa/rego/ast/Expression.java index d76c69a..e6473d8 100644 --- a/rego-java/src/main/java/com/contentgrid/opa/rego/ast/Expression.java +++ b/rego-java/src/main/java/com/contentgrid/opa/rego/ast/Expression.java @@ -1,5 +1,6 @@ package com.contentgrid.opa.rego.ast; +import com.fasterxml.jackson.annotation.JsonFormat; import java.util.List; import lombok.AllArgsConstructor; import lombok.Data; @@ -10,6 +11,8 @@ @AllArgsConstructor public class Expression implements Node { long index; + + @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) List terms; public Expression(long index, Term ... terms) {