Skip to content

Commit

Permalink
feat(java-sdk): ABAC (attribute based access control) support (#239)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhamzeh authored Dec 8, 2023
2 parents fa7d3b9 + 3f9696a commit 3d0557d
Show file tree
Hide file tree
Showing 18 changed files with 699 additions and 235 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
12 changes: 12 additions & 0 deletions config/clients/java/config.overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -123,10 +127,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-ClientTupleKeyWithoutCondition.java.mustache" : {
"destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientTupleKeyWithoutCondition.java",
"templateType": "SupportingFiles"
},
"client-ClientWriteAssertionsResponse.java.mustache" : {
"destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientWriteAssertionsResponse.java",
"templateType": "SupportingFiles"
Expand Down
18 changes: 18 additions & 0 deletions config/clients/java/template/.github/dependabot.yaml
Original file line number Diff line number Diff line change
@@ -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:
- "*"
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,40 @@ 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<TupleKey> DEFAULT_TUPLE_KEYS = List.of(DEFAULT_TUPLE_KEY);
private static final List<TupleKey> 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");
Configuration apiConfig = new Configuration().apiUrl("http://localhost:8080");
api = new OpenFgaApi(apiConfig);
}
Expand Down Expand Up @@ -112,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}]},\"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\":{\"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);
}

Expand All @@ -136,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}]},\"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\":{\"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);
Expand All @@ -149,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 =
Expand All @@ -168,9 +178,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();
Expand All @@ -189,9 +199,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();
Expand All @@ -207,9 +217,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();
Expand All @@ -229,7 +239,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");
Expand All @@ -249,7 +259,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();
Expand All @@ -261,7 +271,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);
}

Expand All @@ -272,7 +282,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();
Expand All @@ -282,7 +297,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);
}

Expand All @@ -303,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();
}

Expand Down
Loading

0 comments on commit 3d0557d

Please sign in to comment.