From 5867981fd6036db7351ac07541d760f4b1625e1a Mon Sep 17 00:00:00 2001 From: "J.R. Hill" Date: Tue, 28 Nov 2023 14:38:33 -0800 Subject: [PATCH 1/7] feat(java-sdk): support ABAC model changes in Java SDK --- .../OpenFgaApiIntegrationTest.java.mustache | 33 +++-- .../template/OpenFgaApiTest.java.mustache | 56 ++++---- .../client-ClientAssertion.java.mustache | 5 +- .../client-ClientTupleKey.java.mustache | 31 ---- .../client-HttpRequestAttempt.java.mustache | 44 ++++++ .../client-OpenFgaClient.java.mustache | 89 +++++++++--- ...OpenFgaClientIntegrationTest.java.mustache | 8 +- .../client-OpenFgaClientTest.java.mustache | 136 ++++++++++-------- .../creds-OAuth2ClientTest.java.mustache | 3 + 9 files changed, 247 insertions(+), 158 deletions(-) diff --git a/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache b/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache index c172204f..deeb8a90 100644 --- a/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache +++ b/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache @@ -29,6 +29,8 @@ public class OpenFgaApiIntegrationTest { @BeforeEach public void initializeApi() throws Exception { + System.setProperty("HttpRequestAttempt.debug-logging", "enable"); + Configuration apiConfig = new Configuration().apiUrl("http://localhost:8080"); api = new OpenFgaApi(apiConfig); } @@ -112,7 +114,7 @@ public class OpenFgaApiIntegrationTest { assertEquals(authModelId, authModel.getId()); String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions()); assertEquals( - "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]}}}}]", + "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]}}}}]", typeDefsJson); } @@ -136,7 +138,7 @@ public class OpenFgaApiIntegrationTest { String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions()); assertEquals( - "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]}}}}]", + "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]}}}}]", typeDefsJson); } catch (JsonProcessingException ex) { assertNull(ex); @@ -168,9 +170,9 @@ public class OpenFgaApiIntegrationTest { String storeName = thisTestName(); String storeId = createStore(storeName); String _authModelId = writeAuthModel(storeId); - WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(List.of(DEFAULT_TUPLE_KEY))); + WriteRequest writeRequest = new WriteRequest().writes(new WriteRequestWrites().tupleKeys(List.of(DEFAULT_TUPLE_KEY))); ReadRequest readRequest = - new ReadRequest().tupleKey(new TupleKey().user(DEFAULT_USER)._object(DEFAULT_DOC)); + new ReadRequest().tupleKey(new ReadRequestTupleKey().user(DEFAULT_USER)._object(DEFAULT_DOC)); // When api.write(storeId, writeRequest).get(); @@ -189,9 +191,9 @@ public class OpenFgaApiIntegrationTest { String storeName = thisTestName(); String storeId = createStore(storeName); String _authModelId = writeAuthModel(storeId); - WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(DEFAULT_TUPLE_KEYS)); + WriteRequest writeRequest = new WriteRequest().writes(new WriteRequestWrites().tupleKeys(DEFAULT_TUPLE_KEYS)); CheckRequest checkRequest = new CheckRequest() - .tupleKey(new TupleKey().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC)); + .tupleKey(new CheckRequestTupleKey().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC)); // When api.write(storeId, writeRequest).get(); @@ -207,9 +209,9 @@ public class OpenFgaApiIntegrationTest { String storeName = thisTestName(); String storeId = createStore(storeName); String _authModelId = writeAuthModel(storeId); - WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(DEFAULT_TUPLE_KEYS)); + WriteRequest writeRequest = new WriteRequest().writes(new WriteRequestWrites().tupleKeys(DEFAULT_TUPLE_KEYS)); ExpandRequest expandRequest = - new ExpandRequest().tupleKey(new TupleKey()._object(DEFAULT_DOC).relation("reader")); + new ExpandRequest().tupleKey(new ExpandRequestTupleKey()._object(DEFAULT_DOC).relation("reader")); // When api.write(storeId, writeRequest).get(); @@ -229,7 +231,7 @@ public class OpenFgaApiIntegrationTest { String storeName = thisTestName(); String storeId = createStore(storeName); String _authModelId = writeAuthModel(storeId); - WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(DEFAULT_TUPLE_KEYS)); + WriteRequest writeRequest = new WriteRequest().writes(new WriteRequestWrites().tupleKeys(DEFAULT_TUPLE_KEYS)); ListObjectsRequest listObjectsRequest = new ListObjectsRequest().user(DEFAULT_USER).relation("reader").type("document"); @@ -249,7 +251,7 @@ public class OpenFgaApiIntegrationTest { String storeName = thisTestName(); String storeId = createStore(storeName); String _authModelId = writeAuthModel(storeId); - WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(DEFAULT_TUPLE_KEYS)); + WriteRequest writeRequest = new WriteRequest().writes(new WriteRequestWrites().tupleKeys(DEFAULT_TUPLE_KEYS)); // When api.write(storeId, writeRequest).get(); @@ -261,7 +263,7 @@ public class OpenFgaApiIntegrationTest { String firstTupleKeyJson = mapper.writeValueAsString(response.getChanges().get(0).getTupleKey()); assertEquals( - "{\"object\":\"document:2021-budget\",\"relation\":\"reader\",\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\"}", + "{\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\",\"relation\":\"reader\",\"object\":\"document:2021-budget\",\"condition\":null}", firstTupleKeyJson); } @@ -272,7 +274,12 @@ public class OpenFgaApiIntegrationTest { String storeId = createStore(storeName); String authModelId = writeAuthModel(storeId); WriteAssertionsRequest writeRequest = new WriteAssertionsRequest() - .assertions(List.of(new Assertion().tupleKey(DEFAULT_TUPLE_KEY).expectation(true))); + .assertions(List.of(new Assertion() + .tupleKey(new CheckRequestTupleKey() + .user(DEFAULT_USER) + .relation("reader") + ._object(DEFAULT_DOC)) + .expectation(true))); // When api.writeAssertions(storeId, authModelId, writeRequest).get(); @@ -282,7 +289,7 @@ public class OpenFgaApiIntegrationTest { // Then String responseJson = mapper.writeValueAsString(response.getAssertions()); assertEquals( - "[{\"tuple_key\":{\"object\":\"document:2021-budget\",\"relation\":\"reader\",\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\"},\"expectation\":true}]", + "[{\"tuple_key\":{\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\",\"relation\":\"reader\",\"object\":\"document:2021-budget\"},\"expectation\":true}]", responseJson); } diff --git a/config/clients/java/template/OpenFgaApiTest.java.mustache b/config/clients/java/template/OpenFgaApiTest.java.mustache index 4c9b73b0..8f52b83d 100644 --- a/config/clients/java/template/OpenFgaApiTest.java.mustache +++ b/config/clients/java/template/OpenFgaApiTest.java.mustache @@ -44,7 +44,10 @@ public class OpenFgaApiTest { @BeforeEach public void beforeEachTest() throws Exception { + System.setProperty("HttpRequestAttempt.debug-logging", "enable"); + mockHttpClient = new HttpClientMock(); + mockHttpClient.debugOn(); mockHttpClientBuilder = mock(HttpClient.Builder.class); when(mockHttpClientBuilder.executor(any())).thenReturn(mockHttpClientBuilder); @@ -560,7 +563,7 @@ public class OpenFgaApiTest { // Given String postUrl = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models"; String expectedBody = - "{\"type_definitions\":[{\"type\":\"document\",\"relations\":{},\"metadata\":null}],\"schema_version\":\"1.1\"}"; + "{\"type_definitions\":[{\"type\":\"document\",\"relations\":{},\"metadata\":null}],\"schema_version\":\"1.1\",\"conditions\":{}}"; String responseBody = String.format("{\"authorization_model_id\":\"%s\"}", DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(201, responseBody); WriteAuthorizationModelRequest request = new WriteAuthorizationModelRequest() @@ -793,8 +796,8 @@ public class OpenFgaApiTest { // Given String getPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/changes"; String responseBody = String.format( - "{\"changes\":[{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}}]}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"changes\":[{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}}]}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); mockHttpClient.onGet(getPath).doReturn(200, responseBody); String type = null; // Input is optional Integer pageSize = null; // Input is optional @@ -909,14 +912,14 @@ public class OpenFgaApiTest { // Given String postUrl = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/read"; String expectedBody = String.format( - "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"page_size\":null,\"continuation_token\":null}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"page_size\":null,\"continuation_token\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); String responseBody = String.format( "{\"tuples\":[{\"key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}}]}", DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, responseBody); ReadRequest request = new ReadRequest() - .tupleKey(new TupleKey() + .tupleKey(new ReadRequestTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER)); @@ -1031,12 +1034,12 @@ public class OpenFgaApiTest { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write"; String expectedBody = String.format( - "{\"writes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID); + "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); WriteRequest request = new WriteRequest() .authorizationModelId(DEFAULT_AUTH_MODEL_ID) - .writes(new TupleKeys() + .writes(new WriteRequestWrites() .tupleKeys(List.of(new TupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) @@ -1057,13 +1060,13 @@ public class OpenFgaApiTest { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write"; String expectedBody = String.format( - "{\"writes\":null,\"deletes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"authorization_model_id\":\"%s\"}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID); + "{\"writes\":null,\"deletes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}]},\"authorization_model_id\":\"%s\"}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); WriteRequest request = new WriteRequest() .authorizationModelId(DEFAULT_AUTH_MODEL_ID) - .deletes(new TupleKeys() - .tupleKeys(List.of(new TupleKey() + .deletes(new WriteRequestDeletes() + .tupleKeys(List.of(new TupleKeyWithoutCondition() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER)))); @@ -1171,11 +1174,11 @@ public class OpenFgaApiTest { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check"; String expectedBody = String.format( - "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":{\"tuple_keys\":[]},\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":{\"tuple_keys\":[]},\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}"); CheckRequest request = new CheckRequest() - .tupleKey(new TupleKey() + .tupleKey(new CheckRequestTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER)) @@ -1289,18 +1292,15 @@ public class OpenFgaApiTest { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/expand"; String expectedBody = String.format( - "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"authorization_model_id\":\"%s\"}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID); + "{\"tuple_key\":{\"relation\":\"%s\",\"object\":\"%s\"},\"authorization_model_id\":\"%s\"}", + DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); String responseBody = String.format( "{\"tree\":{\"root\":{\"union\":{\"nodes\":[{\"leaf\":{\"users\":{\"users\":[\"%s\"]}}}]}}}}", DEFAULT_USER); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, responseBody); ExpandRequest request = new ExpandRequest() .authorizationModelId(DEFAULT_AUTH_MODEL_ID) - .tupleKey(new TupleKey() - ._object(DEFAULT_OBJECT) - .relation(DEFAULT_RELATION) - .user(DEFAULT_USER)); + .tupleKey(new ExpandRequestTupleKey()._object(DEFAULT_OBJECT).relation(DEFAULT_RELATION)); // When var response = fga.expand(DEFAULT_STORE_ID, request).get(); @@ -1421,7 +1421,7 @@ public class OpenFgaApiTest { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/list-objects"; String expectedBody = String.format( - "{\"authorization_model_id\":\"%s\",\"type\":null,\"relation\":\"%s\",\"user\":\"%s\",\"contextual_tuples\":null}", + "{\"authorization_model_id\":\"%s\",\"type\":null,\"relation\":\"%s\",\"user\":\"%s\",\"contextual_tuples\":null,\"context\":null}", DEFAULT_AUTH_MODEL_ID, DEFAULT_RELATION, DEFAULT_USER); mockHttpClient .onPost(postPath) @@ -1537,8 +1537,8 @@ public class OpenFgaApiTest { // Given String getUrl = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/assertions/01G5JAVJ41T49E9TT3SKVS7X1J"; String responseBody = String.format( - "{\"assertions\":[{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"expectation\":true}]}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"assertions\":[{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"expectation\":true}]}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); mockHttpClient.onGet(getUrl).doReturn(200, responseBody); // When @@ -1657,12 +1657,12 @@ public class OpenFgaApiTest { // Given String putUrl = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/assertions/01G5JAVJ41T49E9TT3SKVS7X1J"; String expectedBody = String.format( - "{\"assertions\":[{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"expectation\":true}]}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"assertions\":[{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"expectation\":true}]}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); mockHttpClient.onPut(putUrl).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); WriteAssertionsRequest request = new WriteAssertionsRequest() .assertions(List.of(new Assertion() - .tupleKey(new TupleKey() + .tupleKey(new CheckRequestTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER)) diff --git a/config/clients/java/template/client-ClientAssertion.java.mustache b/config/clients/java/template/client-ClientAssertion.java.mustache index 8624fcd2..e3ded689 100644 --- a/config/clients/java/template/client-ClientAssertion.java.mustache +++ b/config/clients/java/template/client-ClientAssertion.java.mustache @@ -2,8 +2,7 @@ package {{invokerPackage}}; import {{modelPackage}}.Assertion; -import {{modelPackage}}.TupleKey; - +import {{modelPackage}}.CheckRequestTupleKey; import java.util.List; import java.util.stream.Collectors; @@ -62,7 +61,7 @@ public class ClientAssertion { } public Assertion asAssertion() { - TupleKey tupleKey = new TupleKey().user(user).relation(relation)._object(_object); + var tupleKey = new CheckRequestTupleKey().user(user).relation(relation)._object(_object); return new Assertion().tupleKey(tupleKey).expectation(expectation); } diff --git a/config/clients/java/template/client-ClientTupleKey.java.mustache b/config/clients/java/template/client-ClientTupleKey.java.mustache index c380b0ed..d424a020 100644 --- a/config/clients/java/template/client-ClientTupleKey.java.mustache +++ b/config/clients/java/template/client-ClientTupleKey.java.mustache @@ -1,13 +1,6 @@ {{>licenseInfo}} package {{invokerPackage}}; -import {{modelPackage}}.ContextualTupleKeys; -import {{modelPackage}}.TupleKey; -import {{modelPackage}}.TupleKeys; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - public class ClientTupleKey { private String user; private String relation; @@ -51,28 +44,4 @@ public class ClientTupleKey { public String getUser() { return user; } - - public TupleKey asTupleKey() { - return new TupleKey().user(user).relation(relation)._object(_object); - } - - public static Optional asTupleKeys(List clientTupleKeys) { - if (clientTupleKeys == null || clientTupleKeys.size() == 0) { - return Optional.empty(); - } - - return Optional.of(new TupleKeys().tupleKeys(asListOfTupleKey(clientTupleKeys))); - } - - public static ContextualTupleKeys asContextualTupleKeys(List clientTupleKeys) { - if (clientTupleKeys == null || clientTupleKeys.size() == 0) { - return new ContextualTupleKeys(); - } - - return new ContextualTupleKeys().tupleKeys(asListOfTupleKey(clientTupleKeys)); - } - - private static List asListOfTupleKey(List clientTupleKeys) { - return clientTupleKeys.stream().map(ClientTupleKey::asTupleKey).collect(Collectors.toList()); - } } diff --git a/config/clients/java/template/client-HttpRequestAttempt.java.mustache b/config/clients/java/template/client-HttpRequestAttempt.java.mustache index 64af3963..f2852e24 100644 --- a/config/clients/java/template/client-HttpRequestAttempt.java.mustache +++ b/config/clients/java/template/client-HttpRequestAttempt.java.mustache @@ -4,10 +4,13 @@ import static {{utilPackage}}.StringUtil.isNullOrWhitespace; import {{configPackage}}.Configuration; import {{errorsPackage}}.*; import java.io.IOException; +import java.io.PrintStream; import java.net.HttpURLConnection; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Optional; import java.util.concurrent.*; @@ -19,6 +22,9 @@ public class HttpRequestAttempt { private final String name; private final HttpRequest request; + // Intended for only testing the OpenFGA SDK itself. + private final boolean enableDebugLogging = "enable".equals(System.getProperty("HttpRequestAttempt.debug-logging")); + public HttpRequestAttempt( HttpRequest request, String name, Class clazz, ApiClient apiClient, Configuration configuration) throws FgaInvalidParameterException { @@ -33,6 +39,11 @@ public class HttpRequestAttempt { } public CompletableFuture> attemptHttpRequest() throws ApiException { + if (enableDebugLogging) { + request.bodyPublisher() + .ifPresent(requestBodyPublisher -> + requestBodyPublisher.subscribe(new BodyLogger(System.err, "request"))); + } int retryNumber = 0; return attemptHttpRequest(apiClient.getHttpClient(), retryNumber, null); } @@ -83,4 +94,37 @@ public class HttpRequestAttempt { .executor(CompletableFuture.delayedExecutor(retryDelay.toNanos(), TimeUnit.NANOSECONDS)) .build(); } + + private static class BodyLogger implements Flow.Subscriber { + private final PrintStream out; + private final String target; + + BodyLogger(PrintStream out, String target) { + this.out = out; + this.target = target; + } + + @Override + public void onSubscribe(Flow.Subscription subscription) { + out.printf("[%s] subscribed: %s\n", this.getClass().getName(), subscription); + subscription.request(Long.MAX_VALUE); + } + + @Override + public void onNext(ByteBuffer item) { + out.printf( + "[%s] %s: %s\n", + this.getClass().getName(), target, new String(item.array(), StandardCharsets.UTF_8)); + } + + @Override + public void onError(Throwable throwable) { + out.printf("[%s] error: %s\n", this.getClass().getName(), throwable); + } + + @Override + public void onComplete() { + out.flush(); + } + } } diff --git a/config/clients/java/template/client-OpenFgaClient.java.mustache b/config/clients/java/template/client-OpenFgaClient.java.mustache index 2c28bf44..00068e47 100644 --- a/config/clients/java/template/client-OpenFgaClient.java.mustache +++ b/config/clients/java/template/client-OpenFgaClient.java.mustache @@ -233,9 +233,10 @@ public class OpenFgaClient { ReadRequest body = new ReadRequest(); if (request != null && (request.getUser() != null || request.getRelation() != null || request.getObject() != null)) { - TupleKey tupleKey = new TupleKey(); - tupleKey.user(request.getUser()).relation(request.getRelation())._object(request.getObject()); - body.tupleKey(tupleKey); + body.tupleKey(new ReadRequestTupleKey() + .user(request.getUser()) + .relation(request.getRelation()) + ._object(request.getObject())); } if (options != null) { @@ -277,8 +278,27 @@ public class OpenFgaClient { WriteRequest body = new WriteRequest(); - ClientTupleKey.asTupleKeys(request.getWrites()).ifPresent(body::writes); - ClientTupleKey.asTupleKeys(request.getDeletes()).ifPresent(body::deletes); + var writeTuples = request.getWrites(); + if (writeTuples != null && !writeTuples.isEmpty()) { + body.writes(new WriteRequestWrites() + .tupleKeys(writeTuples.stream() + .map(writeTuple -> new TupleKey() + .user(writeTuple.getUser()) + .relation(writeTuple.getRelation()) + ._object(writeTuple.getObject())) + .collect(Collectors.toList()))); + } + + var deleteTuples = request.getDeletes(); + if (deleteTuples != null && !deleteTuples.isEmpty()) { + body.deletes(new WriteRequestDeletes() + .tupleKeys(deleteTuples.stream() + .map(deleteTuple -> new TupleKeyWithoutCondition() + .user(deleteTuple.getUser()) + .relation(deleteTuple.getRelation()) + ._object(deleteTuple.getObject())) + .collect(Collectors.toList()))); + } if (options != null && !isNullOrWhitespace(options.getAuthorizationModelId())) { body.authorizationModelId(options.getAuthorizationModelId()); @@ -342,15 +362,22 @@ public class OpenFgaClient { configuration.assertValid(); String storeId = configuration.getStoreIdChecked(); - var request = new WriteRequest(); - ClientTupleKey.asTupleKeys(tupleKeys).ifPresent(request::writes); + var body = new WriteRequest(); + + body.writes(new WriteRequestWrites() + .tupleKeys(tupleKeys.stream() + .map(writeTuple -> new TupleKey() + .user(writeTuple.getUser()) + .relation(writeTuple.getRelation()) + ._object(writeTuple.getObject())) + .collect(Collectors.toList()))); String authorizationModelId = configuration.getAuthorizationModelId(); if (!isNullOrWhitespace(authorizationModelId)) { - request.authorizationModelId(authorizationModelId); + body.authorizationModelId(authorizationModelId); } - return call(() -> api.write(storeId, request)).thenApply(ClientWriteResponse::new); + return call(() -> api.write(storeId, body)).thenApply(ClientWriteResponse::new); } /** @@ -363,15 +390,22 @@ public class OpenFgaClient { configuration.assertValid(); String storeId = configuration.getStoreIdChecked(); - var request = new WriteRequest(); - ClientTupleKey.asTupleKeys(tupleKeys).ifPresent(request::deletes); + var body = new WriteRequest(); + + body.deletes(new WriteRequestDeletes() + .tupleKeys(tupleKeys.stream() + .map(deleteTuple -> new TupleKeyWithoutCondition() + .user(deleteTuple.getUser()) + .relation(deleteTuple.getRelation()) + ._object(deleteTuple.getObject())) + .collect(Collectors.toList()))); String authorizationModelId = configuration.getAuthorizationModelId(); if (!isNullOrWhitespace(authorizationModelId)) { - request.authorizationModelId(authorizationModelId); + body.authorizationModelId(authorizationModelId); } - return call(() -> api.write(storeId, request)).thenApply(ClientWriteResponse::new); + return call(() -> api.write(storeId, body)).thenApply(ClientWriteResponse::new); } /* ********************** @@ -401,14 +435,20 @@ public class OpenFgaClient { CheckRequest body = new CheckRequest(); if (request != null) { - body.tupleKey(new TupleKey() + body.tupleKey(new CheckRequestTupleKey() .user(request.getUser()) .relation(request.getRelation()) ._object(request.getObject())); var contextualTuples = request.getContextualTuples(); if (contextualTuples != null && !contextualTuples.isEmpty()) { - body.contextualTuples(ClientTupleKey.asContextualTupleKeys(contextualTuples)); + body.contextualTuples(new ContextualTupleKeys() + .tupleKeys(contextualTuples.stream() + .map(contextualTuple -> new TupleKey() + ._object(contextualTuple.getObject()) + .relation(contextualTuple.getRelation()) + .user(contextualTuple.getUser())) + .collect(Collectors.toList()))); } } @@ -480,9 +520,8 @@ public class OpenFgaClient { ExpandRequest body = new ExpandRequest(); if (request != null) { - body.tupleKey(new TupleKey() - .relation(request.getRelation()) - ._object(request.getObject())); + body.tupleKey( + new ExpandRequestTupleKey().relation(request.getRelation())._object(request.getObject())); } if (options != null && !isNullOrWhitespace(options.getAuthorizationModelId())) { @@ -518,10 +557,16 @@ public class OpenFgaClient { ListObjectsRequest body = new ListObjectsRequest(); if (request != null) { - body.user(request.getUser()) - .relation(request.getRelation()) - .type(request.getType()) - .contextualTuples(ClientTupleKey.asContextualTupleKeys(request.getContextualTupleKeys())); + body.user(request.getUser()).relation(request.getRelation()).type(request.getType()); + if (request.getContextualTupleKeys() != null) { + body.contextualTuples(new ContextualTupleKeys() + .tupleKeys(request.getContextualTupleKeys().stream() + .map(contextualTuple -> new TupleKey() + ._object(contextualTuple.getObject()) + .relation(contextualTuple.getRelation()) + .user(contextualTuple.getUser())) + .collect(Collectors.toList()))); + } } if (options != null && !isNullOrWhitespace(options.getAuthorizationModelId())) { diff --git a/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache b/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache index 4f38673e..dfedba9d 100644 --- a/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache +++ b/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache @@ -31,6 +31,8 @@ public class OpenFgaClientIntegrationTest { @BeforeEach public void initializeApi() throws Exception { + System.setProperty("HttpRequestAttempt.debug-logging", "enable"); + ClientConfiguration apiConfig = new ClientConfiguration().apiUrl("http://localhost:8080"); fga = new OpenFgaClient(apiConfig); } @@ -121,7 +123,7 @@ public class OpenFgaClientIntegrationTest { assertEquals(authModelId, response.getAuthorizationModel().getId()); String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions()); assertEquals( - "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]}}}}]", + "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]}}}}]", typeDefsJson); } @@ -149,7 +151,7 @@ public class OpenFgaClientIntegrationTest { String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions()); assertEquals( - "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]}}}}]", + "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]}}}}]", typeDefsJson); } catch (JsonProcessingException ex) { assertNull(ex); @@ -291,7 +293,7 @@ public class OpenFgaClientIntegrationTest { // Then String responseJson = mapper.writeValueAsString(response.getAssertions()); assertEquals( - "[{\"tuple_key\":{\"object\":\"document:2021-budget\",\"relation\":\"reader\",\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\"},\"expectation\":true}]", + "[{\"tuple_key\":{\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\",\"relation\":\"reader\",\"object\":\"document:2021-budget\"},\"expectation\":true}]", responseJson); } diff --git a/config/clients/java/template/client-OpenFgaClientTest.java.mustache b/config/clients/java/template/client-OpenFgaClientTest.java.mustache index e4ab6614..06d4f3d0 100644 --- a/config/clients/java/template/client-OpenFgaClientTest.java.mustache +++ b/config/clients/java/template/client-OpenFgaClientTest.java.mustache @@ -46,8 +46,10 @@ public class OpenFgaClientTest { @BeforeEach public void beforeEachTest() throws Exception { + System.setProperty("HttpRequestAttempt.debug-logging", "enable"); + mockHttpClient = new HttpClientMock(); - // mockHttpClient.debugOn(); // Uncomment when debugging HTTP requests. + mockHttpClient.debugOn(); var mockHttpClientBuilder = mock(HttpClient.Builder.class); when(mockHttpClientBuilder.executor(any())).thenReturn(mockHttpClientBuilder); @@ -658,7 +660,7 @@ public class OpenFgaClientTest { // Given String postUrl = String.format("https://localhost/stores/%s/authorization-models", DEFAULT_STORE_ID); String expectedBody = - "{\"type_definitions\":[{\"type\":\"document\",\"relations\":{},\"metadata\":null}],\"schema_version\":\"1.1\"}"; + "{\"type_definitions\":[{\"type\":\"document\",\"relations\":{},\"metadata\":null}],\"schema_version\":\"1.1\",\"conditions\":{}}"; String responseBody = String.format("{\"authorization_model_id\":\"%s\"}", DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(201, responseBody); WriteAuthorizationModelRequest request = new WriteAuthorizationModelRequest() @@ -915,8 +917,8 @@ public class OpenFgaClientTest { // Given String postUrl = String.format("https://localhost/stores/%s/read", DEFAULT_STORE_ID); String expectedBody = String.format( - "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"page_size\":null,\"continuation_token\":null}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"page_size\":null,\"continuation_token\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); String responseBody = String.format( "{\"tuples\":[{\"key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}}]}", DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); @@ -1041,8 +1043,8 @@ public class OpenFgaClientTest { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write"; String expectedBody = String.format( - "{\"writes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID); + "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); ClientWriteRequest request = new ClientWriteRequest() .writes(List.of(new ClientTupleKey() @@ -1066,8 +1068,8 @@ public class OpenFgaClientTest { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write"; String expectedBody = String.format( - "{\"writes\":null,\"deletes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"authorization_model_id\":\"%s\"}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID); + "{\"writes\":null,\"deletes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}]},\"authorization_model_id\":\"%s\"}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); ClientWriteRequest request = new ClientWriteRequest() .deletes(List.of(new ClientTupleKey() @@ -1086,25 +1088,28 @@ public class OpenFgaClientTest { public void writeTest_transactions() throws Exception { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write"; - String tupleBody = String.format( - "{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + String writeTupleBody = String.format( + "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); ClientTupleKey tuple = new ClientTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER); String write2Body = String.format( "{\"writes\":{\"tuple_keys\":[%s,%s]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", - tupleBody, tupleBody, DEFAULT_AUTH_MODEL_ID); + writeTupleBody, writeTupleBody, DEFAULT_AUTH_MODEL_ID); String write1Body = String.format( "{\"writes\":{\"tuple_keys\":[%s]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", - tupleBody, DEFAULT_AUTH_MODEL_ID); + writeTupleBody, DEFAULT_AUTH_MODEL_ID); + String deleteTupleBody = String.format( + "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); String delete2Body = String.format( "{\"writes\":null,\"deletes\":{\"tuple_keys\":[%s,%s]},\"authorization_model_id\":\"%s\"}", - tupleBody, tupleBody, DEFAULT_AUTH_MODEL_ID); + deleteTupleBody, deleteTupleBody, DEFAULT_AUTH_MODEL_ID); String delete1Body = String.format( "{\"writes\":null,\"deletes\":{\"tuple_keys\":[%s]},\"authorization_model_id\":\"%s\"}", - tupleBody, DEFAULT_AUTH_MODEL_ID); + deleteTupleBody, DEFAULT_AUTH_MODEL_ID); mockHttpClient .onPost(postPath) .withBody(isOneOf(write2Body, write1Body, delete2Body, delete1Body)) @@ -1134,8 +1139,8 @@ public class OpenFgaClientTest { String failedUser = "user:SECOND"; String skippedUser = "user:third"; Function writeBody = user -> String.format( - "{\"writes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", - DEFAULT_OBJECT, DEFAULT_RELATION, user, DEFAULT_AUTH_MODEL_ID); + "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", + user, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient .onPost(postPath) .withBody(isOneOf(writeBody.apply(firstUser), writeBody.apply(skippedUser))) @@ -1182,16 +1187,22 @@ public class OpenFgaClientTest { public void writeTest_nonTransaction() throws Exception { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write"; - String tupleBody = String.format( - "{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + String writeTupleBody = String.format( + "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); + String deleteTupleBody = String.format( + "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); String expectedBody = String.format( "{\"writes\":{\"tuple_keys\":[%s,%s,%s]},\"deletes\":{\"tuple_keys\":[%s,%s,%s]},\"authorization_model_id\":\"%s\"}", - tupleBody, tupleBody, tupleBody, tupleBody, tupleBody, tupleBody, DEFAULT_AUTH_MODEL_ID); - mockHttpClient - .onPost(postPath) - .withBody(is(expectedBody)) - .doReturn(200, EMPTY_RESPONSE_BODY); + writeTupleBody, + writeTupleBody, + writeTupleBody, + deleteTupleBody, + deleteTupleBody, + deleteTupleBody, + DEFAULT_AUTH_MODEL_ID); + mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); ClientTupleKey tuple = new ClientTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) @@ -1216,12 +1227,21 @@ public class OpenFgaClientTest { public void writeTest_nonTransactionsWithFailure() throws Exception { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write"; - String tupleBody = String.format( - "{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + String writeTupleBody = String.format( + "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); + String deleteTupleBody = String.format( + "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); String expectedBody = String.format( "{\"writes\":{\"tuple_keys\":[%s,%s,%s]},\"deletes\":{\"tuple_keys\":[%s,%s,%s]},\"authorization_model_id\":\"%s\"}", - tupleBody, tupleBody, tupleBody, tupleBody, tupleBody, tupleBody, DEFAULT_AUTH_MODEL_ID); + writeTupleBody, + writeTupleBody, + writeTupleBody, + deleteTupleBody, + deleteTupleBody, + deleteTupleBody, + DEFAULT_AUTH_MODEL_ID); mockHttpClient .onPost(postPath) .withBody(is(expectedBody)) @@ -1252,8 +1272,8 @@ public class OpenFgaClientTest { // Given String postPath = String.format("https://localhost/stores/%s/write", DEFAULT_STORE_ID); String expectedBody = String.format( - "{\"writes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID); + "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); List tuples = List.of(new ClientTupleKey() ._object(DEFAULT_OBJECT) @@ -1273,8 +1293,8 @@ public class OpenFgaClientTest { // Given String postPath = String.format("https://localhost/stores/%s/write", DEFAULT_STORE_ID); String expectedBody = String.format( - "{\"writes\":null,\"deletes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"authorization_model_id\":\"%s\"}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID); + "{\"writes\":null,\"deletes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}]},\"authorization_model_id\":\"%s\"}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); List tuples = List.of(new ClientTupleKey() ._object(DEFAULT_OBJECT) @@ -1387,10 +1407,10 @@ public class OpenFgaClientTest { // Given String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID); String expectedBody = String.format( - "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}," + - "\"contextual_tuples\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"owner\",\"user\":\"%s\"}]}," + - "\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_OBJECT, DEFAULT_USER); + "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}," + + "\"contextual_tuples\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"owner\",\"object\":\"%s\",\"condition\":null}]}," + + "\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_USER, DEFAULT_OBJECT); mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}"); ClientCheckRequest request = new ClientCheckRequest() ._object(DEFAULT_OBJECT) @@ -1498,8 +1518,8 @@ public class OpenFgaClientTest { // Given String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID); String expectedBody = String.format( - "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}"); ClientCheckRequest request = new ClientCheckRequest() ._object(DEFAULT_OBJECT) @@ -1521,8 +1541,8 @@ public class OpenFgaClientTest { // Given String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID); String expectedBody = String.format( - "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}"); List requests = IntStream.range(0, 20) .mapToObj(ignored -> new ClientCheckRequest() @@ -1639,8 +1659,8 @@ public class OpenFgaClientTest { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/expand"; String expectedBody = String.format( - "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":null},\"authorization_model_id\":\"%s\"}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_AUTH_MODEL_ID); + "{\"tuple_key\":{\"relation\":\"%s\",\"object\":\"%s\"},\"authorization_model_id\":\"%s\"}", + DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); String responseBody = String.format( "{\"tree\":{\"root\":{\"union\":{\"nodes\":[{\"leaf\":{\"users\":{\"users\":[\"%s\"]}}}]}}}}", DEFAULT_USER); @@ -1755,7 +1775,7 @@ public class OpenFgaClientTest { // Given String postPath = String.format("https://localhost/stores/%s/list-objects", DEFAULT_STORE_ID); String expectedBody = String.format( - "{\"authorization_model_id\":\"%s\",\"type\":null,\"relation\":\"%s\",\"user\":\"%s\",\"contextual_tuples\":{\"tuple_keys\":[]}}", + "{\"authorization_model_id\":\"%s\",\"type\":null,\"relation\":\"%s\",\"user\":\"%s\",\"contextual_tuples\":null,\"context\":null}", DEFAULT_AUTH_MODEL_ID, DEFAULT_RELATION, DEFAULT_USER); mockHttpClient .onPost(postPath) @@ -1859,8 +1879,8 @@ public class OpenFgaClientTest { // Given String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID); String expectedBody = String.format( - "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}"); ClientListRelationsRequest request = new ClientListRelationsRequest() .relations(List.of(DEFAULT_RELATION)) @@ -1886,8 +1906,8 @@ public class OpenFgaClientTest { // Given String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID); String expectedBody = String.format( - "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}", - DEFAULT_OBJECT, "owner", DEFAULT_USER); + "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"%s\",\"trace\":null,\"context\":null}", + DEFAULT_USER, "owner", DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":false}"); ClientListRelationsRequest request = new ClientListRelationsRequest() .relations(List.of("owner")) @@ -1967,8 +1987,8 @@ public class OpenFgaClientTest { // Given String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID); String expectedBody = String.format( - "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); mockHttpClient .onPost(postUrl) .withBody(is(expectedBody)) @@ -1997,8 +2017,8 @@ public class OpenFgaClientTest { // Given String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID); String expectedBody = String.format( - "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); mockHttpClient .onPost(postUrl) .withBody(is(expectedBody)) @@ -2026,8 +2046,8 @@ public class OpenFgaClientTest { // Given String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID); String expectedBody = String.format( - "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); mockHttpClient .onPost(postUrl) .withBody(is(expectedBody)) @@ -2059,8 +2079,8 @@ public class OpenFgaClientTest { String getUrl = String.format("https://localhost/stores/%s/assertions/%s", DEFAULT_STORE_ID, DEFAULT_AUTH_MODEL_ID); String responseBody = String.format( - "{\"assertions\":[{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"expectation\":true}]}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"assertions\":[{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"expectation\":true}]}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); mockHttpClient.onGet(getUrl).doReturn(200, responseBody); // When @@ -2180,8 +2200,8 @@ public class OpenFgaClientTest { String putUrl = String.format("https://localhost/stores/%s/assertions/%s", DEFAULT_STORE_ID, DEFAULT_AUTH_MODEL_ID); String expectedBody = String.format( - "{\"assertions\":[{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"expectation\":true}]}", - DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + "{\"assertions\":[{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"expectation\":true}]}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); mockHttpClient.onPut(putUrl).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); List assertions = List.of(new ClientAssertion() .user(DEFAULT_USER) diff --git a/config/clients/java/template/creds-OAuth2ClientTest.java.mustache b/config/clients/java/template/creds-OAuth2ClientTest.java.mustache index 898f35fb..7f4706be 100644 --- a/config/clients/java/template/creds-OAuth2ClientTest.java.mustache +++ b/config/clients/java/template/creds-OAuth2ClientTest.java.mustache @@ -29,7 +29,10 @@ class OAuth2ClientTest { @BeforeEach public void setup() throws FgaInvalidParameterException { + System.setProperty("HttpRequestAttempt.debug-logging", "enable"); + mockHttpClient = new HttpClientMock(); + mockHttpClient.debugOn(); var credentials = new Credentials(new ClientCredentials() .clientId(CLIENT_ID) From 9794d187e7e860b5c848879f622bf6bcaadce6c6 Mon Sep 17 00:00:00 2001 From: "J.R. Hill" Date: Thu, 30 Nov 2023 15:47:05 -0800 Subject: [PATCH 2/7] test(java-sdk): add write and read tests on OpenFgaApi with context --- .../template/OpenFgaApiTest.java.mustache | 155 +++++++++++++++++- 1 file changed, 153 insertions(+), 2 deletions(-) diff --git a/config/clients/java/template/OpenFgaApiTest.java.mustache b/config/clients/java/template/OpenFgaApiTest.java.mustache index 8f52b83d..c69447c4 100644 --- a/config/clients/java/template/OpenFgaApiTest.java.mustache +++ b/config/clients/java/template/OpenFgaApiTest.java.mustache @@ -14,7 +14,9 @@ import {{configPackage}}.*; import {{errorsPackage}}.*; import java.net.http.HttpClient; import java.time.Duration; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -939,6 +941,62 @@ public class OpenFgaApiTest { assertEquals(DEFAULT_OBJECT, key.getObject()); } + @Test + public void read_complexContext() throws Exception { + // Given + String postUrl = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/read"; + String expectedBody = String.format( + "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"page_size\":null,\"continuation_token\":null}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); + String responseBody = String.format( + "{\"tuples\":[{\"key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"," + + "\"condition\":{\"context\":{" + + " \"num\":1," + + " \"str\":\"banana\"," + + " \"list\":[1, \"banana\", [], {}]," + + " \"obj\":{" + + " \"num\":1," + + " \"str\":\"banana\"," + + " \"list\":[]," + + " \"obj\": {}" + + " }" + + "}}}}]}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); + mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, responseBody); + ReadRequest request = new ReadRequest() + .tupleKey(new ReadRequestTupleKey() + ._object(DEFAULT_OBJECT) + .relation(DEFAULT_RELATION) + .user(DEFAULT_USER)); + + // When + var response = fga.read(DEFAULT_STORE_ID, request).get(); + + // Then + mockHttpClient.verify().post(postUrl).withBody(is(expectedBody)).called(1); + assertNotNull(response.getData()); + assertNotNull(response.getData().getTuples()); + assertEquals(1, response.getData().getTuples().size()); + var key = response.getData().getTuples().get(0).getKey(); + assertNotNull(key); + assertEquals(DEFAULT_USER, key.getUser()); + assertEquals(DEFAULT_RELATION, key.getRelation()); + assertEquals(DEFAULT_OBJECT, key.getObject()); + + // The below is subject to change. + assertNotNull(key.getCondition()); + var context = key.getCondition().getContext(); + assertNotNull(context); + var contextMap = assertInstanceOf(Map.class, context); + assertEquals(1, contextMap.get("num")); + assertEquals("banana", contextMap.get("str")); + assertEquals(List.of(1, "banana", List.of(), Map.of()), contextMap.get("list")); + assertEquals(Map.of("num", 1, + "str", "banana", + "list", List.of(), + "obj", Map.of()), contextMap.get("obj")); + } + @Test public void read_storeIdRequired() { // When @@ -1046,7 +1104,7 @@ public class OpenFgaApiTest { .user(DEFAULT_USER)))); // When - fga.write(DEFAULT_STORE_ID, request); + fga.write(DEFAULT_STORE_ID, request).get(); // Then mockHttpClient.verify().post(postPath).withBody(is(expectedBody)).called(1); @@ -1072,7 +1130,100 @@ public class OpenFgaApiTest { .user(DEFAULT_USER)))); // When - fga.write(DEFAULT_STORE_ID, request); + fga.write(DEFAULT_STORE_ID, request).get(); + + // Then + mockHttpClient.verify().post(postPath).withBody(is(expectedBody)).called(1); + } + + @Test + public void writeWithContext_map() throws Exception { + // Given + String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write"; + String expectedBody = String.format( + "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"conditionName\",\"context\":{\"num\":1,\"str\":\"banana\",\"list\":[],\"obj\":{}}}}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); + mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); + var context = new LinkedHashMap<>(); + context.put("num", 1); + context.put("str", "banana"); + context.put("list", List.of()); + context.put("obj", new LinkedHashMap<>()); + WriteRequest request = new WriteRequest() + .authorizationModelId(DEFAULT_AUTH_MODEL_ID) + .writes(new WriteRequestWrites() + .tupleKeys(List.of(new TupleKey() + ._object(DEFAULT_OBJECT) + .relation(DEFAULT_RELATION) + .user(DEFAULT_USER) + .condition(new RelationshipCondition() + .name("conditionName") + .context(context))))); + + // When + fga.write(DEFAULT_STORE_ID, request).get(); + + // Then + mockHttpClient.verify().post(postPath).withBody(is(expectedBody)).called(1); + } + + @Test + public void writeWithContext_modeledObj() throws Exception { + // Given + + String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write"; + String expectedBody = String.format( + "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"conditionName\",\"context\":{\"num\":1,\"str\":\"apple\",\"list\":[2,\"banana\",[],{\"num\":3,\"str\":\"cupcake\",\"list\":null,\"obj\":null}],\"obj\":{\"num\":4,\"str\":\"dolphin\",\"list\":null,\"obj\":null}}}}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", + DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); + mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); + + class TestObj { + int num; + String str; + List list; + Object obj; + + public int getNum() { + return num; + } + + public String getStr() { + return str; + } + + public List getList() { + return list; + } + + public Object getObj() { + return obj; + } + } + var obj = new TestObj(); + obj.num = 1; + obj.str = "apple"; + var objInList = new TestObj(); + obj.list = List.of(2, "banana", List.of(), objInList); + objInList.num = 3; + objInList.str = "cupcake"; + var objInObj = new TestObj(); + obj.obj = objInObj; + objInObj.num = 4; + objInObj.str = "dolphin"; + + WriteRequest request = new WriteRequest() + .authorizationModelId(DEFAULT_AUTH_MODEL_ID) + .writes(new WriteRequestWrites() + .tupleKeys(List.of(new TupleKey() + ._object(DEFAULT_OBJECT) + .relation(DEFAULT_RELATION) + .user(DEFAULT_USER) + .condition(new RelationshipCondition() + .name("conditionName") + .context(obj))))); + + // When + fga.write(DEFAULT_STORE_ID, request).get(); // Then mockHttpClient.verify().post(postPath).withBody(is(expectedBody)).called(1); From 88bf348163798b8acba7f5bfb42c8a96d64ed11f Mon Sep 17 00:00:00 2001 From: "J.R. Hill" Date: Tue, 5 Dec 2023 16:08:44 -0800 Subject: [PATCH 3/7] feat(java-sdk): add ABAC support to OpenFgaClient --- config/clients/java/config.overrides.json | 8 +++ .../client-ClientCheckRequest.java.mustache | 11 ++- ...ent-ClientListObjectsRequest.java.mustache | 6 +- ...-ClientRelationshipCondition.java.mustache | 31 +++++++++ .../client-ClientTupleKey.java.mustache | 30 +++++++++ ...-ClientTupleKeyWithCondition.java.mustache | 65 ++++++++++++++++++ .../client-ClientWriteRequest.java.mustache | 8 +-- .../client-OpenFgaClient.java.mustache | 57 +++------------- ...OpenFgaClientIntegrationTest.java.mustache | 23 ++++--- .../client-OpenFgaClientTest.java.mustache | 67 +++++++++++-------- 10 files changed, 213 insertions(+), 93 deletions(-) create mode 100644 config/clients/java/template/client-ClientRelationshipCondition.java.mustache create mode 100644 config/clients/java/template/client-ClientTupleKeyWithCondition.java.mustache diff --git a/config/clients/java/config.overrides.json b/config/clients/java/config.overrides.json index 3ec726be..e1343711 100644 --- a/config/clients/java/config.overrides.json +++ b/config/clients/java/config.overrides.json @@ -123,10 +123,18 @@ "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientReadResponse.java", "templateType": "SupportingFiles" }, + "client-ClientRelationshipCondition.java.mustache" : { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientRelationshipCondition.java", + "templateType": "SupportingFiles" + }, "client-ClientTupleKey.java.mustache" : { "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientTupleKey.java", "templateType": "SupportingFiles" }, + "client-ClientTupleKeyWithCondition.java.mustache" : { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientTupleKeyWithCondition.java", + "templateType": "SupportingFiles" + }, "client-ClientWriteAssertionsResponse.java.mustache" : { "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientWriteAssertionsResponse.java", "templateType": "SupportingFiles" diff --git a/config/clients/java/template/client-ClientCheckRequest.java.mustache b/config/clients/java/template/client-ClientCheckRequest.java.mustache index ff2da5b6..42730dc6 100644 --- a/config/clients/java/template/client-ClientCheckRequest.java.mustache +++ b/config/clients/java/template/client-ClientCheckRequest.java.mustache @@ -1,13 +1,18 @@ {{>licenseInfo}} package {{invokerPackage}}; +import dev.openfga.sdk.api.model.CheckRequestTupleKey; import java.util.List; public class ClientCheckRequest { private String user; private String relation; private String _object; - private List contextualTuples; + private List contextualTuples; + + public CheckRequestTupleKey asCheckRequestTupleKey() { + return new CheckRequestTupleKey().user(user).relation(relation)._object(_object); + } public ClientCheckRequest _object(String _object) { this._object = _object; @@ -48,12 +53,12 @@ public class ClientCheckRequest { return user; } - public ClientCheckRequest contextualTuples(List contextualTuples) { + public ClientCheckRequest contextualTuples(List contextualTuples) { this.contextualTuples = contextualTuples; return this; } - public List getContextualTuples() { + public List getContextualTuples() { return contextualTuples; } } diff --git a/config/clients/java/template/client-ClientListObjectsRequest.java.mustache b/config/clients/java/template/client-ClientListObjectsRequest.java.mustache index 3f538643..e3e2218e 100644 --- a/config/clients/java/template/client-ClientListObjectsRequest.java.mustache +++ b/config/clients/java/template/client-ClientListObjectsRequest.java.mustache @@ -7,7 +7,7 @@ public class ClientListObjectsRequest { private String user; private String relation; private String type; - private List contextualTupleKeys; + private List contextualTupleKeys; public ClientListObjectsRequest user(String user) { this.user = user; @@ -44,12 +44,12 @@ public class ClientListObjectsRequest { return type; } - public ClientListObjectsRequest contextualTupleKeys(List contextualTupleKeys) { + public ClientListObjectsRequest contextualTupleKeys(List contextualTupleKeys) { this.contextualTupleKeys = contextualTupleKeys; return this; } - public List getContextualTupleKeys() { + public List getContextualTupleKeys() { return contextualTupleKeys; } } diff --git a/config/clients/java/template/client-ClientRelationshipCondition.java.mustache b/config/clients/java/template/client-ClientRelationshipCondition.java.mustache new file mode 100644 index 00000000..bc6ed094 --- /dev/null +++ b/config/clients/java/template/client-ClientRelationshipCondition.java.mustache @@ -0,0 +1,31 @@ +{{>licenseInfo}} +package {{invokerPackage}}; + +import {{modelPackage}}.RelationshipCondition; + +public class ClientRelationshipCondition { + private String name; + private Object context; + + public ClientRelationshipCondition name(String name) { + this.name = name; + return this; + } + + public String getName() { + return name; + } + + public ClientRelationshipCondition context(Object context) { + this.context = context; + return this; + } + + public Object getContext() { + return context; + } + + public RelationshipCondition asRelationshipCondition() { + return new RelationshipCondition().name(name).context(context); + } +} diff --git a/config/clients/java/template/client-ClientTupleKey.java.mustache b/config/clients/java/template/client-ClientTupleKey.java.mustache index d424a020..16655de4 100644 --- a/config/clients/java/template/client-ClientTupleKey.java.mustache +++ b/config/clients/java/template/client-ClientTupleKey.java.mustache @@ -1,11 +1,27 @@ {{>licenseInfo}} package {{invokerPackage}}; +import {{modelPackage}}.TupleKeyWithoutCondition; +import {{modelPackage}}.WriteRequestDeletes; +import java.util.Collection; +import java.util.stream.Collectors; + public class ClientTupleKey { private String user; private String relation; private String _object; + public TupleKeyWithoutCondition asTupleKeyWithoutCondition() { + return new TupleKeyWithoutCondition().user(user).relation(relation)._object(_object); + } + + public static WriteRequestDeletes asWriteRequestDeletes(Collection tupleKeys) { + return new WriteRequestDeletes() + .tupleKeys(tupleKeys.stream() + .map(ClientTupleKey::asTupleKeyWithoutCondition) + .collect(Collectors.toList())); + } + public ClientTupleKey _object(String _object) { this._object = _object; return this; @@ -44,4 +60,18 @@ public class ClientTupleKey { public String getUser() { return user; } + + /** + * Adds a condition to the tuple key. + * @param condition a {@link ClientRelationshipCondition} + * @return a new {@link ClientTupleKeyWithCondition} with this {@link ClientTupleKey}'s + * user, relation, and object, and the passed condition. + */ + public ClientTupleKeyWithCondition condition(ClientRelationshipCondition condition) { + return new ClientTupleKeyWithCondition() + .user(user) + .relation(relation) + ._object(_object) + .condition(condition); + } } diff --git a/config/clients/java/template/client-ClientTupleKeyWithCondition.java.mustache b/config/clients/java/template/client-ClientTupleKeyWithCondition.java.mustache new file mode 100644 index 00000000..60595ebe --- /dev/null +++ b/config/clients/java/template/client-ClientTupleKeyWithCondition.java.mustache @@ -0,0 +1,65 @@ +{{>licenseInfo}} +package {{invokerPackage}}; + +import {{modelPackage}}.ContextualTupleKeys; +import {{modelPackage}}.TupleKey; +import {{modelPackage}}.WriteRequestWrites; +import java.util.Collection; +import java.util.stream.Collectors; + +public class ClientTupleKeyWithCondition extends ClientTupleKey { + private ClientRelationshipCondition condition; + + public ClientTupleKeyWithCondition condition(ClientRelationshipCondition condition) { + this.condition = condition; + return this; + } + + public ClientRelationshipCondition getCondition() { + return condition; + } + + public TupleKey asTupleKey() { + var tupleKey = new TupleKey().user(getUser()).relation(getRelation())._object(getObject()); + + if (condition != null) { + tupleKey.condition(condition.asRelationshipCondition()); + } + + return tupleKey; + } + + public static ContextualTupleKeys asContextualTupleKeys(Collection tupleKeys) { + return new ContextualTupleKeys() + .tupleKeys(tupleKeys.stream() + .map(ClientTupleKeyWithCondition::asTupleKey) + .collect(Collectors.toList())); + } + + public static WriteRequestWrites asWriteRequestWrites(Collection tupleKeys) { + return new WriteRequestWrites() + .tupleKeys(tupleKeys.stream() + .map(ClientTupleKeyWithCondition::asTupleKey) + .collect(Collectors.toList())); + } + + /* Overrides for correct typing */ + + @Override + public ClientTupleKeyWithCondition user(String user) { + super.user(user); + return this; + } + + @Override + public ClientTupleKeyWithCondition relation(String relation) { + super.relation(relation); + return this; + } + + @Override + public ClientTupleKeyWithCondition _object(String _object) { + super._object(_object); + return this; + } +} diff --git a/config/clients/java/template/client-ClientWriteRequest.java.mustache b/config/clients/java/template/client-ClientWriteRequest.java.mustache index c065d658..f0fb6dd5 100644 --- a/config/clients/java/template/client-ClientWriteRequest.java.mustache +++ b/config/clients/java/template/client-ClientWriteRequest.java.mustache @@ -4,19 +4,19 @@ package {{invokerPackage}}; import java.util.List; public class ClientWriteRequest { - private List writes; + private List writes; private List deletes; - public static ClientWriteRequest ofWrites(List writes) { + public static ClientWriteRequest ofWrites(List writes) { return new ClientWriteRequest().writes(writes); } - public ClientWriteRequest writes(List writes) { + public ClientWriteRequest writes(List writes) { this.writes = writes; return this; } - public List getWrites() { + public List getWrites() { return writes; } diff --git a/config/clients/java/template/client-OpenFgaClient.java.mustache b/config/clients/java/template/client-OpenFgaClient.java.mustache index 00068e47..23c662e6 100644 --- a/config/clients/java/template/client-OpenFgaClient.java.mustache +++ b/config/clients/java/template/client-OpenFgaClient.java.mustache @@ -280,24 +280,12 @@ public class OpenFgaClient { var writeTuples = request.getWrites(); if (writeTuples != null && !writeTuples.isEmpty()) { - body.writes(new WriteRequestWrites() - .tupleKeys(writeTuples.stream() - .map(writeTuple -> new TupleKey() - .user(writeTuple.getUser()) - .relation(writeTuple.getRelation()) - ._object(writeTuple.getObject())) - .collect(Collectors.toList()))); + body.writes(ClientTupleKeyWithCondition.asWriteRequestWrites(writeTuples)); } var deleteTuples = request.getDeletes(); if (deleteTuples != null && !deleteTuples.isEmpty()) { - body.deletes(new WriteRequestDeletes() - .tupleKeys(deleteTuples.stream() - .map(deleteTuple -> new TupleKeyWithoutCondition() - .user(deleteTuple.getUser()) - .relation(deleteTuple.getRelation()) - ._object(deleteTuple.getObject())) - .collect(Collectors.toList()))); + body.deletes(ClientTupleKey.asWriteRequestDeletes(deleteTuples)); } if (options != null && !isNullOrWhitespace(options.getAuthorizationModelId())) { @@ -357,20 +345,14 @@ public class OpenFgaClient { * * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace */ - public CompletableFuture writeTuples(List tupleKeys) + public CompletableFuture writeTuples(List tupleKeys) throws FgaInvalidParameterException { configuration.assertValid(); String storeId = configuration.getStoreIdChecked(); var body = new WriteRequest(); - body.writes(new WriteRequestWrites() - .tupleKeys(tupleKeys.stream() - .map(writeTuple -> new TupleKey() - .user(writeTuple.getUser()) - .relation(writeTuple.getRelation()) - ._object(writeTuple.getObject())) - .collect(Collectors.toList()))); + body.writes(ClientTupleKeyWithCondition.asWriteRequestWrites(tupleKeys)); String authorizationModelId = configuration.getAuthorizationModelId(); if (!isNullOrWhitespace(authorizationModelId)) { @@ -392,13 +374,7 @@ public class OpenFgaClient { var body = new WriteRequest(); - body.deletes(new WriteRequestDeletes() - .tupleKeys(tupleKeys.stream() - .map(deleteTuple -> new TupleKeyWithoutCondition() - .user(deleteTuple.getUser()) - .relation(deleteTuple.getRelation()) - ._object(deleteTuple.getObject())) - .collect(Collectors.toList()))); + body.deletes(ClientTupleKey.asWriteRequestDeletes(tupleKeys)); String authorizationModelId = configuration.getAuthorizationModelId(); if (!isNullOrWhitespace(authorizationModelId)) { @@ -435,20 +411,11 @@ public class OpenFgaClient { CheckRequest body = new CheckRequest(); if (request != null) { - body.tupleKey(new CheckRequestTupleKey() - .user(request.getUser()) - .relation(request.getRelation()) - ._object(request.getObject())); + body.tupleKey(request.asCheckRequestTupleKey()); var contextualTuples = request.getContextualTuples(); if (contextualTuples != null && !contextualTuples.isEmpty()) { - body.contextualTuples(new ContextualTupleKeys() - .tupleKeys(contextualTuples.stream() - .map(contextualTuple -> new TupleKey() - ._object(contextualTuple.getObject()) - .relation(contextualTuple.getRelation()) - .user(contextualTuple.getUser())) - .collect(Collectors.toList()))); + body.contextualTuples(ClientTupleKeyWithCondition.asContextualTupleKeys(contextualTuples)); } } @@ -559,13 +526,9 @@ public class OpenFgaClient { if (request != null) { body.user(request.getUser()).relation(request.getRelation()).type(request.getType()); if (request.getContextualTupleKeys() != null) { - body.contextualTuples(new ContextualTupleKeys() - .tupleKeys(request.getContextualTupleKeys().stream() - .map(contextualTuple -> new TupleKey() - ._object(contextualTuple.getObject()) - .relation(contextualTuple.getRelation()) - .user(contextualTuple.getUser())) - .collect(Collectors.toList()))); + var contextualTuples = request.getContextualTupleKeys(); + var bodyContextualTuples = ClientTupleKeyWithCondition.asContextualTupleKeys(contextualTuples); + body.contextualTuples(bodyContextualTuples); } } diff --git a/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache b/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache index dfedba9d..0a3e691a 100644 --- a/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache +++ b/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache @@ -12,6 +12,7 @@ import {{configPackage}}.*; import dev.openfga.errors.ApiException; import java.net.http.HttpClient; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -21,10 +22,11 @@ public class OpenFgaClientIntegrationTest { "{\"schema_version\":\"1.1\",\"type_definitions\":[{\"type\":\"user\"},{\"type\":\"document\",\"relations\":{\"reader\":{\"this\":{}},\"writer\":{\"this\":{}},\"owner\":{\"this\":{}}},\"metadata\":{\"relations\":{\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}}]}"; private static final String DEFAULT_USER = "user:81684243-9356-4421-8fbf-a4f8d36aa31b"; private static final String DEFAULT_DOC = "document:2021-budget"; - public static final ClientTupleKey DEFAULT_TUPLE_KEY = + private static final ClientTupleKey DEFAULT_TUPLE_KEY = new ClientTupleKey().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC); - public static final List DEFAULT_TUPLE_KEYS = List.of(DEFAULT_TUPLE_KEY); - public static final ClientAssertion DEFAULT_ASSERTION = + private static final ClientRelationshipCondition DEFAULT_CONDITION = + new ClientRelationshipCondition().name("condition").context(Map.of("some", "context")); + private static final ClientAssertion DEFAULT_ASSERTION = new ClientAssertion().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC).expectation(true); private OpenFgaClient fga; @@ -186,8 +188,10 @@ public class OpenFgaClientIntegrationTest { String authModelId = writeAuthModel(storeId); fga.setAuthorizationModelId(authModelId); - ClientWriteRequest writeRequest = new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY)); - ClientReadRequest readRequest = new ClientReadRequest().user(DEFAULT_USER)._object(DEFAULT_DOC); + ClientWriteRequest writeRequest = + new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY.condition(DEFAULT_CONDITION))); + ClientReadRequest readRequest = + new ClientReadRequest().user(DEFAULT_USER)._object(DEFAULT_DOC); // When fga.write(writeRequest).get(); @@ -210,7 +214,8 @@ public class OpenFgaClientIntegrationTest { fga.setStoreId(storeId); String authModelId = writeAuthModel(storeId); fga.setAuthorizationModelId(authModelId); - ClientWriteRequest writeRequest = new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY)); + ClientWriteRequest writeRequest = + new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY.condition(DEFAULT_CONDITION))); ClientCheckRequest checkRequest = new ClientCheckRequest() .user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC); @@ -231,7 +236,8 @@ public class OpenFgaClientIntegrationTest { fga.setStoreId(storeId); String authModelId = writeAuthModel(storeId); fga.setAuthorizationModelId(authModelId); - ClientWriteRequest writeRequest = new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY)); + ClientWriteRequest writeRequest = + new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY.condition(DEFAULT_CONDITION))); ClientExpandRequest expandRequest = new ClientExpandRequest()._object(DEFAULT_DOC).relation("reader"); @@ -261,7 +267,8 @@ public class OpenFgaClientIntegrationTest { fga.setStoreId(storeId); String authModelId = writeAuthModel(storeId); fga.setAuthorizationModelId(authModelId); - ClientWriteRequest writeRequest = new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY)); + ClientWriteRequest writeRequest = + new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY.condition(DEFAULT_CONDITION))); ClientListObjectsRequest listObjectsRequest = new ClientListObjectsRequest().user(DEFAULT_USER).relation("reader").type("document"); diff --git a/config/clients/java/template/client-OpenFgaClientTest.java.mustache b/config/clients/java/template/client-OpenFgaClientTest.java.mustache index 06d4f3d0..b879273a 100644 --- a/config/clients/java/template/client-OpenFgaClientTest.java.mustache +++ b/config/clients/java/template/client-OpenFgaClientTest.java.mustache @@ -15,6 +15,7 @@ import {{errorsPackage}}.*; import java.net.http.HttpClient; import java.time.Duration; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.function.Function; import java.util.stream.Collectors; @@ -36,6 +37,8 @@ public class OpenFgaClientTest { private static final String DEFAULT_OBJECT = "document:budget"; private static final String DEFAULT_SCHEMA_VERSION = "1.1"; private static final String EMPTY_RESPONSE_BODY = "{}"; + private static final ClientRelationshipCondition DEFAULT_CONDITION = + new ClientRelationshipCondition().name("condition").context(Map.of("some", "context")); private static final int DEFAULT_MAX_RETRIES = 3; private static final Duration DEFAULT_RETRY_DELAY = Duration.ofMillis(100); @@ -1047,7 +1050,7 @@ public class OpenFgaClientTest { DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); ClientWriteRequest request = new ClientWriteRequest() - .writes(List.of(new ClientTupleKey() + .writes(List.of(new ClientTupleKeyWithCondition() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER))); @@ -1089,12 +1092,13 @@ public class OpenFgaClientTest { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write"; String writeTupleBody = String.format( - "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}", + "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}", DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); ClientTupleKey tuple = new ClientTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER); + ClientTupleKeyWithCondition writeTuple = tuple.condition(DEFAULT_CONDITION); String write2Body = String.format( "{\"writes\":{\"tuple_keys\":[%s,%s]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", writeTupleBody, writeTupleBody, DEFAULT_AUTH_MODEL_ID); @@ -1115,7 +1119,7 @@ public class OpenFgaClientTest { .withBody(isOneOf(write2Body, write1Body, delete2Body, delete1Body)) .doReturn(200, EMPTY_RESPONSE_BODY); ClientWriteRequest request = new ClientWriteRequest() - .writes(List.of(tuple, tuple, tuple, tuple, tuple)) + .writes(List.of(writeTuple, writeTuple, writeTuple, writeTuple, writeTuple)) .deletes(List.of(tuple, tuple, tuple, tuple, tuple)); ClientWriteOptions options = new ClientWriteOptions().disableTransactions(false).transactionChunkSize(2); @@ -1139,7 +1143,7 @@ public class OpenFgaClientTest { String failedUser = "user:SECOND"; String skippedUser = "user:third"; Function writeBody = user -> String.format( - "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", + "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", user, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient .onPost(postPath) @@ -1151,10 +1155,11 @@ public class OpenFgaClientTest { .doReturn(400, "{\"code\":\"validation_error\",\"message\":\"Generic validation error\"}"); ClientWriteRequest request = new ClientWriteRequest() .writes(Stream.of(firstUser, failedUser, skippedUser) - .map(user -> new ClientTupleKey() + .map(user -> new ClientTupleKeyWithCondition() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) - .user(user)) + .user(user) + .condition(DEFAULT_CONDITION)) .collect(Collectors.toList())); ClientWriteOptions options = new ClientWriteOptions().disableTransactions(false).transactionChunkSize(1); @@ -1188,7 +1193,7 @@ public class OpenFgaClientTest { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write"; String writeTupleBody = String.format( - "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}", + "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}", DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); String deleteTupleBody = String.format( "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}", @@ -1203,10 +1208,11 @@ public class OpenFgaClientTest { deleteTupleBody, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); - ClientTupleKey tuple = new ClientTupleKey() + var tuple = new ClientTupleKeyWithCondition() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) - .user(DEFAULT_USER); + .user(DEFAULT_USER) + .condition(DEFAULT_CONDITION); ClientWriteRequest request = new ClientWriteRequest() .writes(List.of(tuple, tuple, tuple)) .deletes(List.of(tuple, tuple, tuple)); @@ -1228,7 +1234,7 @@ public class OpenFgaClientTest { // Given String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write"; String writeTupleBody = String.format( - "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}", + "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}", DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); String deleteTupleBody = String.format( "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}", @@ -1246,10 +1252,11 @@ public class OpenFgaClientTest { .onPost(postPath) .withBody(is(expectedBody)) .doReturn(400, "{\"code\":\"validation_error\",\"message\":\"Generic validation error\"}"); - ClientTupleKey tuple = new ClientTupleKey() + var tuple = new ClientTupleKeyWithCondition() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) - .user(DEFAULT_USER); + .user(DEFAULT_USER) + .condition(DEFAULT_CONDITION); ClientWriteRequest request = new ClientWriteRequest() .writes(List.of(tuple, tuple, tuple)) .deletes(List.of(tuple, tuple, tuple)); @@ -1272,13 +1279,15 @@ public class OpenFgaClientTest { // Given String postPath = String.format("https://localhost/stores/%s/write", DEFAULT_STORE_ID); String expectedBody = String.format( - "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", + "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}]}," + + "\"deletes\":null,\"authorization_model_id\":\"%s\"}", DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); - List tuples = List.of(new ClientTupleKey() + List tuples = List.of(new ClientTupleKeyWithCondition() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) - .user(DEFAULT_USER)); + .user(DEFAULT_USER) + .condition(DEFAULT_CONDITION)); // When ClientWriteResponse response = fga.writeTuples(tuples).get(); @@ -1331,10 +1340,11 @@ public class OpenFgaClientTest { .onPost(postUrl) .doReturn(400, "{\"code\":\"validation_error\",\"message\":\"Generic validation error\"}"); ClientWriteRequest request = new ClientWriteRequest() - .writes(List.of(new ClientTupleKey() + .writes(List.of(new ClientTupleKeyWithCondition() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) - .user(DEFAULT_USER))); + .user(DEFAULT_USER) + .condition(DEFAULT_CONDITION))); // When ExecutionException execException = @@ -1357,10 +1367,11 @@ public class OpenFgaClientTest { .onPost(postUrl) .doReturn(404, "{\"code\":\"undefined_endpoint\",\"message\":\"Endpoint not enabled\"}"); ClientWriteRequest request = new ClientWriteRequest() - .writes(List.of(new ClientTupleKey() + .writes(List.of(new ClientTupleKeyWithCondition() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) - .user(DEFAULT_USER))); + .user(DEFAULT_USER) + .condition(DEFAULT_CONDITION))); // When ExecutionException execException = @@ -1382,10 +1393,11 @@ public class OpenFgaClientTest { .onPost(postUrl) .doReturn(500, "{\"code\":\"internal_error\",\"message\":\"Internal Server Error\"}"); ClientWriteRequest request = new ClientWriteRequest() - .writes(List.of(new ClientTupleKey() + .writes(List.of(new ClientTupleKeyWithCondition() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) - .user(DEFAULT_USER))); + .user(DEFAULT_USER) + .condition(DEFAULT_CONDITION))); // When ExecutionException execException = @@ -1408,7 +1420,7 @@ public class OpenFgaClientTest { String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID); String expectedBody = String.format( "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}," - + "\"contextual_tuples\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"owner\",\"object\":\"%s\",\"condition\":null}]}," + + "\"contextual_tuples\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"owner\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}]}," + "\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}", DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_USER, DEFAULT_OBJECT); mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}"); @@ -1416,12 +1428,11 @@ public class OpenFgaClientTest { ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER) - .contextualTuples(List.of( - new ClientTupleKey() - ._object(DEFAULT_OBJECT) - .relation("owner") - .user(DEFAULT_USER) - )); + .contextualTuples(List.of(new ClientTupleKeyWithCondition() + ._object(DEFAULT_OBJECT) + .relation("owner") + .user(DEFAULT_USER) + .condition(DEFAULT_CONDITION))); ClientCheckOptions options = new ClientCheckOptions().authorizationModelId(DEFAULT_AUTH_MODEL_ID); // When From 07133c74126dafc903c53b7396d0d6a11a725556 Mon Sep 17 00:00:00 2001 From: "J.R. Hill" Date: Tue, 5 Dec 2023 16:19:28 -0800 Subject: [PATCH 4/7] chore(java-sdk): add dependabot configurations --- .../java/template/.github/dependabot.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 config/clients/java/template/.github/dependabot.yaml diff --git a/config/clients/java/template/.github/dependabot.yaml b/config/clients/java/template/.github/dependabot.yaml new file mode 100644 index 00000000..5a916f67 --- /dev/null +++ b/config/clients/java/template/.github/dependabot.yaml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "monthly" + groups: + dependencies: + patterns: + - "*" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + groups: + dependencies: + patterns: + - "*" From 449018f9be6c63e60dac89f7c1ba4a1d8a336108 Mon Sep 17 00:00:00 2001 From: "J.R. Hill" Date: Wed, 6 Dec 2023 10:25:29 -0800 Subject: [PATCH 5/7] refactor(java-sdk): rename client tuple key classes --- config/clients/java/config.overrides.json | 4 +- .../java/template/README_calling_api.mustache | 2 +- .../client-ClientCheckRequest.java.mustache | 6 +- ...ent-ClientListObjectsRequest.java.mustache | 6 +- .../client-ClientTupleKey.java.mustache | 90 ++++++++----------- ...-ClientTupleKeyWithCondition.java.mustache | 65 -------------- ...ientTupleKeyWithoutCondition.java.mustache | 77 ++++++++++++++++ .../client-ClientWriteRequest.java.mustache | 16 ++-- .../client-OpenFgaClient.java.mustache | 16 ++-- ...OpenFgaClientIntegrationTest.java.mustache | 4 +- .../client-OpenFgaClientTest.java.mustache | 26 +++--- 11 files changed, 156 insertions(+), 156 deletions(-) delete mode 100644 config/clients/java/template/client-ClientTupleKeyWithCondition.java.mustache create mode 100644 config/clients/java/template/client-ClientTupleKeyWithoutCondition.java.mustache diff --git a/config/clients/java/config.overrides.json b/config/clients/java/config.overrides.json index e1343711..970ea67c 100644 --- a/config/clients/java/config.overrides.json +++ b/config/clients/java/config.overrides.json @@ -131,8 +131,8 @@ "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientTupleKey.java", "templateType": "SupportingFiles" }, - "client-ClientTupleKeyWithCondition.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientTupleKeyWithCondition.java", + "client-ClientTupleKeyWithoutCondition.java.mustache" : { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientTupleKeyWithoutCondition.java", "templateType": "SupportingFiles" }, "client-ClientWriteAssertionsResponse.java.mustache" : { diff --git a/config/clients/java/template/README_calling_api.mustache b/config/clients/java/template/README_calling_api.mustache index 67e6624e..716240ec 100644 --- a/config/clients/java/template/README_calling_api.mustache +++ b/config/clients/java/template/README_calling_api.mustache @@ -277,7 +277,7 @@ var request = new ClientWriteRequest() ._object("document:budget") )) .deletes(List.of( - new ClientTupleKey() + new ClientTupleKeyWithoutCondition() .user("user:81684243-9356-4421-8fbf-a4f8d36aa31b") .relation("writer") ._object("document:roadmap") diff --git a/config/clients/java/template/client-ClientCheckRequest.java.mustache b/config/clients/java/template/client-ClientCheckRequest.java.mustache index 42730dc6..1d477745 100644 --- a/config/clients/java/template/client-ClientCheckRequest.java.mustache +++ b/config/clients/java/template/client-ClientCheckRequest.java.mustache @@ -8,7 +8,7 @@ public class ClientCheckRequest { private String user; private String relation; private String _object; - private List contextualTuples; + private List contextualTuples; public CheckRequestTupleKey asCheckRequestTupleKey() { return new CheckRequestTupleKey().user(user).relation(relation)._object(_object); @@ -53,12 +53,12 @@ public class ClientCheckRequest { return user; } - public ClientCheckRequest contextualTuples(List contextualTuples) { + public ClientCheckRequest contextualTuples(List contextualTuples) { this.contextualTuples = contextualTuples; return this; } - public List getContextualTuples() { + public List getContextualTuples() { return contextualTuples; } } diff --git a/config/clients/java/template/client-ClientListObjectsRequest.java.mustache b/config/clients/java/template/client-ClientListObjectsRequest.java.mustache index e3e2218e..3f538643 100644 --- a/config/clients/java/template/client-ClientListObjectsRequest.java.mustache +++ b/config/clients/java/template/client-ClientListObjectsRequest.java.mustache @@ -7,7 +7,7 @@ public class ClientListObjectsRequest { private String user; private String relation; private String type; - private List contextualTupleKeys; + private List contextualTupleKeys; public ClientListObjectsRequest user(String user) { this.user = user; @@ -44,12 +44,12 @@ public class ClientListObjectsRequest { return type; } - public ClientListObjectsRequest contextualTupleKeys(List contextualTupleKeys) { + public ClientListObjectsRequest contextualTupleKeys(List contextualTupleKeys) { this.contextualTupleKeys = contextualTupleKeys; return this; } - public List getContextualTupleKeys() { + public List getContextualTupleKeys() { return contextualTupleKeys; } } diff --git a/config/clients/java/template/client-ClientTupleKey.java.mustache b/config/clients/java/template/client-ClientTupleKey.java.mustache index 16655de4..3b1a826a 100644 --- a/config/clients/java/template/client-ClientTupleKey.java.mustache +++ b/config/clients/java/template/client-ClientTupleKey.java.mustache @@ -1,77 +1,65 @@ {{>licenseInfo}} package {{invokerPackage}}; -import {{modelPackage}}.TupleKeyWithoutCondition; -import {{modelPackage}}.WriteRequestDeletes; +import {{modelPackage}}.ContextualTupleKeys; +import {{modelPackage}}.TupleKey; +import {{modelPackage}}.WriteRequestWrites; import java.util.Collection; import java.util.stream.Collectors; -public class ClientTupleKey { - private String user; - private String relation; - private String _object; +public class ClientTupleKey extends ClientTupleKeyWithoutCondition { + private ClientRelationshipCondition condition; - public TupleKeyWithoutCondition asTupleKeyWithoutCondition() { - return new TupleKeyWithoutCondition().user(user).relation(relation)._object(_object); + public ClientTupleKey condition(ClientRelationshipCondition condition) { + this.condition = condition; + return this; } - public static WriteRequestDeletes asWriteRequestDeletes(Collection tupleKeys) { - return new WriteRequestDeletes() - .tupleKeys(tupleKeys.stream() - .map(ClientTupleKey::asTupleKeyWithoutCondition) - .collect(Collectors.toList())); + public ClientRelationshipCondition getCondition() { + return condition; } - public ClientTupleKey _object(String _object) { - this._object = _object; - return this; - } + public TupleKey asTupleKey() { + var tupleKey = new TupleKey().user(getUser()).relation(getRelation())._object(getObject()); + + if (condition != null) { + tupleKey.condition(condition.asRelationshipCondition()); + } - /** - * Get _object - * @return _object - **/ - public String getObject() { - return _object; + return tupleKey; } - public ClientTupleKey relation(String relation) { - this.relation = relation; - return this; + public static ContextualTupleKeys asContextualTupleKeys(Collection tupleKeys) { + return new ContextualTupleKeys() + .tupleKeys(tupleKeys.stream() + .map(ClientTupleKey::asTupleKey) + .collect(Collectors.toList())); } - /** - * Get relation - * @return relation - **/ - public String getRelation() { - return relation; + public static WriteRequestWrites asWriteRequestWrites(Collection tupleKeys) { + return new WriteRequestWrites() + .tupleKeys(tupleKeys.stream() + .map(ClientTupleKey::asTupleKey) + .collect(Collectors.toList())); } + /* Overrides for correct typing */ + + @Override public ClientTupleKey user(String user) { - this.user = user; + super.user(user); return this; } - /** - * Get user - * @return user - **/ - public String getUser() { - return user; + @Override + public ClientTupleKey relation(String relation) { + super.relation(relation); + return this; } - /** - * Adds a condition to the tuple key. - * @param condition a {@link ClientRelationshipCondition} - * @return a new {@link ClientTupleKeyWithCondition} with this {@link ClientTupleKey}'s - * user, relation, and object, and the passed condition. - */ - public ClientTupleKeyWithCondition condition(ClientRelationshipCondition condition) { - return new ClientTupleKeyWithCondition() - .user(user) - .relation(relation) - ._object(_object) - .condition(condition); + @Override + public ClientTupleKey _object(String _object) { + super._object(_object); + return this; } } diff --git a/config/clients/java/template/client-ClientTupleKeyWithCondition.java.mustache b/config/clients/java/template/client-ClientTupleKeyWithCondition.java.mustache deleted file mode 100644 index 60595ebe..00000000 --- a/config/clients/java/template/client-ClientTupleKeyWithCondition.java.mustache +++ /dev/null @@ -1,65 +0,0 @@ -{{>licenseInfo}} -package {{invokerPackage}}; - -import {{modelPackage}}.ContextualTupleKeys; -import {{modelPackage}}.TupleKey; -import {{modelPackage}}.WriteRequestWrites; -import java.util.Collection; -import java.util.stream.Collectors; - -public class ClientTupleKeyWithCondition extends ClientTupleKey { - private ClientRelationshipCondition condition; - - public ClientTupleKeyWithCondition condition(ClientRelationshipCondition condition) { - this.condition = condition; - return this; - } - - public ClientRelationshipCondition getCondition() { - return condition; - } - - public TupleKey asTupleKey() { - var tupleKey = new TupleKey().user(getUser()).relation(getRelation())._object(getObject()); - - if (condition != null) { - tupleKey.condition(condition.asRelationshipCondition()); - } - - return tupleKey; - } - - public static ContextualTupleKeys asContextualTupleKeys(Collection tupleKeys) { - return new ContextualTupleKeys() - .tupleKeys(tupleKeys.stream() - .map(ClientTupleKeyWithCondition::asTupleKey) - .collect(Collectors.toList())); - } - - public static WriteRequestWrites asWriteRequestWrites(Collection tupleKeys) { - return new WriteRequestWrites() - .tupleKeys(tupleKeys.stream() - .map(ClientTupleKeyWithCondition::asTupleKey) - .collect(Collectors.toList())); - } - - /* Overrides for correct typing */ - - @Override - public ClientTupleKeyWithCondition user(String user) { - super.user(user); - return this; - } - - @Override - public ClientTupleKeyWithCondition relation(String relation) { - super.relation(relation); - return this; - } - - @Override - public ClientTupleKeyWithCondition _object(String _object) { - super._object(_object); - return this; - } -} diff --git a/config/clients/java/template/client-ClientTupleKeyWithoutCondition.java.mustache b/config/clients/java/template/client-ClientTupleKeyWithoutCondition.java.mustache new file mode 100644 index 00000000..fc8c597a --- /dev/null +++ b/config/clients/java/template/client-ClientTupleKeyWithoutCondition.java.mustache @@ -0,0 +1,77 @@ +{{>licenseInfo}} +package {{invokerPackage}}; + +import {{modelPackage}}.TupleKeyWithoutCondition; +import {{modelPackage}}.WriteRequestDeletes; +import java.util.Collection; +import java.util.stream.Collectors; + +public class ClientTupleKeyWithoutCondition { + private String user; + private String relation; + private String _object; + + public TupleKeyWithoutCondition asTupleKeyWithoutCondition() { + return new TupleKeyWithoutCondition().user(user).relation(relation)._object(_object); + } + + public static WriteRequestDeletes asWriteRequestDeletes(Collection tupleKeys) { + return new WriteRequestDeletes() + .tupleKeys(tupleKeys.stream() + .map(ClientTupleKeyWithoutCondition::asTupleKeyWithoutCondition) + .collect(Collectors.toList())); + } + + public ClientTupleKeyWithoutCondition _object(String _object) { + this._object = _object; + return this; + } + + /** + * Get _object + * @return _object + **/ + public String getObject() { + return _object; + } + + public ClientTupleKeyWithoutCondition relation(String relation) { + this.relation = relation; + return this; + } + + /** + * Get relation + * @return relation + **/ + public String getRelation() { + return relation; + } + + public ClientTupleKeyWithoutCondition user(String user) { + this.user = user; + return this; + } + + /** + * Get user + * @return user + **/ + public String getUser() { + return user; + } + + /** + * Adds a condition to the tuple key. + * @param condition a {@link ClientRelationshipCondition} + * @return a new {@link ClientTupleKey} with this {@link ClientTupleKeyWithoutCondition}'s + * user, relation, and object, and the passed condition. + */ + public ClientTupleKey condition(ClientRelationshipCondition condition) { + return new ClientTupleKey() + .user(user) + .relation(relation) + ._object(_object) + .condition(condition); + } +} diff --git a/config/clients/java/template/client-ClientWriteRequest.java.mustache b/config/clients/java/template/client-ClientWriteRequest.java.mustache index f0fb6dd5..a3e0f8b9 100644 --- a/config/clients/java/template/client-ClientWriteRequest.java.mustache +++ b/config/clients/java/template/client-ClientWriteRequest.java.mustache @@ -4,32 +4,32 @@ package {{invokerPackage}}; import java.util.List; public class ClientWriteRequest { - private List writes; - private List deletes; + private List writes; + private List deletes; - public static ClientWriteRequest ofWrites(List writes) { + public static ClientWriteRequest ofWrites(List writes) { return new ClientWriteRequest().writes(writes); } - public ClientWriteRequest writes(List writes) { + public ClientWriteRequest writes(List writes) { this.writes = writes; return this; } - public List getWrites() { + public List getWrites() { return writes; } - public static ClientWriteRequest ofDeletes(List deletes) { + public static ClientWriteRequest ofDeletes(List deletes) { return new ClientWriteRequest().deletes(deletes); } - public ClientWriteRequest deletes(List deletes) { + public ClientWriteRequest deletes(List deletes) { this.deletes = deletes; return this; } - public List getDeletes() { + public List getDeletes() { return deletes; } } diff --git a/config/clients/java/template/client-OpenFgaClient.java.mustache b/config/clients/java/template/client-OpenFgaClient.java.mustache index 23c662e6..067f22b3 100644 --- a/config/clients/java/template/client-OpenFgaClient.java.mustache +++ b/config/clients/java/template/client-OpenFgaClient.java.mustache @@ -280,12 +280,12 @@ public class OpenFgaClient { var writeTuples = request.getWrites(); if (writeTuples != null && !writeTuples.isEmpty()) { - body.writes(ClientTupleKeyWithCondition.asWriteRequestWrites(writeTuples)); + body.writes(ClientTupleKey.asWriteRequestWrites(writeTuples)); } var deleteTuples = request.getDeletes(); if (deleteTuples != null && !deleteTuples.isEmpty()) { - body.deletes(ClientTupleKey.asWriteRequestDeletes(deleteTuples)); + body.deletes(ClientTupleKeyWithoutCondition.asWriteRequestDeletes(deleteTuples)); } if (options != null && !isNullOrWhitespace(options.getAuthorizationModelId())) { @@ -345,14 +345,14 @@ public class OpenFgaClient { * * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace */ - public CompletableFuture writeTuples(List tupleKeys) + public CompletableFuture writeTuples(List tupleKeys) throws FgaInvalidParameterException { configuration.assertValid(); String storeId = configuration.getStoreIdChecked(); var body = new WriteRequest(); - body.writes(ClientTupleKeyWithCondition.asWriteRequestWrites(tupleKeys)); + body.writes(ClientTupleKey.asWriteRequestWrites(tupleKeys)); String authorizationModelId = configuration.getAuthorizationModelId(); if (!isNullOrWhitespace(authorizationModelId)) { @@ -367,14 +367,14 @@ public class OpenFgaClient { * * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace */ - public CompletableFuture deleteTuples(List tupleKeys) + public CompletableFuture deleteTuples(List tupleKeys) throws FgaInvalidParameterException { configuration.assertValid(); String storeId = configuration.getStoreIdChecked(); var body = new WriteRequest(); - body.deletes(ClientTupleKey.asWriteRequestDeletes(tupleKeys)); + body.deletes(ClientTupleKeyWithoutCondition.asWriteRequestDeletes(tupleKeys)); String authorizationModelId = configuration.getAuthorizationModelId(); if (!isNullOrWhitespace(authorizationModelId)) { @@ -415,7 +415,7 @@ public class OpenFgaClient { var contextualTuples = request.getContextualTuples(); if (contextualTuples != null && !contextualTuples.isEmpty()) { - body.contextualTuples(ClientTupleKeyWithCondition.asContextualTupleKeys(contextualTuples)); + body.contextualTuples(ClientTupleKey.asContextualTupleKeys(contextualTuples)); } } @@ -527,7 +527,7 @@ public class OpenFgaClient { body.user(request.getUser()).relation(request.getRelation()).type(request.getType()); if (request.getContextualTupleKeys() != null) { var contextualTuples = request.getContextualTupleKeys(); - var bodyContextualTuples = ClientTupleKeyWithCondition.asContextualTupleKeys(contextualTuples); + var bodyContextualTuples = ClientTupleKey.asContextualTupleKeys(contextualTuples); body.contextualTuples(bodyContextualTuples); } } diff --git a/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache b/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache index 0a3e691a..b1aef828 100644 --- a/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache +++ b/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache @@ -22,8 +22,8 @@ public class OpenFgaClientIntegrationTest { "{\"schema_version\":\"1.1\",\"type_definitions\":[{\"type\":\"user\"},{\"type\":\"document\",\"relations\":{\"reader\":{\"this\":{}},\"writer\":{\"this\":{}},\"owner\":{\"this\":{}}},\"metadata\":{\"relations\":{\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}}]}"; private static final String DEFAULT_USER = "user:81684243-9356-4421-8fbf-a4f8d36aa31b"; private static final String DEFAULT_DOC = "document:2021-budget"; - private static final ClientTupleKey DEFAULT_TUPLE_KEY = - new ClientTupleKey().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC); + private static final ClientTupleKeyWithoutCondition DEFAULT_TUPLE_KEY = + new ClientTupleKeyWithoutCondition().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC); private static final ClientRelationshipCondition DEFAULT_CONDITION = new ClientRelationshipCondition().name("condition").context(Map.of("some", "context")); private static final ClientAssertion DEFAULT_ASSERTION = diff --git a/config/clients/java/template/client-OpenFgaClientTest.java.mustache b/config/clients/java/template/client-OpenFgaClientTest.java.mustache index b879273a..adbe913f 100644 --- a/config/clients/java/template/client-OpenFgaClientTest.java.mustache +++ b/config/clients/java/template/client-OpenFgaClientTest.java.mustache @@ -1050,7 +1050,7 @@ public class OpenFgaClientTest { DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); ClientWriteRequest request = new ClientWriteRequest() - .writes(List.of(new ClientTupleKeyWithCondition() + .writes(List.of(new ClientTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER))); @@ -1075,7 +1075,7 @@ public class OpenFgaClientTest { DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); ClientWriteRequest request = new ClientWriteRequest() - .deletes(List.of(new ClientTupleKey() + .deletes(List.of(new ClientTupleKeyWithoutCondition() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER))); @@ -1094,11 +1094,11 @@ public class OpenFgaClientTest { String writeTupleBody = String.format( "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}", DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); - ClientTupleKey tuple = new ClientTupleKey() + ClientTupleKeyWithoutCondition tuple = new ClientTupleKeyWithoutCondition() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER); - ClientTupleKeyWithCondition writeTuple = tuple.condition(DEFAULT_CONDITION); + ClientTupleKey writeTuple = tuple.condition(DEFAULT_CONDITION); String write2Body = String.format( "{\"writes\":{\"tuple_keys\":[%s,%s]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", writeTupleBody, writeTupleBody, DEFAULT_AUTH_MODEL_ID); @@ -1155,7 +1155,7 @@ public class OpenFgaClientTest { .doReturn(400, "{\"code\":\"validation_error\",\"message\":\"Generic validation error\"}"); ClientWriteRequest request = new ClientWriteRequest() .writes(Stream.of(firstUser, failedUser, skippedUser) - .map(user -> new ClientTupleKeyWithCondition() + .map(user -> new ClientTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(user) @@ -1208,7 +1208,7 @@ public class OpenFgaClientTest { deleteTupleBody, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); - var tuple = new ClientTupleKeyWithCondition() + var tuple = new ClientTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER) @@ -1252,7 +1252,7 @@ public class OpenFgaClientTest { .onPost(postPath) .withBody(is(expectedBody)) .doReturn(400, "{\"code\":\"validation_error\",\"message\":\"Generic validation error\"}"); - var tuple = new ClientTupleKeyWithCondition() + var tuple = new ClientTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER) @@ -1283,7 +1283,7 @@ public class OpenFgaClientTest { + "\"deletes\":null,\"authorization_model_id\":\"%s\"}", DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); - List tuples = List.of(new ClientTupleKeyWithCondition() + List tuples = List.of(new ClientTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER) @@ -1305,7 +1305,7 @@ public class OpenFgaClientTest { "{\"writes\":null,\"deletes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}]},\"authorization_model_id\":\"%s\"}", DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY); - List tuples = List.of(new ClientTupleKey() + List tuples = List.of(new ClientTupleKeyWithoutCondition() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER)); @@ -1340,7 +1340,7 @@ public class OpenFgaClientTest { .onPost(postUrl) .doReturn(400, "{\"code\":\"validation_error\",\"message\":\"Generic validation error\"}"); ClientWriteRequest request = new ClientWriteRequest() - .writes(List.of(new ClientTupleKeyWithCondition() + .writes(List.of(new ClientTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER) @@ -1367,7 +1367,7 @@ public class OpenFgaClientTest { .onPost(postUrl) .doReturn(404, "{\"code\":\"undefined_endpoint\",\"message\":\"Endpoint not enabled\"}"); ClientWriteRequest request = new ClientWriteRequest() - .writes(List.of(new ClientTupleKeyWithCondition() + .writes(List.of(new ClientTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER) @@ -1393,7 +1393,7 @@ public class OpenFgaClientTest { .onPost(postUrl) .doReturn(500, "{\"code\":\"internal_error\",\"message\":\"Internal Server Error\"}"); ClientWriteRequest request = new ClientWriteRequest() - .writes(List.of(new ClientTupleKeyWithCondition() + .writes(List.of(new ClientTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER) @@ -1428,7 +1428,7 @@ public class OpenFgaClientTest { ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) .user(DEFAULT_USER) - .contextualTuples(List.of(new ClientTupleKeyWithCondition() + .contextualTuples(List.of(new ClientTupleKey() ._object(DEFAULT_OBJECT) .relation("owner") .user(DEFAULT_USER) From 0132f8da3e8538aeaebeadb7eb4249d742f548e7 Mon Sep 17 00:00:00 2001 From: "J.R. Hill" Date: Wed, 6 Dec 2023 10:39:31 -0800 Subject: [PATCH 6/7] chore: use openfga v1.4.0-rc1 for integration tests --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e2368e62..b2c30e12 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # Main config -OPENFGA_DOCKER_TAG = latest +OPENFGA_DOCKER_TAG = v1.4.0-rc1 OPEN_API_URL = https://raw.githubusercontent.com/openfga/api/main/docs/openapiv2/apidocs.swagger.json OPENAPI_GENERATOR_CLI_DOCKER_TAG = v6.4.0 NODE_DOCKER_TAG = 18-alpine From 3f9696a8cc36522afef556ad66661ac243ad9792 Mon Sep 17 00:00:00 2001 From: "J.R. Hill" Date: Thu, 7 Dec 2023 12:39:31 -0800 Subject: [PATCH 7/7] test: update integ test auth model for ABAC --- config/clients/java/config.overrides.json | 4 ++ .../OpenFgaApiIntegrationTest.java.mustache | 36 +++++----- config/clients/java/template/auth-model.json | 67 +++++++++++++++++++ ...OpenFgaClientIntegrationTest.java.mustache | 57 +++++++++------- 4 files changed, 125 insertions(+), 39 deletions(-) create mode 100644 config/clients/java/template/auth-model.json diff --git a/config/clients/java/config.overrides.json b/config/clients/java/config.overrides.json index 970ea67c..0897a7d5 100644 --- a/config/clients/java/config.overrides.json +++ b/config/clients/java/config.overrides.json @@ -31,6 +31,10 @@ "allowUnicodeIdentifiers": true, "caseInsensitiveResponseHeaders": true, "files": { + "auth-model.json" : { + "destinationFilename": "src/test-integration/resources/auth-model.json", + "templateType": "SupportingFiles" + }, "build.gradle.mustache" : { "destinationFilename": "build.gradle", "templateType": "SupportingFiles" diff --git a/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache b/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache index deeb8a90..0fb562b3 100644 --- a/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache +++ b/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache @@ -6,27 +6,36 @@ import static org.junit.jupiter.api.Assertions.*; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import {{invokerPackage}}.*; -import {{modelPackage}}.*; import {{configPackage}}.*; -import dev.openfga.errors.ApiException; +import {{modelPackage}}.*; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.net.http.HttpClient; import java.util.List; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +@TestInstance(Lifecycle.PER_CLASS) public class OpenFgaApiIntegrationTest { private static final ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); - private static final String DEFAULT_AUTH_MODEL = - "{\"schema_version\":\"1.1\",\"type_definitions\":[{\"type\":\"user\"},{\"type\":\"document\",\"relations\":{\"reader\":{\"this\":{}},\"writer\":{\"this\":{}},\"owner\":{\"this\":{}}},\"metadata\":{\"relations\":{\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}}]}"; private static final String DEFAULT_USER = "user:81684243-9356-4421-8fbf-a4f8d36aa31b"; private static final String DEFAULT_DOC = "document:2021-budget"; - public static final TupleKey DEFAULT_TUPLE_KEY = + private static final TupleKey DEFAULT_TUPLE_KEY = new TupleKey().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC); - public static final List DEFAULT_TUPLE_KEYS = List.of(DEFAULT_TUPLE_KEY); + private static final List DEFAULT_TUPLE_KEYS = List.of(DEFAULT_TUPLE_KEY); + private String authModelJson; private OpenFgaApi api; + @BeforeAll + public void loadAuthModelJson() throws IOException { + authModelJson = Files.readString(Paths.get("src", "test-integration", "resources", "auth-model.json")); + } + @BeforeEach public void initializeApi() throws Exception { System.setProperty("HttpRequestAttempt.debug-logging", "enable"); @@ -114,7 +123,7 @@ public class OpenFgaApiIntegrationTest { assertEquals(authModelId, authModel.getId()); String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions()); assertEquals( - "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]}}}}]", + "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"conditional_reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"name_starts_with_a\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]}}}}]", typeDefsJson); } @@ -138,7 +147,7 @@ public class OpenFgaApiIntegrationTest { String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions()); assertEquals( - "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]}}}}]", + "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"conditional_reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"name_starts_with_a\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]}}}}]", typeDefsJson); } catch (JsonProcessingException ex) { assertNull(ex); @@ -151,8 +160,7 @@ public class OpenFgaApiIntegrationTest { // Given String storeName = thisTestName(); String storeId = createStore(storeName); - WriteAuthorizationModelRequest request = - mapper.readValue(DEFAULT_AUTH_MODEL, WriteAuthorizationModelRequest.class); + WriteAuthorizationModelRequest request = mapper.readValue(authModelJson, WriteAuthorizationModelRequest.class); // When WriteAuthorizationModelResponse response = @@ -310,10 +318,8 @@ public class OpenFgaApiIntegrationTest { * @return The created Authorization Model ID */ private String writeAuthModel(String storeId) throws Exception { - WriteAuthorizationModelRequest request = - mapper.readValue(DEFAULT_AUTH_MODEL, WriteAuthorizationModelRequest.class); - WriteAuthorizationModelResponse response = - api.writeAuthorizationModel(storeId, request).get().getData(); + var request = mapper.readValue(authModelJson, WriteAuthorizationModelRequest.class); + var response = api.writeAuthorizationModel(storeId, request).get().getData(); return response.getAuthorizationModelId(); } diff --git a/config/clients/java/template/auth-model.json b/config/clients/java/template/auth-model.json new file mode 100644 index 00000000..3ef492c5 --- /dev/null +++ b/config/clients/java/template/auth-model.json @@ -0,0 +1,67 @@ +{ + "schema_version": "1.1", + "type_definitions": [ + { + "type": "user" + }, + { + "type": "document", + "relations": { + "reader": { + "this": {} + }, + "writer": { + "this": {} + }, + "owner": { + "this": {} + } + }, + "metadata": { + "relations": { + "reader": { + "directly_related_user_types": [ + { + "type": "user" + } + ] + }, + "writer": { + "directly_related_user_types": [ + { + "type": "user" + } + ] + }, + "owner": { + "directly_related_user_types": [ + { + "type": "user" + } + ] + }, + "conditional_reader": { + "directly_related_user_types": [ + { + "condition": "name_starts_with_a", + "type": "user" + } + ] + } + } + } + } + ], + "conditions": { + "name_starts_with_a": { + "name": "name_starts_with_a", + "expression": "name.startsWith(\"a\")", + "parameters": { + "name": { + "type_name": "TYPE_NAME_STRING" + } + } + } + } + } + \ No newline at end of file diff --git a/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache b/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache index b1aef828..b6d3c6b5 100644 --- a/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache +++ b/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache @@ -6,31 +6,47 @@ import static org.junit.jupiter.api.Assertions.*; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import {{invokerPackage}}.*; -import {{modelPackage}}.*; import {{configPackage}}.*; +import {{modelPackage}}.*; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import dev.openfga.errors.ApiException; import java.net.http.HttpClient; import java.util.List; import java.util.Map; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +@TestInstance(Lifecycle.PER_CLASS) public class OpenFgaClientIntegrationTest { private static final ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); - private static final String DEFAULT_AUTH_MODEL = - "{\"schema_version\":\"1.1\",\"type_definitions\":[{\"type\":\"user\"},{\"type\":\"document\",\"relations\":{\"reader\":{\"this\":{}},\"writer\":{\"this\":{}},\"owner\":{\"this\":{}}},\"metadata\":{\"relations\":{\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}}]}"; private static final String DEFAULT_USER = "user:81684243-9356-4421-8fbf-a4f8d36aa31b"; private static final String DEFAULT_DOC = "document:2021-budget"; - private static final ClientTupleKeyWithoutCondition DEFAULT_TUPLE_KEY = - new ClientTupleKeyWithoutCondition().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC); - private static final ClientRelationshipCondition DEFAULT_CONDITION = - new ClientRelationshipCondition().name("condition").context(Map.of("some", "context")); + private static final ClientTupleKeyWithoutCondition DEFAULT_TUPLE_KEY_NO_CONDITION = + new ClientTupleKeyWithoutCondition() + .user(DEFAULT_USER) + .relation("reader") + ._object(DEFAULT_DOC); + private static final ClientTupleKey DEFAULT_TUPLE_KEY = new ClientTupleKeyWithoutCondition() + .user(DEFAULT_USER) + .relation("reader") + ._object(DEFAULT_DOC) + .condition(null); // TODO: Add integ tests with conditions private static final ClientAssertion DEFAULT_ASSERTION = new ClientAssertion().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC).expectation(true); + private String authModelJson; private OpenFgaClient fga; + @BeforeAll + public void loadAuthModelJson() throws IOException { + authModelJson = Files.readString(Paths.get("src", "test-integration", "resources", "auth-model.json")); + } + @BeforeEach public void initializeApi() throws Exception { System.setProperty("HttpRequestAttempt.debug-logging", "enable"); @@ -125,7 +141,7 @@ public class OpenFgaClientIntegrationTest { assertEquals(authModelId, response.getAuthorizationModel().getId()); String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions()); assertEquals( - "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]}}}}]", + "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"conditional_reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"name_starts_with_a\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]}}}}]", typeDefsJson); } @@ -153,7 +169,7 @@ public class OpenFgaClientIntegrationTest { String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions()); assertEquals( - "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":null}]}}}}]", + "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"conditional_reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"name_starts_with_a\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]}}}}]", typeDefsJson); } catch (JsonProcessingException ex) { assertNull(ex); @@ -167,8 +183,7 @@ public class OpenFgaClientIntegrationTest { String storeName = thisTestName(); String storeId = createStore(storeName); fga.setStoreId(storeId); - WriteAuthorizationModelRequest request = - mapper.readValue(DEFAULT_AUTH_MODEL, WriteAuthorizationModelRequest.class); + WriteAuthorizationModelRequest request = mapper.readValue(authModelJson, WriteAuthorizationModelRequest.class); // When WriteAuthorizationModelResponse response = fga.writeAuthorizationModel(request).get(); @@ -188,8 +203,7 @@ public class OpenFgaClientIntegrationTest { String authModelId = writeAuthModel(storeId); fga.setAuthorizationModelId(authModelId); - ClientWriteRequest writeRequest = - new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY.condition(DEFAULT_CONDITION))); + ClientWriteRequest writeRequest = new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY)); ClientReadRequest readRequest = new ClientReadRequest().user(DEFAULT_USER)._object(DEFAULT_DOC); @@ -214,8 +228,7 @@ public class OpenFgaClientIntegrationTest { fga.setStoreId(storeId); String authModelId = writeAuthModel(storeId); fga.setAuthorizationModelId(authModelId); - ClientWriteRequest writeRequest = - new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY.condition(DEFAULT_CONDITION))); + ClientWriteRequest writeRequest = new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY)); ClientCheckRequest checkRequest = new ClientCheckRequest() .user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC); @@ -236,8 +249,7 @@ public class OpenFgaClientIntegrationTest { fga.setStoreId(storeId); String authModelId = writeAuthModel(storeId); fga.setAuthorizationModelId(authModelId); - ClientWriteRequest writeRequest = - new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY.condition(DEFAULT_CONDITION))); + ClientWriteRequest writeRequest = new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY)); ClientExpandRequest expandRequest = new ClientExpandRequest()._object(DEFAULT_DOC).relation("reader"); @@ -267,8 +279,7 @@ public class OpenFgaClientIntegrationTest { fga.setStoreId(storeId); String authModelId = writeAuthModel(storeId); fga.setAuthorizationModelId(authModelId); - ClientWriteRequest writeRequest = - new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY.condition(DEFAULT_CONDITION))); + ClientWriteRequest writeRequest = new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY)); ClientListObjectsRequest listObjectsRequest = new ClientListObjectsRequest().user(DEFAULT_USER).relation("reader").type("document"); @@ -322,10 +333,8 @@ public class OpenFgaClientIntegrationTest { */ private String writeAuthModel(String storeId) throws Exception { fga.setStoreId(storeId); - WriteAuthorizationModelRequest request = - mapper.readValue(DEFAULT_AUTH_MODEL, WriteAuthorizationModelRequest.class); - WriteAuthorizationModelResponse response = - fga.writeAuthorizationModel(request).get(); + var request = mapper.readValue(authModelJson, WriteAuthorizationModelRequest.class); + var response = fga.writeAuthorizationModel(request).get(); return response.getAuthorizationModelId(); }