diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 387c966..d10a714 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -28,11 +28,17 @@ jobs: - name: Set up Java for publishing to Maven Central Repository uses: actions/setup-java@v3 with: - java-version: 8 + java-version: 17 distribution: temurin - - name: Run tests - run: mvn clean test + - name: Set up Homebrew + uses: Homebrew/actions/setup-homebrew@master + + - name: Install topaz + run: brew tap aserto-dev/tap && brew install aserto-dev/tap/topaz && topaz install + + - name: Run all tests + run: mvn clean test -Pintegration release: runs-on: ubuntu-latest needs: build diff --git a/examples/directory-example/.gitignore b/examples/directory-example/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/examples/directory-example/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/examples/directory-example/pom.xml b/examples/directory-example/pom.xml new file mode 100644 index 0000000..1c9e8fe --- /dev/null +++ b/examples/directory-example/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + org.example + directory-example + 0.0.1 + + + 17 + 17 + UTF-8 + + + + + com.aserto + aserto-java + 0.20.10 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 + + false + + + + + shade + + + true + + + + + org.example.DirectoryExample + + + + + + + + + \ No newline at end of file diff --git a/examples/directory-example/src/main/java/org/example/DirectoryExample.java b/examples/directory-example/src/main/java/org/example/DirectoryExample.java new file mode 100644 index 0000000..4874f83 --- /dev/null +++ b/examples/directory-example/src/main/java/org/example/DirectoryExample.java @@ -0,0 +1,53 @@ +package org.example; + +import com.aserto.ChannelBuilder; +import com.aserto.directory.v3.DirectoryClient; +import com.aserto.directory.common.v3.ObjectIdentifier; +import com.aserto.directory.reader.v3.GetObjectManyResponse; +import com.aserto.directory.reader.v3.GetObjectResponse; +import com.aserto.directory.reader.v3.GetObjectsResponse; +import com.aserto.directory.v3.Directory; +import io.grpc.ManagedChannel; + +import javax.net.ssl.SSLException; +import java.util.List; + +public class DirectoryExample { + public static void main(String[] args) throws SSLException { + // create a channel that has the connection details + ManagedChannel channel = new ChannelBuilder() + .withHost("localhost") + .withPort(9292) + .withInsecure(true) + .build(); + + // create a directory client that wil be used to interact with the directory + DirectoryClient directoryClient = new DirectoryClient(channel); + + getUserExample(directoryClient); + getUsersExample(directoryClient); + getObjectManyRequest(directoryClient); + } + + public static void getUserExample(DirectoryClient directoryClient) { + System.out.println("------ Get user example ------"); + GetObjectResponse getObjectResponse = directoryClient.getObject("user", "morty@the-citadel.com", false); + System.out.println(getObjectResponse); + } + + public static void getUsersExample(DirectoryClient directoryClient) { + System.out.println("------ Get users example ------"); + GetObjectsResponse getObjectsResponse = directoryClient.getObjects("user", 100, ""); + System.out.println(getObjectsResponse); + } + + public static void getObjectManyRequest(DirectoryClient directoryClient) { + System.out.println("------ Get object many example ------"); + List objects = List.of( + Directory.buildObjectIdentifier("user", "rick@the-citadel.com"), + Directory.buildObjectIdentifier("user", "morty@the-citadel.com")); + + GetObjectManyResponse getObjectManyRequest = directoryClient.getObjectManyRequest(objects); + System.out.println(getObjectManyRequest); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5f0e8ce..7b755d5 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.aserto aserto-java - 0.20.9 + 0.20.10 ${project.groupId}:${project.artifactId} Java SDK to interact with aserto services @@ -37,11 +37,11 @@ - 8 - 8 + 17 + 17 UTF-8 - 1.58.0 + 1.59.0 IntegrationTest @@ -49,25 +49,25 @@ com.aserto java-authorizer - 0.20.6 + 0.20.7 com.aserto java-directory - 0.0.1 + 0.30.1 org.junit.jupiter junit-jupiter-engine - 5.10.0 + 5.10.1 test - org.junit.platform - junit-platform-runner - 1.10.0 + org.assertj + assertj-core + 3.24.2 test @@ -83,6 +83,18 @@ 3.12.4 test + + + + org.apache.logging.log4j + log4j-api + 2.21.1 + + + org.apache.logging.log4j + log4j-core + 2.21.1 + @@ -174,7 +186,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.1.2 + 3.2.2 ${exclude-tests} diff --git a/src/main/java/com/aserto/ChannelBuilder.java b/src/main/java/com/aserto/ChannelBuilder.java index 2fe06fd..f685b67 100644 --- a/src/main/java/com/aserto/ChannelBuilder.java +++ b/src/main/java/com/aserto/ChannelBuilder.java @@ -90,7 +90,11 @@ public ManagedChannel build() throws SSLException { .intercept(MetadataUtils.newAttachHeadersInterceptor(metadata)); boolean insecure = cfg.getInsecure(); - boolean caSpecified = !cfg.getCaCertPath().isEmpty(); + + boolean caSpecified = true; + if (cfg.getCaCertPath() == null || cfg.getCaCertPath().isEmpty()) { + caSpecified = false; + } if (insecure) { SslContext context = GrpcSslContexts.forClient() diff --git a/src/main/java/com/aserto/DirectoryClient.java b/src/main/java/com/aserto/DirectoryClient.java deleted file mode 100644 index 824b586..0000000 --- a/src/main/java/com/aserto/DirectoryClient.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.aserto; - -import com.aserto.directory.common.v2.ObjectTypeIdentifier; -import com.aserto.directory.common.v2.PaginationRequest; -import com.aserto.directory.exporter.v2.ExporterGrpc; -import com.aserto.directory.importer.v2.ImporterGrpc; -import com.aserto.directory.reader.v2.*; -import com.aserto.directory.writer.v2.WriterGrpc; -import com.aserto.directory.common.v2.Object; -import io.grpc.ManagedChannel; - -public class DirectoryClient { - private ReaderGrpc.ReaderBlockingStub readerClient; - private WriterGrpc.WriterBlockingStub writerClient; - private ImporterGrpc.ImporterBlockingStub importerClient; - private ExporterGrpc.ExporterBlockingStub exporterClient; - private ManagedChannel channel; - - public DirectoryClient(ManagedChannel channel) { - this.readerClient = ReaderGrpc.newBlockingStub(channel); - this.writerClient = WriterGrpc.newBlockingStub(channel); - this.importerClient = ImporterGrpc.newBlockingStub(channel); - this.exporterClient = ExporterGrpc.newBlockingStub(channel); - this.channel = channel; - } - - public ReaderGrpc.ReaderBlockingStub getReaderClient() { - return readerClient; - } - - public WriterGrpc.WriterBlockingStub getWriterClient() { - return writerClient; - } - - public ImporterGrpc.ImporterBlockingStub getImporterClient() { - return importerClient; - } - - public ExporterGrpc.ExporterBlockingStub getExporterClient() { - return exporterClient; - } - class Result { - private T[] results; - private String nextPageToken; - - public Result(T[] results, String nextPageToken) { - this.results = results; - this.nextPageToken = nextPageToken; - } - - public T[] getResults() { - return results; - } - - public String getNextPageToken() { - return nextPageToken; - } - } - - public Result getObjects(String objectType, Integer pageSize, String nextPageToken) { - GetObjectsRequest.Builder builder = GetObjectsRequest.newBuilder(); - - PaginationRequest paginationRequest = PaginationRequest.newBuilder() - .setSize(pageSize) - .setToken(nextPageToken) - .build(); - ObjectTypeIdentifier objectIdentifier = ObjectTypeIdentifier.newBuilder() - .setName(objectType) - .build(); - - GetObjectsRequest request = builder - .setPage(paginationRequest) - .setParam(objectIdentifier) - .build(); - GetObjectsResponse response = readerClient.getObjects(request); - String nextToken = response.getPage().getNextToken(); - - - Object[] objects = response.getResultsList().toArray(new Object[0]); - return new Result<>(objects, nextToken); - } - - public void close() { - channel.shutdown(); - } -} diff --git a/src/main/java/com/aserto/AuthzClient.java b/src/main/java/com/aserto/authorizer/AuthzClient.java similarity index 98% rename from src/main/java/com/aserto/AuthzClient.java rename to src/main/java/com/aserto/authorizer/AuthzClient.java index e67c85b..3c57a2c 100644 --- a/src/main/java/com/aserto/AuthzClient.java +++ b/src/main/java/com/aserto/authorizer/AuthzClient.java @@ -1,5 +1,6 @@ -package com.aserto; +package com.aserto.authorizer; +import com.aserto.AuthorizerClient; import com.aserto.authorizer.v2.*; import com.aserto.authorizer.v2.Decision; import com.aserto.authorizer.v2.api.*; diff --git a/src/main/java/com/aserto/directory/v3/Directory.java b/src/main/java/com/aserto/directory/v3/Directory.java new file mode 100644 index 0000000..a23b154 --- /dev/null +++ b/src/main/java/com/aserto/directory/v3/Directory.java @@ -0,0 +1,26 @@ +package com.aserto.directory.v3; + +import com.aserto.directory.common.v3.Object; +import com.aserto.directory.common.v3.ObjectIdentifier; +import com.aserto.directory.common.v3.Relation; + +public class Directory { + + public static Object buildObject(String type, String id) { + return Object.newBuilder().setType(type).setId(id).build(); + } + + public static ObjectIdentifier buildObjectIdentifier(String type, String id) { + return ObjectIdentifier.newBuilder().setObjectType(type).setObjectId(id).build(); + } + + public static Relation buildRelation(String objectType, String objectId, String relation, String subjectType, String subjectId) { + return Relation.newBuilder() + .setObjectType(objectType) + .setObjectId(objectId) + .setRelation(relation) + .setSubjectType(subjectType) + .setSubjectId(subjectId) + .build(); + } +} diff --git a/src/main/java/com/aserto/directory/v3/DirectoryClient.java b/src/main/java/com/aserto/directory/v3/DirectoryClient.java new file mode 100644 index 0000000..b34cbe9 --- /dev/null +++ b/src/main/java/com/aserto/directory/v3/DirectoryClient.java @@ -0,0 +1,415 @@ +package com.aserto.directory.v3; + +import com.aserto.utils.MessageChunker; +import com.aserto.directory.common.v3.ObjectIdentifier; +import com.aserto.directory.common.v3.PaginationRequest; +import com.aserto.directory.common.v3.Relation; +import com.aserto.directory.exporter.v3.ExportRequest; +import com.aserto.directory.exporter.v3.ExportResponse; +import com.aserto.directory.exporter.v3.ExporterGrpc; +import com.aserto.directory.exporter.v3.Option; +import com.aserto.directory.importer.v3.ImportRequest; +import com.aserto.directory.importer.v3.ImportResponse; +import com.aserto.directory.importer.v3.ImporterGrpc; +import com.aserto.directory.model.v3.*; +import com.aserto.directory.reader.v3.*; +import com.aserto.directory.writer.v3.*; +import com.aserto.model.ImportElement; +import com.google.protobuf.ByteString; +import com.google.protobuf.Struct; +import com.aserto.directory.common.v3.Object; + + +import com.google.protobuf.Timestamp; +import io.grpc.ManagedChannel; +import io.grpc.stub.StreamObserver; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.time.Instant; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +public class DirectoryClient implements DirectoryClientReader, + DirectoryClientWriter, + DirectoryClientModel, + DirectoryClientImporter, + DirectoryClientExporter { + static final int MAX_CHUNK_SIZE = 65536; + Logger logger = LogManager.getLogger(DirectoryClient.class); + private ReaderGrpc.ReaderBlockingStub readerClient; + private WriterGrpc.WriterBlockingStub writerClient; + private ImporterGrpc.ImporterStub importerClient; + private ExporterGrpc.ExporterBlockingStub exporterClient; + private ModelGrpc.ModelBlockingStub modelClient; + private ModelGrpc.ModelStub modelClientAsync; + + public DirectoryClient(ManagedChannel channelBuilder) { + DirectoryClientBuilder dirClientBuilder = new DirectoryClientBuilder(channelBuilder); + readerClient = dirClientBuilder.getReaderClient(); + writerClient = dirClientBuilder.getWriterClient(); + importerClient = dirClientBuilder.getImporterClient(); + exporterClient = dirClientBuilder.getExporterClient(); + modelClient = dirClientBuilder.getModelClient(); + modelClientAsync = dirClientBuilder.getModelClientAsync(); + } + + @Override + public GetObjectResponse getObject(String type, String id) { + return getObject(type, id, false); + } + @Override + public GetObjectResponse getObject(String type, String id, boolean withRelations) { + return readerClient.getObject(GetObjectRequest.newBuilder() + .setObjectType(type) + .setObjectId(id) + .setWithRelations(withRelations) + .build()); + } + + @Override + public GetObjectsResponse getObjects(String type) { + return getObjects(type, 100, ""); + } + + @Override + public GetObjectsResponse getObjects(String type, int pageSize, String pageToken) { + return readerClient.getObjects(GetObjectsRequest.newBuilder() + .setObjectType(type) + .setPage(buildPaginationRequest(pageSize, pageToken)) + .build()); + } + + @Override + public GetObjectManyResponse getObjectManyRequest(List objectIdentifiers) { + return readerClient.getObjectMany(GetObjectManyRequest.newBuilder() + .addAllParam(new ObjectIdentifierList(objectIdentifiers)) + .build()); + } + + private PaginationRequest buildPaginationRequest(int pageSize, String pageToken) { + return PaginationRequest.newBuilder() + .setSize(pageSize) + .setToken(pageToken) + .build(); + } + + @Override + public GetRelationResponse getRelation(String objectType, String objectId, String relationName, String subjectType, String subjectId) { + return getRelation(objectType, objectId, relationName, subjectType, subjectId, "", false); + } + + @Override + public GetRelationResponse getRelation(String objectType, String objectId, String relationName, String subjectType, String subjectId, String subjectRelation) { + return getRelation(objectType, objectId, relationName, subjectType, subjectId, subjectRelation, false); + } + + @Override + public GetRelationResponse getRelation(String objectType, String objectId, String relationName, String subjectType, String subjectId, String subjectRelation, boolean withObjects) { + return readerClient.getRelation(GetRelationRequest.newBuilder() + .setObjectType(objectType) + .setObjectId(objectId) + .setRelation(relationName) + .setSubjectType(subjectType) + .setSubjectId(subjectId) + .setSubjectRelation(subjectRelation) + .setWithObjects(withObjects) + .build()); + } + + @Override + public GetRelationsResponse getRelations(GetRelationsRequest relationsRequest) { + return readerClient.getRelations(relationsRequest); + } + + @Override + public CheckPermissionResponse checkPermission(String objectType, String objectId, String subjectType, String subjectId, String permissionName) { + return checkPermission(objectType, objectId, subjectType, subjectId, permissionName, false); + } + + @Override + public CheckPermissionResponse checkPermission(String objectType, String objectId, String subjectType, String subjectId, String permissionName, boolean trace) { + return readerClient.checkPermission(CheckPermissionRequest.newBuilder() + .setObjectType(objectType) + .setObjectId(objectId) + .setSubjectType(subjectType) + .setSubjectId(subjectId) + .setPermission(permissionName) + .setTrace(trace) + .build()); + } + + @Override + public CheckRelationResponse checkRelation(String objectType, String objectId, String relationName, String subjectType, String subjectId) { + return checkRelation(objectType, objectId, relationName, subjectType, subjectId, false); + } + + @Override + public CheckRelationResponse checkRelation(String objectType, String objectId, String relationName, String subjectType, String subjectId, boolean trace) { + return readerClient.checkRelation(CheckRelationRequest.newBuilder() + .setObjectType(objectType) + .setObjectId(objectId) + .setRelation(relationName) + .setSubjectType(subjectType) + .setSubjectId(subjectId) + .setTrace(trace) + .build()); + } + + @Override + public CheckResponse check(String objectType, String objectId, String relationName, String subjectType, String subjectId) { + return check(objectType, objectId, relationName, subjectType, subjectId, false); + } + + @Override + public CheckResponse check(String objectType, String objectId, String relationName, String subjectType, String subjectId, boolean trace) { + return readerClient.check(CheckRequest.newBuilder() + .setObjectType(objectType) + .setObjectId(objectId) + .setRelation(relationName) + .setSubjectType(subjectType) + .setSubjectId(subjectId) + .setTrace(trace) + .build()); + } + + @Override + public GetGraphResponse getGraph(GetGraphRequest getGraphRequest) { + return readerClient.getGraph(getGraphRequest); + } + + @Override + public SetObjectResponse setObject(String type, String id) { + return setObject(type, id, "", Struct.newBuilder().build(), ""); + } + + @Override + public SetObjectResponse setObject(String type, String id, String displayName, Struct properties, String hash) { + Instant time = Instant.now(); + Timestamp timestamp = Timestamp.newBuilder().setSeconds(time.getEpochSecond()) + .setNanos(time.getNano()).build(); + + SetObjectRequest objRequest = SetObjectRequest.newBuilder().setObject( + Object.newBuilder() + .setType(type) + .setId(id) + .setDisplayName(displayName) + .setProperties(properties) + .setCreatedAt(timestamp) + .build() + ).build(); + + return writerClient.setObject(objRequest); + } + + @Override + public DeleteObjectResponse deleteObject(String type, String id) { + return deleteObject(type, id, false); + } + + @Override + public DeleteObjectResponse deleteObject(String type, String id, boolean withRelations) { + return writerClient.deleteObject(DeleteObjectRequest.newBuilder() + .setObjectType(type) + .setObjectId(id) + .setWithRelations(withRelations) + .build()); + } + + @Override + public SetRelationResponse setRelation(String objectType, String objectId, String relationName, String subjectType, String subjectId) { + Relation relation = Relation.newBuilder() + .setObjectType(objectType) + .setObjectId(objectId) + .setRelation(relationName) + .setSubjectType(subjectType) + .setSubjectId(subjectId) + .build(); + + return writerClient.setRelation(SetRelationRequest.newBuilder().setRelation(relation).build()); + } + + @Override + public SetRelationResponse setRelation(String objectType, String objectId, String relationName, String subjectType, String subjectId, String subjectRelation) { + Relation relation = Relation.newBuilder() + .setObjectType(objectType) + .setObjectId(objectId) + .setRelation(relationName) + .setSubjectType(subjectType) + .setSubjectId(subjectId) + .setSubjectRelation(subjectRelation) + .build(); + + return writerClient.setRelation(SetRelationRequest.newBuilder().setRelation(relation).build()); + } + + @Override + public SetRelationResponse setRelation(String objectType, String objectId, String relationName, String subjectType, String subjectId, String subjectRelation, String hash) { + Relation relation = Relation.newBuilder() + .setObjectType(objectType) + .setObjectId(objectId) + .setRelation(relationName) + .setSubjectType(subjectType) + .setSubjectId(subjectId) + .setSubjectRelation(subjectRelation) + .setEtag(hash) + .build(); + + return writerClient.setRelation(SetRelationRequest.newBuilder().setRelation(relation).build()); + } + + @Override + public DeleteRelationResponse deleteRelation(String objectType, String objectId, String relationName, String subjectType, String subjectId) { + return writerClient.deleteRelation(DeleteRelationRequest.newBuilder() + .setObjectType(objectType) + .setObjectId(objectId) + .setRelation(relationName) + .setSubjectType(subjectType) + .setSubjectId(subjectId) + .build()); + } + + @Override + public DeleteRelationResponse deleteRelation(String objectType, String objectId, String relationName, String subjectType, String subjectId, String subjectRelation) { + return writerClient.deleteRelation(DeleteRelationRequest.newBuilder() + .setObjectType(objectType) + .setObjectId(objectId) + .setRelation(relationName) + .setSubjectType(subjectType) + .setSubjectId(subjectId) + .setSubjectRelation(subjectRelation) + .build()); + } + + @Override + public GetManifestResponse getManifest() { + GetManifestRequest manifestRequest = GetManifestRequest.newBuilder().build(); + Iterator manifestResponses = modelClient.getManifest(manifestRequest); + + Metadata.Builder metadataBuilder = Metadata.newBuilder(); + + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream( ); + manifestResponses.forEachRemaining(manifestResponse -> { + if (!manifestResponse.getMetadata().getAllFields().isEmpty()) { + manifestResponse.getMetadata().getAllFields().forEach(metadataBuilder::setField); + } else if (!manifestResponse.getBody().getData().isEmpty()) { + try { + outputStream.write(manifestResponse.getBody().getData().toByteArray()); + } catch (IOException e) { + logger.error("Could not write to stream the fallowing message: {}", manifestResponse.getBody().getData().toByteArray()); + } + } + }); + + Body manifestBody = Body.newBuilder().setData(ByteString.copyFrom(outputStream.toByteArray())).build(); + + return GetManifestResponse.newBuilder() + .setMetadata(metadataBuilder.build()) + .setBody(manifestBody) + .build(); + } + + @Override + public void setManifest(String manifest) throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + + StreamObserver readStream = new StreamObserver() { + @Override + public void onNext(SetManifestResponse setManifestResponse) { + logger.info("Received response: [{}] ", setManifestResponse.getResult()); + } + + @Override + public void onError(Throwable throwable) { + logger.error("Error from server: [{}]: ", throwable.getMessage()); + } + + @Override + public void onCompleted() { + logger.trace("Server completed stream."); + latch.countDown(); + } + }; + + StreamObserver writeStream = modelClientAsync.setManifest(readStream); + + MessageChunker chunker = new MessageChunker(MAX_CHUNK_SIZE, manifest.getBytes()); + while (chunker.hasNextChunk()) { + byte[] chunk = chunker.nextChunk(); + Body manifestBody = Body.newBuilder().setData(ByteString.copyFrom(chunk)).build(); + writeStream.onNext(SetManifestRequest.newBuilder().setBody(manifestBody).build()); + } + + writeStream.onCompleted(); + + boolean timedOut = !latch.await(5, TimeUnit.SECONDS); + if (timedOut) { + logger.error("Timed out waiting for server response."); + } + } + + + @Override + public DeleteManifestResponse deleteManifest() { + return modelClient.deleteManifest(DeleteManifestRequest.newBuilder().build()); + } + + @Override + public void importData(Stream importStream) throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + StreamObserver readStream = new StreamObserver() { + @Override + public void onNext(ImportResponse importResponse) { + logger.info("Received response: [{}] ", importResponse.getObject()); + } + + @Override + public void onError(Throwable throwable) { + logger.error("Error from server: [{}]: ", throwable.getMessage()); + } + + @Override + public void onCompleted() { + logger.trace("Server completed importStream."); + latch.countDown(); + } + }; + + StreamObserver writer = importerClient.import_(readStream); + + importStream.forEach(importElement -> { + if (importElement.getObject() != null) { + writer.onNext(ImportRequest.newBuilder().setObject(importElement.getObject()).build()); + } else if (importElement.getRelation() != null) { + writer.onNext(ImportRequest.newBuilder().setRelation(importElement.getRelation()).build()); + } + }); + writer.onCompleted(); + + boolean timedOut = !latch.await(5, TimeUnit.SECONDS); + if (timedOut) { + logger.error("Timed out waiting for server response."); + } + } + + @Override + public Iterator exportData(Option options) { + return exporterClient.export(ExportRequest.newBuilder() + .setOptions(options.getNumber()) + .build()); + } + + @Override + public Iterator exportData(Option options, Timestamp startFrom) { + return exporterClient.export(ExportRequest.newBuilder() + .setOptions(options.getNumber()) + .setStartFrom(startFrom) + .build()); + } +} diff --git a/src/main/java/com/aserto/directory/v3/DirectoryClientBuilder.java b/src/main/java/com/aserto/directory/v3/DirectoryClientBuilder.java new file mode 100644 index 0000000..3cc6308 --- /dev/null +++ b/src/main/java/com/aserto/directory/v3/DirectoryClientBuilder.java @@ -0,0 +1,146 @@ +package com.aserto.directory.v3; + +import com.aserto.directory.exporter.v3.ExporterGrpc; +import com.aserto.directory.importer.v3.ImporterGrpc; +import com.aserto.directory.model.v3.ModelGrpc; +import com.aserto.directory.reader.v3.*; +import com.aserto.directory.writer.v3.WriterGrpc; +import io.grpc.ManagedChannel; + +public class DirectoryClientBuilder { + private ReaderGrpc.ReaderBlockingStub readerClient; + private WriterGrpc.WriterBlockingStub writerClient; + private ImporterGrpc.ImporterStub importerClient; + private ExporterGrpc.ExporterBlockingStub exporterClient; + private ModelGrpc.ModelBlockingStub modelClient; + private ModelGrpc.ModelStub modelClientAsync; + private ManagedChannel channel; + private ManagedChannel readerChannel; + private ManagedChannel writerChannel; + private ManagedChannel importerChannel; + private ManagedChannel exporterChannel; + private ManagedChannel modelChannel; + + public DirectoryClientBuilder(ManagedChannel channel) { + this.readerClient = ReaderGrpc.newBlockingStub(channel); + this.writerClient = WriterGrpc.newBlockingStub(channel); + this.importerClient = ImporterGrpc.newStub(channel); + this.exporterClient = ExporterGrpc.newBlockingStub(channel); + this.modelClient = ModelGrpc.newBlockingStub(channel); + this.modelClientAsync = ModelGrpc.newStub(channel); + this.channel = channel; + } + + public DirectoryClientBuilder(ManagedChannel readerChannel, + ManagedChannel writerChannel, + ManagedChannel importerChannel, + ManagedChannel exporterChannel, + ManagedChannel modelChannel) { + + if (readerChannel != null) { + this.readerClient = ReaderGrpc.newBlockingStub(readerChannel); + this.readerChannel = readerChannel; + } + + if (writerChannel != null) { + this.writerClient = WriterGrpc.newBlockingStub(writerChannel); + this.writerChannel = writerChannel; + } + + if (importerChannel != null) { + this.importerClient = ImporterGrpc.newStub(importerChannel); + this.importerChannel = importerChannel; + } + + if (exporterChannel != null) { + this.exporterClient = ExporterGrpc.newBlockingStub(exporterChannel); + this.exporterChannel = exporterChannel; + } + + if (modelClient != null) { + this.modelClient = ModelGrpc.newBlockingStub(modelChannel); + this.modelClientAsync = ModelGrpc.newStub(modelChannel); + this.modelChannel = modelChannel; + } + } + + public ReaderGrpc.ReaderBlockingStub getReaderClient() { + return readerClient; + } + + public WriterGrpc.WriterBlockingStub getWriterClient() { + return writerClient; + } + + public ImporterGrpc.ImporterStub getImporterClient() { + return importerClient; + } + + public ExporterGrpc.ExporterBlockingStub getExporterClient() { + return exporterClient; + } + + public ModelGrpc.ModelBlockingStub getModelClient() { + return modelClient; + } + + public ModelGrpc.ModelStub getModelClientAsync() { + return modelClientAsync; + } + + public void close() { + if (channel != null) { + channel.shutdown(); + } + + if (readerChannel != null) { + readerChannel.shutdown(); + } + + if (writerChannel != null) { + writerChannel.shutdown(); + } + + if (importerChannel != null) { + importerChannel.shutdown(); + } + + if (exporterChannel != null) { + exporterChannel.shutdown(); + } + + if (modelChannel != null) { + modelChannel.shutdown(); + } + } + + public void closeReader() { + if (readerChannel != null) { + readerChannel.shutdown(); + } + } + + public void closeWriter() { + if (writerChannel != null) { + writerChannel.shutdown(); + } + } + + public void closeImporter() { + if (importerChannel != null) { + importerChannel.shutdown(); + } + } + + public void closeExporter() { + if (exporterChannel != null) { + exporterChannel.shutdown(); + } + } + + public void closeModel() { + if (modelChannel != null) { + modelChannel.shutdown(); + } + } +} diff --git a/src/main/java/com/aserto/directory/v3/DirectoryClientExporter.java b/src/main/java/com/aserto/directory/v3/DirectoryClientExporter.java new file mode 100644 index 0000000..fd5c6e3 --- /dev/null +++ b/src/main/java/com/aserto/directory/v3/DirectoryClientExporter.java @@ -0,0 +1,12 @@ +package com.aserto.directory.v3; + +import com.aserto.directory.exporter.v3.ExportResponse; +import com.aserto.directory.exporter.v3.Option; +import com.google.protobuf.Timestamp; + +import java.util.Iterator; + +public interface DirectoryClientExporter { + Iterator exportData(Option options); + Iterator exportData(Option options, Timestamp startFrom); +} diff --git a/src/main/java/com/aserto/directory/v3/DirectoryClientImporter.java b/src/main/java/com/aserto/directory/v3/DirectoryClientImporter.java new file mode 100644 index 0000000..aea6276 --- /dev/null +++ b/src/main/java/com/aserto/directory/v3/DirectoryClientImporter.java @@ -0,0 +1,8 @@ +package com.aserto.directory.v3; + +import com.aserto.model.ImportElement; +import java.util.stream.Stream; + +public interface DirectoryClientImporter { + void importData(Stream importStream) throws InterruptedException; +} diff --git a/src/main/java/com/aserto/directory/v3/DirectoryClientModel.java b/src/main/java/com/aserto/directory/v3/DirectoryClientModel.java new file mode 100644 index 0000000..763c320 --- /dev/null +++ b/src/main/java/com/aserto/directory/v3/DirectoryClientModel.java @@ -0,0 +1,10 @@ +package com.aserto.directory.v3; + +import com.aserto.directory.model.v3.DeleteManifestResponse; +import com.aserto.directory.model.v3.GetManifestResponse; + +public interface DirectoryClientModel { + GetManifestResponse getManifest(); + void setManifest(String manifest) throws InterruptedException; + DeleteManifestResponse deleteManifest(); +} diff --git a/src/main/java/com/aserto/directory/v3/DirectoryClientReader.java b/src/main/java/com/aserto/directory/v3/DirectoryClientReader.java new file mode 100644 index 0000000..5a5b541 --- /dev/null +++ b/src/main/java/com/aserto/directory/v3/DirectoryClientReader.java @@ -0,0 +1,33 @@ +package com.aserto.directory.v3; + +import com.aserto.directory.common.v3.ObjectIdentifier; +import com.aserto.directory.reader.v3.*; + +import java.util.List; + +public interface DirectoryClientReader { + GetObjectResponse getObject(String type, String id); + GetObjectResponse getObject(String type, String id, boolean withRelations); + GetObjectsResponse getObjects(String type); + GetObjectsResponse getObjects(String type, int pageSize, String pageToken); + GetObjectManyResponse getObjectManyRequest(List objectIdentifiers); + GetRelationResponse getRelation(String objectType, String objectId, String relationName, + String subjectType, String subjectId); + GetRelationResponse getRelation(String objectType, String objectId, String relationName, String subjectType, + String subjectId, String subjectRelation); + GetRelationResponse getRelation(String objectType, String objectId, String relationName, String subjectType, + String subjectId, String subjectRelation, boolean withObjects); + GetRelationsResponse getRelations(GetRelationsRequest relationsRequest); + + CheckPermissionResponse checkPermission(String objectType, String objectId, String subjectType, + String subjectId, String permissionName); + CheckPermissionResponse checkPermission(String objectType, String objectId, + String subjectType, String subjectId, String permissionName, boolean trace); + CheckRelationResponse checkRelation(String objectType, String objectId, String relationName, String subjectType, String subjectId); + CheckRelationResponse checkRelation(String objectType, String objectId, String relationName, + String subjectType, String subjectId, boolean trace); + CheckResponse check(String objectType, String objectId, String relationName, String subjectType, String subjectId); + CheckResponse check(String objectType, String objectId, String relationName, + String subjectType, String subjectId, boolean trace); + GetGraphResponse getGraph(GetGraphRequest getGraphRequest); +} diff --git a/src/main/java/com/aserto/directory/v3/DirectoryClientWriter.java b/src/main/java/com/aserto/directory/v3/DirectoryClientWriter.java new file mode 100644 index 0000000..b07bebc --- /dev/null +++ b/src/main/java/com/aserto/directory/v3/DirectoryClientWriter.java @@ -0,0 +1,24 @@ +package com.aserto.directory.v3; + +import com.aserto.directory.writer.v3.DeleteObjectResponse; +import com.aserto.directory.writer.v3.DeleteRelationResponse; +import com.aserto.directory.writer.v3.SetObjectResponse; +import com.aserto.directory.writer.v3.SetRelationResponse; +import com.google.protobuf.Struct; + +public interface DirectoryClientWriter { + public SetObjectResponse setObject(String type, String id); + public SetObjectResponse setObject(String type, String id, String displayName, Struct properties, String hash); + public DeleteObjectResponse deleteObject(String type, String id); + public DeleteObjectResponse deleteObject(String type, String id, boolean withRelations); + public SetRelationResponse setRelation(String objectType, String objectId, String relationName, + String subjectType, String subjectId); + public SetRelationResponse setRelation(String objectType, String objectId, String relationName, + String subjectType, String subjectId, String subjectRelation); + public SetRelationResponse setRelation(String objectType, String objectId, String relationName, + String subjectType, String subjectId, String subjectRelation, String hash); + public DeleteRelationResponse deleteRelation(String objectType, String objectId, String relationName, + String subjectType, String subjectId); + public DeleteRelationResponse deleteRelation(String objectType, String objectId, String relationName, + String subjectType, String subjectId, String subjectRelation); +} diff --git a/src/main/java/com/aserto/directory/v3/ObjectIdentifierList.java b/src/main/java/com/aserto/directory/v3/ObjectIdentifierList.java new file mode 100644 index 0000000..886b7d6 --- /dev/null +++ b/src/main/java/com/aserto/directory/v3/ObjectIdentifierList.java @@ -0,0 +1,19 @@ +package com.aserto.directory.v3; + +import com.aserto.directory.common.v3.ObjectIdentifier; + +import java.util.Iterator; +import java.util.List; + +class ObjectIdentifierList implements Iterable { + private List objects; + + public ObjectIdentifierList(List objects) { + this.objects = objects; + } + + @Override + public Iterator iterator() { + return objects.iterator(); + } +} diff --git a/src/main/java/com/aserto/model/ImportElement.java b/src/main/java/com/aserto/model/ImportElement.java new file mode 100644 index 0000000..5531ce7 --- /dev/null +++ b/src/main/java/com/aserto/model/ImportElement.java @@ -0,0 +1,25 @@ +package com.aserto.model; + +import com.aserto.directory.common.v3.Object; +import com.aserto.directory.common.v3.Relation; + +public class ImportElement { + private Object object; + private Relation relation; + + public ImportElement(Object object) { + this.object = object; + } + + public ImportElement(Relation relation){ + this.relation = relation; + } + + public Object getObject() { + return object; + } + + public Relation getRelation() { + return relation; + } +} diff --git a/src/main/java/com/aserto/utils/MessageChunker.java b/src/main/java/com/aserto/utils/MessageChunker.java new file mode 100644 index 0000000..17bca84 --- /dev/null +++ b/src/main/java/com/aserto/utils/MessageChunker.java @@ -0,0 +1,28 @@ +package com.aserto.utils; + +/* Chunk messages into smaller pieces. */ +public class MessageChunker { + private int maxChunkSize; + private byte[] message; + + private int chunkStart = 0; + + public MessageChunker(int maxChunkSize, byte[] message) { + this.maxChunkSize = maxChunkSize; + this.message = message; + } + + public byte[] nextChunk() { + int chunkEnd = Math.min(chunkStart + maxChunkSize, message.length); + + byte[] chunk = new byte[chunkEnd - chunkStart]; + System.arraycopy(message, chunkStart, chunk, 0, chunk.length); + chunkStart = chunkEnd; + return chunk; + } + + public boolean hasNextChunk() { + return chunkStart < message.length; + } + +} diff --git a/src/test/java/AuthzClientIntegrationTest.java b/src/test/java/AuthzClientIntegrationTest.java index a95ef08..772a063 100644 --- a/src/test/java/AuthzClientIntegrationTest.java +++ b/src/test/java/AuthzClientIntegrationTest.java @@ -1,10 +1,12 @@ -import com.aserto.AuthzClient; +import com.aserto.authorizer.AuthzClient; import com.aserto.ChannelBuilder; import com.aserto.authorizer.v2.api.Module; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import utils.IntegrationTestsExtenion; import javax.net.ssl.SSLException; import java.io.IOException; @@ -14,9 +16,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; @Tag("IntegrationTest") +@ExtendWith({IntegrationTestsExtenion.class}) class AuthzClientIntegrationTest { @Test - @Tag("IntegrationTest") void testBuildAuthzClient() throws IOException { // Arrange ManagedChannel channel = new ChannelBuilder() @@ -33,11 +35,10 @@ void testBuildAuthzClient() throws IOException { authzClient.close(); // Assert - assertEquals(6, policies.size()); + assertEquals(5, policies.size()); } @Test - @Tag("IntegrationTest") void testInsecureConnectionToInsecureClient() throws SSLException { // Arrange ManagedChannel channel = new ChannelBuilder() @@ -53,11 +54,10 @@ void testInsecureConnectionToInsecureClient() throws SSLException { authzClient.close(); // Assert - assertEquals(6, policies.size()); + assertEquals(5, policies.size()); } @Test - @Tag("IntegrationTest") void testFailWhenSecureConnectionToInsecureClient() throws SSLException { // Arrange ManagedChannel channel = new ChannelBuilder() diff --git a/src/test/java/AuthzClientTest.java b/src/test/java/AuthzClientTest.java index 983f1ee..4a43c7a 100644 --- a/src/test/java/AuthzClientTest.java +++ b/src/test/java/AuthzClientTest.java @@ -1,5 +1,5 @@ import com.aserto.AuthorizerClient; -import com.aserto.AuthzClient; +import com.aserto.authorizer.AuthzClient; import com.aserto.authorizer.v2.*; import com.aserto.authorizer.v2.api.IdentityType; import com.aserto.authorizer.v2.api.Module; @@ -12,10 +12,7 @@ import io.grpc.inprocess.InProcessServerBuilder; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; -import org.junit.Rule; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import javax.net.ssl.SSLException; import java.io.IOException; @@ -27,12 +24,10 @@ class AuthzClientTest { - - @Rule - public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); + public static final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); // Will be used to mock the grpc server - private final AuthorizerGrpc.AuthorizerImplBase serviceImpl = + private static final AuthorizerGrpc.AuthorizerImplBase serviceImpl = mock(AuthorizerGrpc.AuthorizerImplBase.class, delegatesTo( new AuthorizerGrpc.AuthorizerImplBase() { // Implement necessary behaviour for tests by overriding the grpc called methods @@ -114,10 +109,10 @@ public void getPolicy(GetPolicyRequest request, StreamObserver list = importCitadelDataList(); + directoryClient.importData(list.stream()); + } + + @AfterEach + void afterEach() { + directoryClient.deleteManifest(); + } + + @Test + void testGetUserWithNoRelations() { + // Arrange + Object managerObject = Directory.buildObject("user", "rick@the-citadel.com"); + + // Act + GetObjectResponse getObjectResponse = directoryClient.getObject("user", "rick@the-citadel.com"); + + // Assert + assertThat(getObjectResponse.getResult()) + .usingRecursiveComparison() + .comparingOnlyFields("objectType_", "objectId_") + .isEqualTo(managerObject); + assertEquals(0, getObjectResponse.getRelationsList().size()); + } + + @Test + void testGetUserWithRelations() { + // Arrange + Object managerObject = Directory.buildObject("user", "rick@the-citadel.com"); + Relation managerRelation = Directory.buildRelation("user", "morty@the-citadel.com", "manager", "user", "rick@the-citadel.com"); + Relation adminRelation = Directory.buildRelation("group", "admin", "member", "user", "rick@the-citadel.com"); + + // Act + GetObjectResponse getObjectResponse = directoryClient.getObject("user", "rick@the-citadel.com", true); + + // Assert + assertThat(getObjectResponse.getResult()) + .usingRecursiveComparison() + .comparingOnlyFields("objectType_", "objectId_") + .isEqualTo(managerObject); + assertThat(getObjectResponse.getRelationsList()) + .usingRecursiveFieldByFieldElementComparatorOnFields("objectId_", "objectType_", "relation_", "subjectId_", "subjectType_") + .containsExactlyInAnyOrderElementsOf(List.of(managerRelation, adminRelation)); + } + + @Test + void testGetUsers() { + // Arrange & Act + GetObjectsResponse getObjectsResponse = directoryClient.getObjects("user"); + + // Assert + assertEquals(2, getObjectsResponse.getResultsList().size()); + } + + @Test + void testGetUsersWithLimit() { + // Arrange & Act + GetObjectsResponse getObjectsResponse = directoryClient.getObjects("user", 1, ""); + + // Assert + while (!getObjectsResponse.getPage().getNextToken().isEmpty()) { + assertEquals(1, getObjectsResponse.getResultsList().size()); + getObjectsResponse = directoryClient.getObjects("user", 1, getObjectsResponse.getPage().getNextToken()); + } + } + + @Test + void testGetUserManyRequest() { + // Arrange + List objects = List.of( + Directory.buildObjectIdentifier("user", "rick@the-citadel.com"), + Directory.buildObjectIdentifier("user", "morty@the-citadel.com")); + Set expectedUsers = objects.stream().map(ObjectIdentifier::getObjectId).collect(Collectors.toSet()); + + // Act + GetObjectManyResponse getObjectManyResponse = directoryClient.getObjectManyRequest(objects); + + // Assert + Set actualUsers = getObjectManyResponse.getResultsList().stream().map(Object::getId).collect(Collectors.toSet()); + assertEquals(expectedUsers, actualUsers); + } + + @Test + void testGetRelation() { + // Arrange + Relation expectedRelation = Directory.buildRelation("user", "morty@the-citadel.com", "manager", "user", "rick@the-citadel.com"); + + // Act + GetRelationResponse getRelationResponse = directoryClient.getRelation( + "user", + "morty@the-citadel.com", + "manager", + "user", + "rick@the-citadel.com"); + + // Assert + Relation relation = getRelationResponse.getResult(); + assertThat(relation) + .usingRecursiveComparison() + .comparingOnlyFields("objectType_", "objectId_", "relation_", "subjectId_", "subjectType_") + .isEqualTo(expectedRelation); + } + + @Test + void testGetRelations() { + // Arrange + Relation expectedManagerRelation = Directory.buildRelation("user", "morty@the-citadel.com", "manager", "user", "rick@the-citadel.com"); + Relation expectedFriendRelation = Directory.buildRelation("user", "morty@the-citadel.com", "friend", "user", "rick@the-citadel.com"); + + directoryClient.setRelation( + "user", + "morty@the-citadel.com", + "friend", + "user", + "rick@the-citadel.com"); + + GetRelationsRequest getRelationsRequest = GetRelationsRequest.newBuilder().setObjectType("user").build(); + + // Act + GetRelationsResponse getRelationsResponse = directoryClient.getRelations(getRelationsRequest); + + // Assert + assertEquals(2, getRelationsResponse.getResultsList().size()); + assertThat(getRelationsResponse.getResultsList()) + .usingRecursiveFieldByFieldElementComparatorOnFields("objectId_", "objectType_", "relation_", "subjectId_", "subjectType_") + .containsExactlyInAnyOrderElementsOf(List.of(expectedManagerRelation, expectedFriendRelation)); + } + + @Test + void testCheckRelationManager() { + // Arrange & Act + CheckRelationResponse checkRelationResponse = directoryClient.checkRelation( + "user", + "morty@the-citadel.com", + "manager", + "user", + "rick@the-citadel.com"); + + // Assert + assertTrue(checkRelationResponse.getCheck()); + } + + @Test + void testCheckRelationFriend() { + // Arrange & Act + CheckRelationResponse checkRelationResponse = directoryClient.checkRelation( + "user", + "morty@the-citadel.com", + "friend", + "user", + "rick@the-citadel.com"); + + // Assert + assertFalse(checkRelationResponse.getCheck()); + } + + @Test + void testCheckManager() { + // Arrange & Act + CheckResponse checkResponse = directoryClient.check( + "user", + "morty@the-citadel.com", + "manager", + "user", + "rick@the-citadel.com"); + + // Assert + assertTrue(checkResponse.getCheck()); + } + + @Test + void testGetGraph() { + // Arrange + GetGraphRequest getGraphRequest = GetGraphRequest.newBuilder() + .setAnchorType("user") + .setAnchorId("morty@the-citadel.com") + .setObjectType("user") + .setObjectId("morty@the-citadel.com") + .build(); + + List objectDependencyList = Arrays.asList( + ObjectDependency.newBuilder() + .setObjectType("user") + .setObjectId("morty@the-citadel.com") + .setRelation("manager") + .setSubjectType("user") + .setSubjectId("rick@the-citadel.com") + .build() + ); + + // Act + GetGraphResponse getGraphResponse = directoryClient.getGraph(getGraphRequest); + + // Assert + assertThat(getGraphResponse.getResultsList()) + .usingRecursiveFieldByFieldElementComparatorOnFields("objectId_", "objectType_", "relation_", "subjectId_", "subjectType_") + .containsExactlyInAnyOrderElementsOf(objectDependencyList); + } + + @Test + void setObjectTest() { + // Arrange + Object object = Directory.buildObject("test_type", "test_id"); + + // Act + SetObjectResponse setObjectResponse = directoryClient.setObject("test_type", "test_id"); + + // Assert + assertThat(setObjectResponse.getResult()) + .usingRecursiveComparison() + .comparingOnlyFields("type_", "id_") + .isEqualTo(object); + } + + @Test + void deleteObjectTest() { + // Arrange + directoryClient.setObject("test_type", "test_id"); + assertEquals(1, directoryClient.getObjects("test_type").getResultsList().size()); + + // Act + directoryClient.deleteObject("test_type", "test_id"); + + // Assert + assertEquals(0, directoryClient.getObjects("test_type").getResultsList().size()); + } + + @Test + void setRelationTest() { + // Arrange + Relation relation = Directory.buildRelation("user", "morty@the-citadel.com", "friend", "user", "rick@the-citadel.com"); + + // Act + SetRelationResponse setRelationResponse = directoryClient.setRelation( + "user", + "morty@the-citadel.com", + "friend", + "user", + "rick@the-citadel.com"); + + // Assert + assertThat(setRelationResponse.getResult()) + .usingRecursiveComparison() + .comparingOnlyFields("objectType_", "objectId_", "relation_", "subjectType_", "subjectId_") + .isEqualTo(relation); + } + + @Test + void deleteRelationTest() { + // Arrange & Act + DeleteRelationResponse deleteRelationResponse = directoryClient.deleteRelation( + "user", + "morty@the-citadel.com", + "manager", + "user", + "rick@the-citadel.com"); + + // Assert + StatusRuntimeException exception = assertThrows(StatusRuntimeException.class, () -> { + directoryClient.getRelation( + "user", + "morty@the-citadel.com", + "manager", + "user", + "rick@the-citadel.com"); + }); + + assertEquals("NOT_FOUND: E20051 key not found", exception.getMessage()); + } + + @Test + void testGetManifest() { + // Arrange & Act + GetManifestResponse getManifestResponse = directoryClient.getManifest(); + + // Assert + assertEquals(originalManifest, getManifestResponse.getBody().getData().toStringUtf8()); + } + + @Test + void testSetManifest() throws InterruptedException { + // Arrange & Act + directoryClient.setManifest(modifiedManifest); + GetManifestResponse getManifestResponse = directoryClient.getManifest(); + + // Assert + assertEquals(modifiedManifest, getManifestResponse.getBody().getData().toStringUtf8()); + } + + @Test + void testDeleteManifest() { + // Arrange & Act + directoryClient.deleteManifest(); + GetManifestResponse getManifestResponse = directoryClient.getManifest(); + + // Assert + assertEquals("", getManifestResponse.getBody().getData().toStringUtf8()); + } + + @Test + void importDataTest() throws InterruptedException { + // Arrange + List list = importCitadelDataList(); + List users = list.stream() + .map(ImportElement::getObject) + .filter(object -> object != null && object.getType().equals("user")) + .collect(Collectors.toList()); + + // Act + directoryClient.importData(list.stream()); + + // Assert + GetObjectsResponse getObjectsResponse = directoryClient.getObjects("user"); + assertThat(getObjectsResponse.getResultsList()) + .usingRecursiveFieldByFieldElementComparatorOnFields("objectId_", "objectType_", "relation_", "subjectId_", "subjectType_") + .containsAll(users); + + } + + @Test + void exportDataTest() { + // Arrange & Act + Iterator exportedData = directoryClient.exportData(Option.OPTION_DATA); + + // Assert + int elementCount = 0; + while(exportedData.hasNext()) { + exportedData.next(); + elementCount++; + } + + assertEquals(7, elementCount); + } + + private List importCitadelDataList() { + List importElements = new ArrayList<>(); + Object rick = Directory.buildObject("user", "rick@the-citadel.com"); + Object morty = Directory.buildObject("user", "morty@the-citadel.com"); + Object adminGroup = Directory.buildObject("group", "admin"); + Object editorGroup = Directory.buildObject("group", "editor"); + Relation rickAdminRelation = Directory.buildRelation("group", "admin", "member", "user", "rick@the-citadel.com"); + Relation mortyEditorRelation = Directory.buildRelation("group", "editor", "member", "user", "morty@the-citadel.com"); + Relation managerRelation = Directory.buildRelation("user", "morty@the-citadel.com", "manager", "user", "rick@the-citadel.com"); + + importElements.add(new ImportElement(rick)); + importElements.add(new ImportElement(morty)); + importElements.add(new ImportElement(adminGroup)); + importElements.add(new ImportElement(editorGroup)); + importElements.add(new ImportElement(rickAdminRelation)); + importElements.add(new ImportElement(mortyEditorRelation)); + importElements.add(new ImportElement(managerRelation)); + + return importElements; + } +} diff --git a/src/test/java/utils/IntegrationTestsExtenion.java b/src/test/java/utils/IntegrationTestsExtenion.java new file mode 100644 index 0000000..99564cd --- /dev/null +++ b/src/test/java/utils/IntegrationTestsExtenion.java @@ -0,0 +1,35 @@ +package utils; + +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +import java.io.IOException; +import java.net.URISyntaxException; + +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL; + +public class IntegrationTestsExtenion implements BeforeAllCallback, ExtensionContext.Store.CloseableResource { + + private static boolean started = false; + private static Topaz topaz; + + @Override + public void beforeAll(ExtensionContext context) throws IOException, InterruptedException, URISyntaxException { + if (!started) { + started = true; +// Your "before all tests" startup logic goes here +// https://stackoverflow.com/questions/43282798/in-junit-5-how-to-run-code-before-all-tests + topaz = new Topaz(); + topaz.run(); + +// The following line registers a callback hook when the root test context is shut down + context.getRoot().getStore(GLOBAL).put("test close hook", this); + } + + } + + @Override + public void close() throws IOException, InterruptedException { + topaz.stop(); + } +} diff --git a/src/test/java/utils/Topaz.java b/src/test/java/utils/Topaz.java new file mode 100644 index 0000000..3d5aefb --- /dev/null +++ b/src/test/java/utils/Topaz.java @@ -0,0 +1,113 @@ +package utils; + +import com.aserto.ChannelBuilder; +import com.aserto.directory.v3.DirectoryClient; +import io.grpc.ManagedChannel; + +import javax.net.ssl.SSLException; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.time.Duration; +import java.util.concurrent.*; + +public class Topaz { + private String HOME_DIR = System.getProperty("user.home"); + private String DB_DIR = HOME_DIR + "/.config/topaz/db"; + private String TOPAZ_CFG_DIR = HOME_DIR + "/.config/topaz/cfg"; + private DirectoryClient directoryClient; + + public Topaz() throws SSLException { + ManagedChannel channel = new ChannelBuilder() + .withHost("localhost") + .withPort(9292) + .withInsecure(true) + .build(); + directoryClient = new DirectoryClient(channel); + + } + + public void run() throws IOException, InterruptedException, URISyntaxException { + stop(); + backupDb(); + backupCfg(); + configure(); + start(); + } + + public void stop() throws IOException, InterruptedException { + Process process = new ProcessBuilder("topaz","stop").start(); + process.waitFor(); + restoreDb(); + restoreCfg(); + } + + private void start() throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder("topaz","start"); + pb.inheritIO(); + Process process = pb.start(); + process.waitFor(); + process.waitFor(); + + final Duration timeout = Duration.ofSeconds(60); + ExecutorService executor = Executors.newSingleThreadExecutor(); + + final Future handler = executor.submit(new Callable() { + @Override + public Integer call() throws Exception { + while (true) { + try { + directoryClient.getObjects("user"); + } catch (Exception e) { + Thread.sleep(2000); + continue; + } + + return directoryClient.getObjects("user").getResultsList().size(); + } + } + }); + + try { + handler.get(timeout.toMillis(), TimeUnit.MILLISECONDS); + } catch (TimeoutException | InterruptedException | ExecutionException e) { + handler.cancel(true); + } + + } + + private void configure() throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder("topaz", "configure", "-r", "ghcr.io/aserto-policies/policy-todo:2.1.0", "-n", "todo", "-d", "-s"); + pb.inheritIO(); + Process process = pb.start(); + process.waitFor(); + } + + private void backupDb() { + File directoryDb = new File(DB_DIR + "/directory.db" ); + if(directoryDb.exists()) { + directoryDb.renameTo(new File(DB_DIR + "/directory.db.bak" )); + } + } + + private void restoreDb() { + File directoryDb = new File(DB_DIR + "/directory.db.bak" ); + if(directoryDb.exists()) { + directoryDb.renameTo(new File(DB_DIR + "/directory.db" )); + } + } + + private void backupCfg() { + File directoryDb = new File(TOPAZ_CFG_DIR + "/config.yaml" ); + if(directoryDb.exists()) { + directoryDb.renameTo(new File(TOPAZ_CFG_DIR + "/config.yaml.bak" )); + } + } + + private void restoreCfg() { + File directoryDb = new File(TOPAZ_CFG_DIR + "/config.yaml.bak" ); + if(directoryDb.exists()) { + directoryDb.renameTo(new File(TOPAZ_CFG_DIR + "/config.yaml" )); + } + } +}