diff --git a/.github/changelog.yaml b/.github/changelog.yaml
new file mode 100644
index 0000000..0a5526e
--- /dev/null
+++ b/.github/changelog.yaml
@@ -0,0 +1,13 @@
+sections:
+ - title: Major changes
+ labels:
+ - "release/super-feature"
+ - title: Complete changelog
+ labels:
+ - "bug"
+ - "enhancement"
+ - "dependencies"
+template: |
+ {{ range $section := .Sections }}{{ if $section.Items }}### {{ $section.GetTitle }}{{ range $item := $section.Items }}
+ * [#{{ $item.GetID }}]({{ $item.GetURL }}) - {{ $item.GetTitle }}{{ end }}{{ end }}
+ {{ end }}
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..543ce22
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,14 @@
+version: 2
+updates:
+ - package-ecosystem: maven
+ directory: "/"
+ schedule:
+ interval: daily
+ labels:
+ - dependencies
+ - package-ecosystem: "docker"
+ directory: "/src/main/docker"
+ schedule:
+ interval: daily
+ labels:
+ - docker-image
\ No newline at end of file
diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml
new file mode 100644
index 0000000..7270213
--- /dev/null
+++ b/.github/workflows/build-branch.yml
@@ -0,0 +1,13 @@
+name: Build Feature Branch
+
+on:
+ push:
+ branches:
+ - '**'
+ - '!main'
+ - '!fix/[0-9]+.[0-9]+.x'
+
+jobs:
+ branch:
+ uses: onecx/ci-quarkus/.github/workflows/build-branch.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml
new file mode 100644
index 0000000..acff155
--- /dev/null
+++ b/.github/workflows/build-pr.yml
@@ -0,0 +1,9 @@
+name: Build Pull Request
+
+on:
+ pull_request:
+
+jobs:
+ pr:
+ uses: onecx/ci-quarkus/.github/workflows/build-pr.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
new file mode 100644
index 0000000..b04a59a
--- /dev/null
+++ b/.github/workflows/build-release.yml
@@ -0,0 +1,9 @@
+name: Build Release
+on:
+ push:
+ tags:
+ - '**'
+jobs:
+ release:
+ uses: onecx/ci-quarkus/.github/workflows/build-release.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..9ccb418
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,15 @@
+name: Build
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - 'main'
+ - 'fix/[0-9]+.[0-9]+.x'
+
+jobs:
+ build:
+ uses: onecx/ci-quarkus/.github/workflows/build.yml@v1
+ secrets: inherit
+ with:
+ helmEventTargetRepository: onecx/onecx-permission
diff --git a/.github/workflows/create-fix-branch.yml b/.github/workflows/create-fix-branch.yml
new file mode 100644
index 0000000..92af624
--- /dev/null
+++ b/.github/workflows/create-fix-branch.yml
@@ -0,0 +1,7 @@
+name: Create Fix Branch
+on:
+ workflow_dispatch:
+jobs:
+ fix:
+ uses: onecx/ci-common/.github/workflows/create-fix-branch.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/create-new-build.yml b/.github/workflows/create-new-build.yml
new file mode 100644
index 0000000..1404492
--- /dev/null
+++ b/.github/workflows/create-new-build.yml
@@ -0,0 +1,9 @@
+name: Create new build
+
+on:
+ workflow_dispatch:
+
+jobs:
+ build:
+ uses: onecx/ci-common/.github/workflows/create-new-build.yml@v1
+ secrets: inherit
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
new file mode 100644
index 0000000..c97eb42
--- /dev/null
+++ b/.github/workflows/create-release.yml
@@ -0,0 +1,7 @@
+name: Create Release Version
+on:
+ workflow_dispatch:
+jobs:
+ release:
+ uses: onecx/ci-common/.github/workflows/create-release.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
new file mode 100644
index 0000000..3d17922
--- /dev/null
+++ b/.github/workflows/documentation.yml
@@ -0,0 +1,10 @@
+name: Update documentation
+on:
+ push:
+ branches: [ main ]
+ paths:
+ - 'docs/**'
+jobs:
+ release:
+ uses: onecx/ci-common/.github/workflows/documentation.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/sonar-pr.yml b/.github/workflows/sonar-pr.yml
new file mode 100644
index 0000000..02c3e1e
--- /dev/null
+++ b/.github/workflows/sonar-pr.yml
@@ -0,0 +1,12 @@
+name: Sonar Pull Request
+
+on:
+ workflow_run:
+ workflows: ["Build Pull Request"]
+ types:
+ - completed
+
+jobs:
+ pr:
+ uses: onecx/ci-quarkus/.github/workflows/quarkus-pr-sonar.yml@v1
+ secrets: inherit
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..da86281
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,43 @@
+#Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+release.properties
+.flattened-pom.xml
+
+# Eclipse
+.project
+.classpath
+.settings/
+bin/
+
+# IntelliJ
+.idea
+*.ipr
+*.iml
+*.iws
+
+# NetBeans
+nb-configuration.xml
+
+# Visual Studio Code
+.vscode
+.factorypath
+
+# OSX
+.DS_Store
+
+# Vim
+*.swp
+*.swo
+
+# patch
+*.orig
+*.rej
+
+# Local environment
+.env
+
+# Plugin directory
+/.quarkus/cli/plugins/
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..9e5e863
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,200 @@
+
+
+ 4.0.0
+
+ org.tkit.onecx
+ onecx-quarkus3-parent
+ 0.33.0
+
+
+ onecx-permission-bff
+ 999-SNAPSHOT
+
+
+
+ io.quarkus
+ quarkus-resteasy-reactive
+
+
+ io.quarkus
+ quarkus-smallrye-openapi
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-jackson
+
+
+ io.quarkus
+ quarkus-smallrye-health
+
+
+ io.quarkiverse.openapi.generator
+ quarkus-openapi-generator
+
+
+ io.quarkus
+ quarkus-rest-client-reactive-jackson
+
+
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-log-cdi
+
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-log-rs
+
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-log-json
+
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-rest
+
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-rest-context
+
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-jpa
+
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-security
+
+
+ org.mapstruct
+ mapstruct
+
+
+ io.quarkus
+ quarkus-hibernate-validator
+
+
+ org.tkit.onecx.quarkus
+ onecx-permissions
+
+
+ io.quarkus
+ quarkus-oidc
+
+
+ io.quarkus
+ quarkus-oidc-client-reactive-filter
+
+
+
+ io.quarkiverse.mockserver
+ quarkus-mockserver
+ provided
+
+
+
+
+ io.quarkiverse.mockserver
+ quarkus-mockserver-test
+
+
+ io.swagger.parser.v3
+ swagger-parser
+
+
+ test
+
+
+ io.swagger.parser.v3
+ swagger-parser
+ test
+
+
+ io.quarkus
+ quarkus-test-keycloak-server
+ test
+
+
+
+
+
+
+ org.openapitools
+ openapi-generator-maven-plugin
+
+
+ internal
+
+ generate
+
+
+ src/main/openapi/openapi-bff.yaml
+ gen.org.tkit.onecx.permission.bff.rs.internal
+ gen.org.tkit.onecx.permission.bff.rs.internal.model
+
+
+
+
+ onecx-permissions=true
+ jaxrs-spec
+ ApiService
+ DTO
+ false
+ false
+ false
+ false
+ false
+ true
+ quarkus
+
+ /
+ false
+ true
+ true
+ true
+ true
+ true
+ java8
+ true
+ true
+ false
+ true
+
+
+
+
+ com.googlecode.maven-download-plugin
+ download-maven-plugin
+
+
+ permission-svc-internal
+ generate-resources
+
+ wget
+
+
+ https://raw.githubusercontent.com/onecx/onecx-permission-svc/main/src/main/openapi/onecx-permission-internal-openapi.yaml
+ target/tmp/openapi
+ onecx-permission-svc.yaml
+ true
+
+
+
+ workspace-svc-external-v1
+ generate-resources
+
+ wget
+
+
+ https://raw.githubusercontent.com/onecx/onecx-workspace-svc/main/src/main/openapi/onecx-workspace-v1-openapi.yaml
+ target/tmp/openapi
+ onecx-workspace-svc-v1.yaml
+ true
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm
new file mode 100644
index 0000000..01c98fe
--- /dev/null
+++ b/src/main/docker/Dockerfile.jvm
@@ -0,0 +1,7 @@
+FROM ghcr.io/onecx/docker-quarkus-jvm:0.4.0
+
+COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
+COPY --chown=185 target/quarkus-app/*.jar /deployments/
+COPY --chown=185 target/quarkus-app/app/ /deployments/app/
+COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
+USER 185
diff --git a/src/main/docker/Dockerfile.native b/src/main/docker/Dockerfile.native
new file mode 100644
index 0000000..c0ba5ed
--- /dev/null
+++ b/src/main/docker/Dockerfile.native
@@ -0,0 +1,3 @@
+FROM ghcr.io/onecx/docker-quarkus-native:0.2.0
+
+COPY --chown=1001:root target/*-runner /work/application
diff --git a/src/main/helm/Chart.yaml b/src/main/helm/Chart.yaml
new file mode 100644
index 0000000..3bc5a53
--- /dev/null
+++ b/src/main/helm/Chart.yaml
@@ -0,0 +1,17 @@
+apiVersion: v2
+name: onecx-permission-bff
+version: 0.0.0
+appVersion: 0.0.0
+description: Onecx permission management bff
+keywords:
+ - permission
+sources:
+ - https://github.com/onecx/onecx-permission-bff
+maintainers:
+ - name: Tkit Developer
+ email: tkit_dev@1000kit.org
+dependencies:
+ - name: helm-quarkus-app
+ alias: app
+ version: ^0
+ repository: oci://ghcr.io/onecx/charts
\ No newline at end of file
diff --git a/src/main/helm/values.yaml b/src/main/helm/values.yaml
new file mode 100644
index 0000000..b5ddfd3
--- /dev/null
+++ b/src/main/helm/values.yaml
@@ -0,0 +1,14 @@
+app:
+ name: bff
+ image:
+ repository: "onecx/onecx-permission-bff"
+ operator:
+ # Permission
+ permission:
+ enabled: true
+ spec:
+ permissions:
+ permission:
+ read: permission on all GET requests and POST search
+ write: permission on PUT, POST, PATCH requests, where objects are saved or updated
+ delete: permission on all DELETE requests
diff --git a/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/ApplicationRestController.java b/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/ApplicationRestController.java
new file mode 100644
index 0000000..a052bbd
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/ApplicationRestController.java
@@ -0,0 +1,44 @@
+package org.tkit.onecx.permission.bff.rs.controllers;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Response;
+
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
+import org.tkit.onecx.permission.bff.rs.mappers.ApplicationMapper;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.ApplicationApiService;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.ApplicationPageResultDTO;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.ApplicationSearchCriteriaDTO;
+import gen.org.tkit.onecx.permission.client.api.ApplicationInternalApi;
+import gen.org.tkit.onecx.permission.client.model.ApplicationPageResult;
+
+@ApplicationScoped
+@Transactional(value = Transactional.TxType.NOT_SUPPORTED)
+@LogService
+public class ApplicationRestController implements ApplicationApiService {
+
+ @RestClient
+ @Inject
+ ApplicationInternalApi applicationClient;
+
+ @Inject
+ ApplicationMapper mapper;
+
+ @Override
+ public Response searchApplications(ApplicationSearchCriteriaDTO applicationSearchCriteriaDTO) {
+ try (Response response = applicationClient.searchApplications(mapper.map(applicationSearchCriteriaDTO))) {
+ ApplicationPageResultDTO responseDTO = mapper.map(response.readEntity(ApplicationPageResult.class));
+ return Response.status(response.getStatus()).entity(responseDTO).build();
+ }
+ }
+
+ @ServerExceptionMapper
+ public Response restException(WebApplicationException ex) {
+ return Response.status(ex.getResponse().getStatus()).build();
+ }
+}
diff --git a/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/AssignmentRestController.java b/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/AssignmentRestController.java
new file mode 100644
index 0000000..0dc78af
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/AssignmentRestController.java
@@ -0,0 +1,79 @@
+package org.tkit.onecx.permission.bff.rs.controllers;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Response;
+
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.resteasy.reactive.RestResponse;
+import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
+import org.tkit.onecx.permission.bff.rs.mappers.AssignmentMapper;
+import org.tkit.onecx.permission.bff.rs.mappers.ExceptionMapper;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.AssignmentApiService;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.*;
+import gen.org.tkit.onecx.permission.client.api.AssignmentInternalApi;
+import gen.org.tkit.onecx.permission.client.model.Assignment;
+import gen.org.tkit.onecx.permission.client.model.AssignmentPageResult;
+
+@ApplicationScoped
+@Transactional(value = Transactional.TxType.NOT_SUPPORTED)
+@LogService
+public class AssignmentRestController implements AssignmentApiService {
+
+ @RestClient
+ @Inject
+ AssignmentInternalApi assignmentClient;
+
+ @Inject
+ AssignmentMapper mapper;
+
+ @Inject
+ ExceptionMapper exceptionMapper;
+
+ @Override
+ public Response createAssignment(CreateAssignmentRequestDTO createAssignmentRequestDTO) {
+ try (Response response = assignmentClient
+ .createAssignment(mapper.map(createAssignmentRequestDTO))) {
+ AssignmentDTO responseDTO = mapper.map(response.readEntity(Assignment.class));
+ return Response.status(response.getStatus()).entity(responseDTO).build();
+ }
+ }
+
+ @Override
+ public Response deleteAssignment(String id) {
+ try (Response response = assignmentClient.deleteAssignment(id)) {
+ return Response.status(response.getStatus()).build();
+ }
+ }
+
+ @Override
+ public Response getAssignment(String id) {
+ try (Response response = assignmentClient.getAssignment(id)) {
+ AssignmentDTO responseDTO = mapper.map(response.readEntity(Assignment.class));
+ return Response.status(response.getStatus()).entity(responseDTO).build();
+ }
+ }
+
+ @Override
+ public Response searchAssignments(AssignmentSearchCriteriaDTO assignmentSearchCriteriaDTO) {
+ try (Response response = assignmentClient.searchAssignments(mapper.map(assignmentSearchCriteriaDTO))) {
+ AssignmentPageResultDTO responseDTO = mapper.map(response.readEntity(AssignmentPageResult.class));
+ return Response.status(response.getStatus()).entity(responseDTO).build();
+ }
+ }
+
+ @ServerExceptionMapper
+ public RestResponse constraint(ConstraintViolationException ex) {
+ return exceptionMapper.constraint(ex);
+ }
+
+ @ServerExceptionMapper
+ public Response restException(WebApplicationException ex) {
+ return Response.status(ex.getResponse().getStatus()).build();
+ }
+}
diff --git a/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/PermissionRestController.java b/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/PermissionRestController.java
new file mode 100644
index 0000000..a779d13
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/PermissionRestController.java
@@ -0,0 +1,44 @@
+package org.tkit.onecx.permission.bff.rs.controllers;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Response;
+
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
+import org.tkit.onecx.permission.bff.rs.mappers.PermissionMapper;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.PermissionApiService;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.PermissionPageResultDTO;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.PermissionSearchCriteriaDTO;
+import gen.org.tkit.onecx.permission.client.api.PermissionInternalApi;
+import gen.org.tkit.onecx.permission.client.model.PermissionPageResult;
+
+@ApplicationScoped
+@Transactional(value = Transactional.TxType.NOT_SUPPORTED)
+@LogService
+public class PermissionRestController implements PermissionApiService {
+
+ @RestClient
+ @Inject
+ PermissionInternalApi permissionClient;
+
+ @Inject
+ PermissionMapper mapper;
+
+ @Override
+ public Response searchPermissions(PermissionSearchCriteriaDTO permissionSearchCriteriaDTO) {
+ try (Response response = permissionClient.searchPermissions(mapper.map(permissionSearchCriteriaDTO))) {
+ PermissionPageResultDTO responseDTO = mapper.map(response.readEntity(PermissionPageResult.class));
+ return Response.status(response.getStatus()).entity(responseDTO).build();
+ }
+ }
+
+ @ServerExceptionMapper
+ public Response restException(WebApplicationException ex) {
+ return Response.status(ex.getResponse().getStatus()).build();
+ }
+}
diff --git a/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/RoleRestController.java b/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/RoleRestController.java
new file mode 100644
index 0000000..aeaa4c1
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/RoleRestController.java
@@ -0,0 +1,87 @@
+package org.tkit.onecx.permission.bff.rs.controllers;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Response;
+
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.resteasy.reactive.RestResponse;
+import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
+import org.tkit.onecx.permission.bff.rs.mappers.ExceptionMapper;
+import org.tkit.onecx.permission.bff.rs.mappers.RoleMapper;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.RoleApiService;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.*;
+import gen.org.tkit.onecx.permission.client.api.RoleInternalApi;
+import gen.org.tkit.onecx.permission.client.model.Role;
+import gen.org.tkit.onecx.permission.client.model.RolePageResult;
+
+@ApplicationScoped
+@Transactional(value = Transactional.TxType.NOT_SUPPORTED)
+@LogService
+public class RoleRestController implements RoleApiService {
+
+ @RestClient
+ @Inject
+ RoleInternalApi roleClient;
+
+ @Inject
+ RoleMapper mapper;
+
+ @Inject
+ ExceptionMapper exceptionMapper;
+
+ @Override
+ public Response createRole(CreateRoleRequestDTO createRoleRequestDTO) {
+ try (Response response = roleClient
+ .createRole(mapper.map(createRoleRequestDTO))) {
+ RoleDTO responseDTO = mapper.map(response.readEntity(Role.class));
+ return Response.status(response.getStatus()).entity(responseDTO).build();
+ }
+ }
+
+ @Override
+ public Response deleteRole(String id) {
+ try (Response response = roleClient.deleteRole(id)) {
+ return Response.status(response.getStatus()).build();
+ }
+ }
+
+ @Override
+ public Response getRoleById(String id) {
+ try (Response response = roleClient.getRoleById(id)) {
+ RoleDTO responseDTO = mapper.map(response.readEntity(Role.class));
+ return Response.status(response.getStatus()).entity(responseDTO).build();
+ }
+ }
+
+ @Override
+ public Response searchRoles(RoleSearchCriteriaDTO roleSearchCriteriaDTO) {
+ try (Response response = roleClient.searchRoles(mapper.map(roleSearchCriteriaDTO))) {
+ RolePageResultDTO responseDTO = mapper.map(response.readEntity(RolePageResult.class));
+ return Response.status(response.getStatus()).entity(responseDTO).build();
+ }
+ }
+
+ @Override
+ public Response updateRole(String id, UpdateRoleRequestDTO updateRoleRequestDTO) {
+ try (Response response = roleClient.updateRole(id,
+ mapper.map(updateRoleRequestDTO))) {
+ return Response.status(response.getStatus()).build();
+ }
+ }
+
+ @ServerExceptionMapper
+ public Response restException(WebApplicationException ex) {
+ return Response.status(ex.getResponse().getStatus()).build();
+ }
+
+ @ServerExceptionMapper
+ public RestResponse constraint(ConstraintViolationException ex) {
+ return exceptionMapper.constraint(ex);
+ }
+}
diff --git a/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/WorkspaceRestController.java b/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/WorkspaceRestController.java
new file mode 100644
index 0000000..f322a63
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/WorkspaceRestController.java
@@ -0,0 +1,46 @@
+package org.tkit.onecx.permission.bff.rs.controllers;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.ws.rs.core.Response;
+
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.tkit.onecx.permission.bff.rs.mappers.WorkspaceMapper;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.WorkspaceApiService;
+import gen.org.tkit.onecx.permission.client.api.ProductExternalApi;
+import gen.org.tkit.onecx.permission.client.api.WorkspaceExternalApi;
+import gen.org.tkit.onecx.permission.client.model.Product;
+
+@ApplicationScoped
+@Transactional(value = Transactional.TxType.NOT_SUPPORTED)
+@LogService
+public class WorkspaceRestController implements WorkspaceApiService {
+
+ @RestClient
+ @Inject
+ WorkspaceExternalApi workspaceClient;
+
+ @RestClient
+ @Inject
+ ProductExternalApi productClient;
+
+ @Inject
+ WorkspaceMapper mapper;
+
+ @Override
+ public Response getAllProductsByWorkspaceName(String workspaceName) {
+ try (Response response = productClient.getProducts(workspaceName)) {
+ return Response.status(response.getStatus()).entity(mapper.map(response.readEntity(Product[].class))).build();
+ }
+ }
+
+ @Override
+ public Response getAllWorkspaceNames() {
+ try (Response response = workspaceClient.getAllWorkspaceNames()) {
+ return Response.status(response.getStatus()).entity(response.readEntity(String[].class)).build();
+ }
+ }
+}
diff --git a/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/ApplicationMapper.java b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/ApplicationMapper.java
new file mode 100644
index 0000000..d002351
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/ApplicationMapper.java
@@ -0,0 +1,19 @@
+package org.tkit.onecx.permission.bff.rs.mappers;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.ApplicationPageResultDTO;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.ApplicationSearchCriteriaDTO;
+import gen.org.tkit.onecx.permission.client.model.ApplicationPageResult;
+import gen.org.tkit.onecx.permission.client.model.ApplicationSearchCriteria;
+
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public interface ApplicationMapper {
+
+ ApplicationSearchCriteria map(ApplicationSearchCriteriaDTO applicationSearchCriteriaDTO);
+
+ @Mapping(target = "removeStreamItem", ignore = true)
+ ApplicationPageResultDTO map(ApplicationPageResult pageResult);
+}
diff --git a/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/AssignmentMapper.java b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/AssignmentMapper.java
new file mode 100644
index 0000000..9d5c6bd
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/AssignmentMapper.java
@@ -0,0 +1,27 @@
+package org.tkit.onecx.permission.bff.rs.mappers;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.AssignmentDTO;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.AssignmentPageResultDTO;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.AssignmentSearchCriteriaDTO;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.CreateAssignmentRequestDTO;
+import gen.org.tkit.onecx.permission.client.model.Assignment;
+import gen.org.tkit.onecx.permission.client.model.AssignmentPageResult;
+import gen.org.tkit.onecx.permission.client.model.AssignmentSearchCriteria;
+import gen.org.tkit.onecx.permission.client.model.CreateAssignmentRequest;
+
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public interface AssignmentMapper {
+ CreateAssignmentRequest map(CreateAssignmentRequestDTO createAssignmentRequestDTO);
+
+ AssignmentDTO map(Assignment assignment);
+
+ @Mapping(target = "appId", source = "appIds")
+ AssignmentSearchCriteria map(AssignmentSearchCriteriaDTO assignmentSearchCriteriaDTO);
+
+ @Mapping(target = "removeStreamItem", ignore = true)
+ AssignmentPageResultDTO map(AssignmentPageResult pageResult);
+}
diff --git a/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/ExceptionMapper.java b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/ExceptionMapper.java
new file mode 100644
index 0000000..4e53583
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/ExceptionMapper.java
@@ -0,0 +1,60 @@
+package org.tkit.onecx.permission.bff.rs.mappers;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.Path;
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.resteasy.reactive.RestResponse;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.ProblemDetailInvalidParamDTO;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.ProblemDetailParamDTO;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.ProblemDetailResponseDTO;
+
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public interface ExceptionMapper {
+
+ default RestResponse constraint(ConstraintViolationException ex) {
+ var dto = exception("CONSTRAINT_VIOLATIONS", ex.getMessage());
+ dto.setInvalidParams(createErrorValidationResponse(ex.getConstraintViolations()));
+ return RestResponse.status(Response.Status.BAD_REQUEST, dto);
+ }
+
+ @Mapping(target = "removeParamsItem", ignore = true)
+ @Mapping(target = "params", ignore = true)
+ @Mapping(target = "invalidParams", ignore = true)
+ @Mapping(target = "removeInvalidParamsItem", ignore = true)
+ ProblemDetailResponseDTO exception(String errorCode, String detail);
+
+ default List map(Map params) {
+ if (params == null) {
+ return List.of();
+ }
+ return params.entrySet().stream().map(e -> {
+ var item = new ProblemDetailParamDTO();
+ item.setKey(e.getKey());
+ if (e.getValue() != null) {
+ item.setValue(e.getValue().toString());
+ }
+ return item;
+ }).toList();
+ }
+
+ List createErrorValidationResponse(
+ Set> constraintViolation);
+
+ @Mapping(target = "name", source = "propertyPath")
+ @Mapping(target = "message", source = "message")
+ ProblemDetailInvalidParamDTO createError(ConstraintViolation> constraintViolation);
+
+ default String mapPath(Path path) {
+ return path.toString();
+ }
+}
diff --git a/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/PermissionMapper.java b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/PermissionMapper.java
new file mode 100644
index 0000000..4f305f7
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/PermissionMapper.java
@@ -0,0 +1,19 @@
+package org.tkit.onecx.permission.bff.rs.mappers;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.PermissionPageResultDTO;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.PermissionSearchCriteriaDTO;
+import gen.org.tkit.onecx.permission.client.model.PermissionPageResult;
+import gen.org.tkit.onecx.permission.client.model.PermissionSearchCriteria;
+
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public interface PermissionMapper {
+ PermissionSearchCriteria map(PermissionSearchCriteriaDTO permissionSearchCriteriaDTO);
+
+ @Mapping(target = "removeStreamItem", ignore = true)
+ PermissionPageResultDTO map(PermissionPageResult pageResult);
+
+}
diff --git a/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/RoleMapper.java b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/RoleMapper.java
new file mode 100644
index 0000000..c096aba
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/RoleMapper.java
@@ -0,0 +1,22 @@
+package org.tkit.onecx.permission.bff.rs.mappers;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.*;
+import gen.org.tkit.onecx.permission.client.model.*;
+
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public interface RoleMapper {
+ CreateRoleRequest map(CreateRoleRequestDTO createRoleRequestDTO);
+
+ RoleDTO map(Role role);
+
+ RoleSearchCriteria map(RoleSearchCriteriaDTO roleSearchCriteriaDTO);
+
+ @Mapping(target = "removeStreamItem", ignore = true)
+ RolePageResultDTO map(RolePageResult pageResult);
+
+ UpdateRoleRequest map(UpdateRoleRequestDTO updateRoleRequestDTO);
+}
diff --git a/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/WorkspaceMapper.java b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/WorkspaceMapper.java
new file mode 100644
index 0000000..1980aa5
--- /dev/null
+++ b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/WorkspaceMapper.java
@@ -0,0 +1,14 @@
+package org.tkit.onecx.permission.bff.rs.mappers;
+
+import org.mapstruct.Mapper;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.ProductDTO;
+import gen.org.tkit.onecx.permission.client.model.Product;
+
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public interface WorkspaceMapper {
+ ProductDTO[] map(Product[] products);
+
+ ProductDTO map(Product product);
+}
diff --git a/src/main/openapi/openapi-bff.yaml b/src/main/openapi/openapi-bff.yaml
new file mode 100644
index 0000000..29da29b
--- /dev/null
+++ b/src/main/openapi/openapi-bff.yaml
@@ -0,0 +1,682 @@
+---
+openapi: 3.0.3
+info:
+ title: onecx-permission-bff
+ version: 1.0.0
+servers:
+ - url: "http://onecx-permission-bff:8080"
+tags:
+ - name: permission
+ - name: assignment
+ - name: role
+ - name: workspace
+paths:
+ /roles:
+ post:
+ x-onecx:
+ permissions:
+ permission:
+ - write
+ tags:
+ - role
+ description: create role
+ operationId: createRole
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateRoleRequest'
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Role'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /roles/{id}:
+ get:
+ x-onecx:
+ permissions:
+ permission:
+ - read
+ tags:
+ - role
+ description: Return role by ID
+ operationId: getRoleById
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Role'
+ 404:
+ description: Role not found
+ put:
+ x-onecx:
+ permissions:
+ permission:
+ - write
+ tags:
+ - role
+ description: Update role by ID
+ operationId: updateRole
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateRoleRequest'
+ responses:
+ 204:
+ description: Theme updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Role'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ 404:
+ description: Role not found
+ delete:
+ x-onecx:
+ permissions:
+ permission:
+ - delete
+ tags:
+ - role
+ description: Delete role by ID
+ operationId: deleteRole
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 204:
+ description: Role deleted
+ /roles/search:
+ post:
+ x-onecx:
+ permissions:
+ permission:
+ - read
+ tags:
+ - role
+ description: Search for roles
+ operationId: searchRoles
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RoleSearchCriteria'
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/RolePageResult'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /permissions/search:
+ post:
+ x-onecx:
+ permissions:
+ permission:
+ - read
+ tags:
+ - permission
+ description: Search for permissions
+ operationId: searchPermissions
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PermissionSearchCriteria'
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/PermissionPageResult'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /assignments/search:
+ post:
+ x-onecx:
+ permissions:
+ permission:
+ - read
+ tags:
+ - assignment
+ description: Search for assignments
+ operationId: searchAssignments
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AssignmentSearchCriteria'
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/AssignmentPageResult'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /assignments:
+ post:
+ x-onecx:
+ permissions:
+ permission:
+ - write
+ tags:
+ - assignment
+ description: Create new assignment
+ operationId: createAssignment
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateAssignmentRequest'
+ responses:
+ 201:
+ description: New assignment created
+ headers:
+ Location:
+ required: true
+ schema:
+ type: string
+ format: url
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Assignment'
+ 404:
+ description: Data not found
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /assignments/{id}:
+ get:
+ x-onecx:
+ permissions:
+ permission:
+ - read
+ tags:
+ - assignment
+ description: Get assignment
+ operationId: getAssignment
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 200:
+ description: Get assignment
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Assignment'
+ 404:
+ description: Assignment not found
+ delete:
+ x-onecx:
+ permissions:
+ permission:
+ - delete
+ tags:
+ - assignment
+ description: Delete assignment
+ operationId: deleteAssignment
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 204:
+ description: Assignment deleted
+ /applications/search:
+ post:
+ x-onecx:
+ permissions:
+ permission:
+ - read
+ tags:
+ - application
+ description: Search for applications
+ operationId: searchApplications
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ApplicationSearchCriteria'
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/ApplicationPageResult'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /workspaces:
+ get:
+ x-onecx:
+ permissions:
+ permission:
+ - read
+ tags:
+ - workspace
+ description: get all workspace names
+ operationId: getAllWorkspaceNames
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/WorkspaceNameList'
+ /workspaces/{workspaceName}/products:
+ get:
+ x-onecx:
+ permissions:
+ permission:
+ - read
+ tags:
+ - workspace
+ description: get all products by workspace name
+ operationId: getAllProductsByWorkspaceName
+ parameters:
+ - name: workspaceName
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Product'
+components:
+ schemas:
+ CreateRoleRequest:
+ type: object
+ properties:
+ name:
+ type: string
+ shortDescription:
+ type: string
+ description:
+ type: string
+ Role:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ creationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ creationUser:
+ type: string
+ modificationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ modificationUser:
+ type: string
+ id:
+ type: string
+ name:
+ type: string
+ description:
+ type: string
+ UpdateRoleRequest:
+ type: object
+ required:
+ - modificationCount
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ name:
+ type: string
+ shortDescription:
+ type: string
+ description:
+ type: string
+ RoleSearchCriteria:
+ type: object
+ properties:
+ name:
+ type: string
+ description:
+ type: string
+ pageNumber:
+ format: int32
+ description: The number of page.
+ default: 0
+ type: integer
+ pageSize:
+ format: int32
+ description: The size of page
+ default: 100
+ type: integer
+ PermissionSearchCriteria:
+ type: object
+ properties:
+ appId:
+ type: string
+ pageNumber:
+ format: int32
+ description: The number of page.
+ default: 0
+ type: integer
+ pageSize:
+ format: int32
+ description: The size of page
+ default: 100
+ type: integer
+ RolePageResult:
+ type: object
+ properties:
+ totalElements:
+ format: int64
+ description: The total elements in the resource.
+ type: integer
+ number:
+ format: int32
+ type: integer
+ size:
+ format: int32
+ type: integer
+ totalPages:
+ format: int64
+ type: integer
+ stream:
+ type: array
+ items:
+ $ref: '#/components/schemas/Role'
+ PermissionPageResult:
+ type: object
+ properties:
+ totalElements:
+ format: int64
+ description: The total elements in the resource.
+ type: integer
+ number:
+ format: int32
+ type: integer
+ size:
+ format: int32
+ type: integer
+ totalPages:
+ format: int64
+ type: integer
+ stream:
+ type: array
+ items:
+ $ref: '#/components/schemas/Permission'
+ Permission:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ creationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ creationUser:
+ type: string
+ modificationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ modificationUser:
+ type: string
+ id:
+ type: string
+ appId:
+ type: string
+ resource:
+ type: string
+ action:
+ type: string
+ description:
+ type: string
+ AssignmentSearchCriteria:
+ type: object
+ properties:
+ appIds:
+ type: array
+ items:
+ type: string
+ pageNumber:
+ format: int32
+ description: The number of page.
+ default: 0
+ type: integer
+ pageSize:
+ format: int32
+ description: The size of page
+ default: 100
+ type: integer
+ AssignmentPageResult:
+ type: object
+ properties:
+ totalElements:
+ format: int64
+ description: The total elements in the resource.
+ type: integer
+ number:
+ format: int32
+ type: integer
+ size:
+ format: int32
+ type: integer
+ totalPages:
+ format: int64
+ type: integer
+ stream:
+ type: array
+ items:
+ $ref: '#/components/schemas/Assignment'
+ CreateAssignmentRequest:
+ type: object
+ required:
+ - roleId
+ - permissionId
+ properties:
+ roleId:
+ type: string
+ permissionId:
+ type: string
+ Assignment:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ creationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ creationUser:
+ type: string
+ modificationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ modificationUser:
+ type: string
+ roleId:
+ type: string
+ permissionId:
+ type: string
+ appId:
+ type: string
+ ApplicationSearchCriteria:
+ type: object
+ properties:
+ appId:
+ type: string
+ name:
+ type: string
+ pageNumber:
+ format: int32
+ description: The number of page.
+ default: 0
+ type: integer
+ pageSize:
+ format: int32
+ description: The size of page
+ default: 100
+ type: integer
+ ApplicationPageResult:
+ type: object
+ properties:
+ totalElements:
+ format: int64
+ description: The total elements in the resource.
+ type: integer
+ number:
+ format: int32
+ type: integer
+ size:
+ format: int32
+ type: integer
+ totalPages:
+ format: int64
+ type: integer
+ stream:
+ type: array
+ items:
+ $ref: '#/components/schemas/Application'
+ Application:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ creationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ creationUser:
+ type: string
+ modificationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ modificationUser:
+ type: string
+ id:
+ type: string
+ appId:
+ type: string
+ name:
+ type: string
+ description:
+ type: string
+ WorkspaceNameList:
+ type: array
+ items:
+ type: string
+ Product:
+ type: object
+ properties:
+ id:
+ type: string
+ productName:
+ type: string
+ baseUrl:
+ type: string
+ OffsetDateTime:
+ format: date-time
+ type: string
+ example: 2022-03-10T12:15:50-04:00
+ ProblemDetailResponse:
+ type: object
+ properties:
+ errorCode:
+ type: string
+ detail:
+ type: string
+ params:
+ type: array
+ items:
+ $ref: '#/components/schemas/ProblemDetailParam'
+ invalidParams:
+ type: array
+ items:
+ $ref: '#/components/schemas/ProblemDetailInvalidParam'
+ ProblemDetailParam:
+ type: object
+ properties:
+ key:
+ type: string
+ value:
+ type: string
+ ProblemDetailInvalidParam:
+ type: object
+ properties:
+ name:
+ type: string
+ message:
+ type: string
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..85d99fb
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,66 @@
+# AUTHENTICATED
+quarkus.http.auth.permission.health.paths=/q/*
+quarkus.http.auth.permission.health.policy=permit
+quarkus.http.auth.permission.default.paths=/*
+quarkus.http.auth.permission.default.policy=authenticated
+
+onecx.permissions.application-id=${quarkus.application.name}
+
+# propagate the apm-principal-token from requests we receive
+org.eclipse.microprofile.rest.client.propagateHeaders=apm-principal-token
+
+# PROD
+%prod.quarkus.rest-client.onecx_permission_svc.url=http://onecx-permission-svc:8080
+%prod.quarkus.rest-client.onecx_workspace_svc.url=http://onecx-workspace-svc:8080
+%prod.quarkus.oidc-client.client-id=${quarkus.application.name}
+
+# DEV
+%dev.quarkus.rest-client.onecx_workspace_svc.url=${quarkus.mockserver.endpoint}
+%dev.quarkus.rest-client.onecx_permission_svc.url=${quarkus.mockserver.endpoint}
+
+%dev.quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url}
+%dev.quarkus.oidc-client.client-id=${quarkus.oidc.client-id}
+%dev.quarkus.oidc-client.credentials.secret=${quarkus.oidc.credentials.secret}
+%dev.quarkus.rest-client.onecx_user_permission_svc.url=${quarkus.mockserver.endpoint}
+%dev.quarkus.mockserver.devservices.config-file=src/test/resources/mockserver.properties
+%dev.quarkus.mockserver.devservices.config-dir=src/test/resources/mockserver
+
+# BUILD
+quarkus.openapi-generator.codegen.input-base-dir=target/tmp/openapi
+# permission client
+quarkus.openapi-generator.codegen.spec.onecx_permission_svc_yaml.config-key=onecx_permission_svc
+quarkus.openapi-generator.codegen.spec.onecx_permission_svc_yaml.base-package=gen.org.tkit.onecx.permission.client
+quarkus.openapi-generator.codegen.spec.onecx_permission_svc_yaml.return-response=true
+quarkus.openapi-generator.codegen.spec.onecx_permission_svc_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+# workspace client
+quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_v1_yaml.config-key=onecx_workspace_svc
+quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_v1_yaml.base-package=gen.org.tkit.onecx.permission.client
+quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_v1_yaml.return-response=true
+quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+
+# INTEGRATION TEST
+quarkus.test.integration-test-profile=test
+
+# TEST
+%test.quarkus.http.test-port=0
+%test.tkit.log.json.enabled=false
+%test.quarkus.mockserver.devservices.config-class-path=true
+%test.quarkus.mockserver.devservices.config-file=/mockserver.properties
+%test.quarkus.mockserver.devservices.config-dir=/mockserver
+%test.quarkus.mockserver.devservices.log=false
+%test.quarkus.mockserver.devservices.reuse=true
+%test.quarkus.rest-client.onecx_permission_svc.url=${quarkus.mockserver.endpoint}
+%test.quarkus.rest-client.onecx_workspace_svc.url=${quarkus.mockserver.endpoint}
+%test.tkit.rs.context.token.header-param=apm-principal-token
+%test.tkit.rs.context.token.enabled=false
+%test.quarkus.rest-client.onecx_permission_svc.providers=io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter
+%test.quarkus.rest-client.onecx_workspace_svc.providers=io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter
+%test.tkit.rs.context.tenant-id.mock.claim-org-id=orgId
+%test.quarkus.rest-client.onecx_permission.url=${quarkus.mockserver.endpoint}
+%test.quarkus.keycloak.devservices.roles.alice=role-admin
+%test.quarkus.keycloak.devservices.roles.bob=role-user
+%test.quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url}
+%test.quarkus.oidc-client.client-id=${quarkus.oidc.client-id}
+%test.quarkus.oidc-client.credentials.secret=${quarkus.oidc.credentials.secret}
+# PIPE CONFIG
+
diff --git a/src/test/java/org/tkit/onecx/permission/rs/AbstractTest.java b/src/test/java/org/tkit/onecx/permission/rs/AbstractTest.java
new file mode 100644
index 0000000..56808f3
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/permission/rs/AbstractTest.java
@@ -0,0 +1,41 @@
+package org.tkit.onecx.permission.rs;
+
+import org.eclipse.microprofile.config.ConfigProvider;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+import io.quarkiverse.mockserver.test.MockServerTestResource;
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.keycloak.client.KeycloakTestClient;
+import io.restassured.RestAssured;
+import io.restassured.config.ObjectMapperConfig;
+import io.restassured.config.RestAssuredConfig;
+
+@QuarkusTestResource(MockServerTestResource.class)
+public abstract class AbstractTest {
+
+ protected static final String MOCKID = "MOCK";
+
+ protected static final String ADMIN = "alice";
+
+ protected static final String USER = "bob";
+
+ KeycloakTestClient keycloakClient = new KeycloakTestClient();
+
+ protected static final String APM_HEADER_PARAM = ConfigProvider.getConfig()
+ .getValue("%test.tkit.rs.context.token.header-param", String.class);
+
+ static {
+ RestAssured.config = RestAssuredConfig.config().objectMapperConfig(
+ ObjectMapperConfig.objectMapperConfig().jackson2ObjectMapperFactory(
+ (cls, charset) -> {
+ ObjectMapper objectMapper = new ObjectMapper();
+ objectMapper.registerModule(new JavaTimeModule());
+ objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ return objectMapper;
+ }));
+ }
+
+}
diff --git a/src/test/java/org/tkit/onecx/permission/rs/ApplicationRestControllerIT.java b/src/test/java/org/tkit/onecx/permission/rs/ApplicationRestControllerIT.java
new file mode 100644
index 0000000..68a1bf0
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/permission/rs/ApplicationRestControllerIT.java
@@ -0,0 +1,7 @@
+package org.tkit.onecx.permission.rs;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class ApplicationRestControllerIT extends ApplicationRestControllerTest {
+}
diff --git a/src/test/java/org/tkit/onecx/permission/rs/ApplicationRestControllerTest.java b/src/test/java/org/tkit/onecx/permission/rs/ApplicationRestControllerTest.java
new file mode 100644
index 0000000..85dc6bd
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/permission/rs/ApplicationRestControllerTest.java
@@ -0,0 +1,104 @@
+package org.tkit.onecx.permission.rs;
+
+import static io.restassured.RestAssured.given;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import java.util.List;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.core.Response;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.model.JsonBody;
+import org.mockserver.model.MediaType;
+import org.tkit.onecx.permission.bff.rs.controllers.ApplicationRestController;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.ApplicationPageResultDTO;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.ApplicationSearchCriteriaDTO;
+import gen.org.tkit.onecx.permission.client.model.Application;
+import gen.org.tkit.onecx.permission.client.model.ApplicationPageResult;
+import gen.org.tkit.onecx.permission.client.model.ApplicationSearchCriteria;
+import io.quarkiverse.mockserver.test.InjectMockServerClient;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@TestHTTPEndpoint(ApplicationRestController.class)
+public class ApplicationRestControllerTest extends AbstractTest {
+
+ @InjectMockServerClient
+ MockServerClient mockServerClient;
+
+ @Test
+ void searchApplicationByCriteriaTest() {
+ ApplicationSearchCriteria criteria = new ApplicationSearchCriteria();
+ criteria.pageNumber(1).pageSize(1).appId("app1");
+
+ ApplicationPageResult pageResult = new ApplicationPageResult();
+ Application application = new Application();
+ application.appId("app1").name("app1");
+ pageResult.stream(List.of(application)).size(1).number(1).totalElements(1L).totalPages(1L);
+
+ // create mock rest endpoint
+ mockServerClient.when(request().withPath("/internal/applications/search").withMethod(HttpMethod.POST)
+ .withBody(JsonBody.json(criteria)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(pageResult)));
+
+ ApplicationSearchCriteriaDTO criteriaDTO = new ApplicationSearchCriteriaDTO();
+ criteriaDTO.pageNumber(1).appId("app1").pageSize(1);
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(criteriaDTO)
+ .post()
+ .then()
+ .statusCode(Response.Status.OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(ApplicationPageResultDTO.class);
+
+ Assertions.assertNotNull(output);
+ Assertions.assertEquals(pageResult.getSize(), output.getSize());
+ Assertions.assertEquals(pageResult.getStream().size(), output.getStream().size());
+ Assertions.assertEquals(pageResult.getStream().get(0).getAppId(), output.getStream().get(0).getAppId());
+ Assertions.assertEquals(pageResult.getStream().get(0).getName(), output.getStream().get(0).getName());
+
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void searchApplicationsByEmptyCriteriaTest() {
+
+ ApplicationSearchCriteria criteria = new ApplicationSearchCriteria();
+
+ // create mock rest endpoint
+ mockServerClient.when(request().withPath("/internal/applications/search").withMethod(HttpMethod.POST)
+ .withBody(JsonBody.json(criteria)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON));
+
+ ApplicationSearchCriteriaDTO criteriaDTO = new ApplicationSearchCriteriaDTO();
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(criteriaDTO)
+ .post()
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode());
+
+ mockServerClient.clear(MOCKID);
+ }
+}
diff --git a/src/test/java/org/tkit/onecx/permission/rs/AssignmentRestControllerIT.java b/src/test/java/org/tkit/onecx/permission/rs/AssignmentRestControllerIT.java
new file mode 100644
index 0000000..2a40f24
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/permission/rs/AssignmentRestControllerIT.java
@@ -0,0 +1,7 @@
+package org.tkit.onecx.permission.rs;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class AssignmentRestControllerIT extends AssignmentRestControllerTest {
+}
diff --git a/src/test/java/org/tkit/onecx/permission/rs/AssignmentRestControllerTest.java b/src/test/java/org/tkit/onecx/permission/rs/AssignmentRestControllerTest.java
new file mode 100644
index 0000000..26db965
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/permission/rs/AssignmentRestControllerTest.java
@@ -0,0 +1,251 @@
+package org.tkit.onecx.permission.rs;
+
+import static io.restassured.RestAssured.given;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import java.util.List;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.core.Response;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.model.JsonBody;
+import org.mockserver.model.MediaType;
+import org.tkit.onecx.permission.bff.rs.controllers.AssignmentRestController;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.*;
+import gen.org.tkit.onecx.permission.client.model.*;
+import io.quarkiverse.mockserver.test.InjectMockServerClient;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@TestHTTPEndpoint(AssignmentRestController.class)
+class AssignmentRestControllerTest extends AbstractTest {
+
+ @InjectMockServerClient
+ MockServerClient mockServerClient;
+
+ @Test
+ void createAssignmentTest() {
+ CreateAssignmentRequest request = new CreateAssignmentRequest();
+ request.permissionId("permission1").roleId("role1");
+
+ Assignment response = new Assignment();
+ response.roleId("role1").permissionId("permission1");
+
+ mockServerClient.when(request().withPath("/internal/assignments").withMethod(HttpMethod.POST)
+ .withBody(JsonBody.json(request)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.CREATED.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(response)));
+
+ CreateAssignmentRequestDTO input = new CreateAssignmentRequestDTO();
+ input.roleId("role1").permissionId("permission1");
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(input)
+ .post()
+ .then()
+ .statusCode(Response.Status.CREATED.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(AssignmentDTO.class);
+
+ // standard USER get FORBIDDEN with only READ permission
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(USER))
+ .header(APM_HEADER_PARAM, USER)
+ .contentType(APPLICATION_JSON)
+ .body(input)
+ .post()
+ .then()
+ .statusCode(Response.Status.FORBIDDEN.getStatusCode());
+
+ Assertions.assertNotNull(output);
+ Assertions.assertEquals(request.getRoleId(), output.getRoleId());
+ Assertions.assertEquals(request.getPermissionId(), output.getPermissionId());
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void createAssignmentEmptyBodyTest() {
+ CreateAssignmentRequest request = new CreateAssignmentRequest();
+
+ mockServerClient.when(request().withPath("/internal/assignments").withMethod(HttpMethod.POST)
+ .withBody(JsonBody.json(request)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON));
+
+ CreateAssignmentRequestDTO input = new CreateAssignmentRequestDTO();
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(input)
+ .post()
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode());
+
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void searchAssignmentByCriteriaTest() {
+ AssignmentSearchCriteria criteria = new AssignmentSearchCriteria();
+ criteria.pageNumber(1).pageSize(1).appId(List.of("app1"));
+
+ AssignmentPageResult pageResult = new AssignmentPageResult();
+ Assignment assignment = new Assignment();
+ assignment.permissionId("permission1").roleId("role1");
+ pageResult.stream(List.of(assignment)).size(1).number(1).totalElements(1L).totalPages(1L);
+
+ // create mock rest endpoint
+ mockServerClient.when(request().withPath("/internal/assignments/search").withMethod(HttpMethod.POST)
+ .withBody(JsonBody.json(criteria)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(pageResult)));
+
+ AssignmentSearchCriteriaDTO criteriaDTO = new AssignmentSearchCriteriaDTO();
+ criteriaDTO.pageNumber(1).appIds(List.of("app1")).pageSize(1);
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(criteriaDTO)
+ .post("/search")
+ .then()
+ .statusCode(Response.Status.OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(AssignmentPageResultDTO.class);
+
+ Assertions.assertNotNull(output);
+ Assertions.assertEquals(pageResult.getSize(), output.getSize());
+ Assertions.assertEquals(pageResult.getStream().size(), output.getStream().size());
+ Assertions.assertEquals(pageResult.getStream().get(0).getRoleId(), output.getStream().get(0).getRoleId());
+ Assertions.assertEquals(pageResult.getStream().get(0).getPermissionId(), output.getStream().get(0).getPermissionId());
+
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void searchAssignmentsByEmptyCriteriaTest() {
+
+ AssignmentSearchCriteria criteria = new AssignmentSearchCriteria();
+
+ // create mock rest endpoint
+ mockServerClient.when(request().withPath("/internal/assignments/search").withMethod(HttpMethod.POST)
+ .withBody(JsonBody.json(criteria)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON));
+
+ AssignmentSearchCriteriaDTO criteriaDTO = new AssignmentSearchCriteriaDTO();
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(criteriaDTO)
+ .post("/search")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode());
+
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void getAssignmentByIdTest() {
+
+ Assignment data = new Assignment();
+ data.roleId("role1").permissionId("permission1");
+ String id = "assignment1";
+
+ // create mock rest endpoint
+ mockServerClient
+ .when(request().withPath("/internal/assignments/" + id).withMethod(HttpMethod.GET))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(data)));
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .pathParam("id", id)
+ .get("/{id}")
+ .then()
+ .statusCode(Response.Status.OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(AssignmentDTO.class);
+
+ Assertions.assertNotNull(output);
+ Assertions.assertEquals(data.getRoleId(), output.getRoleId());
+ Assertions.assertEquals(data.getPermissionId(), output.getPermissionId());
+
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void getAssignmentByIdNotFoundTest() {
+ String notFoundId = "notFound";
+ // create mock rest endpoint
+ mockServerClient
+ .when(request().withPath("/internal/assignments/" + notFoundId).withMethod(HttpMethod.GET))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NOT_FOUND.getStatusCode()));
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .pathParam("id", notFoundId)
+ .get("/{id}")
+ .then()
+ .statusCode(Response.Status.NOT_FOUND.getStatusCode());
+ Assertions.assertNotNull(output);
+
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void deleteAssignmentTest() {
+
+ String id = "test-id-1";
+
+ // create mock rest endpoint
+ mockServerClient.when(request().withPath("/internal/assignments/" + id).withMethod(HttpMethod.DELETE))
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NO_CONTENT.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON));
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .pathParam("id", id)
+ .delete("/{id}")
+ .then()
+ .statusCode(Response.Status.NO_CONTENT.getStatusCode());
+ }
+}
diff --git a/src/test/java/org/tkit/onecx/permission/rs/PermissionRestControllerIT.java b/src/test/java/org/tkit/onecx/permission/rs/PermissionRestControllerIT.java
new file mode 100644
index 0000000..2bf1891
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/permission/rs/PermissionRestControllerIT.java
@@ -0,0 +1,7 @@
+package org.tkit.onecx.permission.rs;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class PermissionRestControllerIT extends PermissionRestControllerTest {
+}
diff --git a/src/test/java/org/tkit/onecx/permission/rs/PermissionRestControllerTest.java b/src/test/java/org/tkit/onecx/permission/rs/PermissionRestControllerTest.java
new file mode 100644
index 0000000..911f173
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/permission/rs/PermissionRestControllerTest.java
@@ -0,0 +1,103 @@
+package org.tkit.onecx.permission.rs;
+
+import static io.restassured.RestAssured.given;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import java.util.List;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.core.Response;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.model.JsonBody;
+import org.mockserver.model.MediaType;
+import org.tkit.onecx.permission.bff.rs.controllers.PermissionRestController;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.PermissionPageResultDTO;
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.PermissionSearchCriteriaDTO;
+import gen.org.tkit.onecx.permission.client.model.Permission;
+import gen.org.tkit.onecx.permission.client.model.PermissionPageResult;
+import gen.org.tkit.onecx.permission.client.model.PermissionSearchCriteria;
+import io.quarkiverse.mockserver.test.InjectMockServerClient;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@TestHTTPEndpoint(PermissionRestController.class)
+class PermissionRestControllerTest extends AbstractTest {
+
+ @InjectMockServerClient
+ MockServerClient mockServerClient;
+
+ @Test
+ void searchPermissionsByCriteriaTest() {
+
+ PermissionSearchCriteria criteria = new PermissionSearchCriteria();
+ criteria.pageNumber(1).appId("app1").pageSize(1);
+
+ PermissionPageResult pageResult = new PermissionPageResult();
+ Permission permission = new Permission();
+ permission.appId("app1").action("delete").id("id1");
+ pageResult.stream(List.of(permission)).size(1).number(1).totalElements(1L).totalPages(1L);
+
+ // create mock rest endpoint
+ mockServerClient.when(request().withPath("/internal/permissions/search").withMethod(HttpMethod.POST)
+ .withBody(JsonBody.json(criteria)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(pageResult)));
+
+ PermissionSearchCriteriaDTO criteriaDTO = new PermissionSearchCriteriaDTO();
+ criteriaDTO.pageNumber(1).appId("app1").pageSize(1);
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(criteriaDTO)
+ .post()
+ .then()
+ .statusCode(Response.Status.OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(PermissionPageResultDTO.class);
+
+ Assertions.assertNotNull(output);
+ Assertions.assertEquals(pageResult.getSize(), output.getSize());
+ Assertions.assertEquals(pageResult.getStream().size(), output.getStream().size());
+ Assertions.assertEquals(pageResult.getStream().get(0).getAppId(), output.getStream().get(0).getAppId());
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void searchPermissionsByEmptyCriteriaTest() {
+
+ PermissionSearchCriteria criteria = new PermissionSearchCriteria();
+
+ // create mock rest endpoint
+ mockServerClient.when(request().withPath("/internal/permissions/search").withMethod(HttpMethod.POST)
+ .withBody(JsonBody.json(criteria)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON));
+
+ PermissionSearchCriteriaDTO criteriaDTO = new PermissionSearchCriteriaDTO();
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(criteriaDTO)
+ .post()
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode());
+
+ mockServerClient.clear(MOCKID);
+ }
+}
diff --git a/src/test/java/org/tkit/onecx/permission/rs/RoleRestControllerIT.java b/src/test/java/org/tkit/onecx/permission/rs/RoleRestControllerIT.java
new file mode 100644
index 0000000..a1ea283
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/permission/rs/RoleRestControllerIT.java
@@ -0,0 +1,7 @@
+package org.tkit.onecx.permission.rs;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class RoleRestControllerIT extends RoleRestControllerTest {
+}
diff --git a/src/test/java/org/tkit/onecx/permission/rs/RoleRestControllerTest.java b/src/test/java/org/tkit/onecx/permission/rs/RoleRestControllerTest.java
new file mode 100644
index 0000000..0d17140
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/permission/rs/RoleRestControllerTest.java
@@ -0,0 +1,342 @@
+package org.tkit.onecx.permission.rs;
+
+import static io.restassured.RestAssured.given;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import java.util.List;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.core.Response;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.model.JsonBody;
+import org.mockserver.model.MediaType;
+import org.tkit.onecx.permission.bff.rs.controllers.RoleRestController;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.*;
+import gen.org.tkit.onecx.permission.client.model.*;
+import io.quarkiverse.mockserver.test.InjectMockServerClient;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@TestHTTPEndpoint(RoleRestController.class)
+class RoleRestControllerTest extends AbstractTest {
+ @InjectMockServerClient
+ MockServerClient mockServerClient;
+
+ @Test
+ void createRoleTest() {
+ CreateRoleRequest request = new CreateRoleRequest();
+ request.name("role1").description("desc1");
+
+ Role response = new Role();
+ response.name("role1").description("desc1").id("test-id");
+
+ mockServerClient.when(request().withPath("/internal/roles").withMethod(HttpMethod.POST)
+ .withBody(JsonBody.json(request)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.CREATED.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(response)));
+
+ CreateRoleRequestDTO input = new CreateRoleRequestDTO();
+ input.name("role1").description("desc1");
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(input)
+ .post()
+ .then()
+ .statusCode(Response.Status.CREATED.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(RoleDTO.class);
+
+ // standard USER get FORBIDDEN with only READ permission
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(USER))
+ .header(APM_HEADER_PARAM, USER)
+ .contentType(APPLICATION_JSON)
+ .body(input)
+ .post()
+ .then()
+ .statusCode(Response.Status.FORBIDDEN.getStatusCode());
+
+ Assertions.assertNotNull(output);
+ Assertions.assertEquals(request.getName(), output.getName());
+ Assertions.assertEquals(request.getDescription(), output.getDescription());
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void createRoleEmptyBodyTest() {
+ CreateRoleRequest request = new CreateRoleRequest();
+
+ mockServerClient.when(request().withPath("/internal/roles").withMethod(HttpMethod.POST)
+ .withBody(JsonBody.json(request)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON));
+
+ CreateRoleRequestDTO input = new CreateRoleRequestDTO();
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(input)
+ .post()
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode());
+
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void searchRoleByCriteriaTest() {
+ RoleSearchCriteria criteria = new RoleSearchCriteria();
+ criteria.pageNumber(1).pageSize(1).name("role1");
+
+ RolePageResult pageResult = new RolePageResult();
+ Role role = new Role();
+ role.name("role1").description("desc1");
+ pageResult.stream(List.of(role)).size(1).number(1).totalElements(1L).totalPages(1L);
+
+ // create mock rest endpoint
+ mockServerClient.when(request().withPath("/internal/roles/search").withMethod(HttpMethod.POST)
+ .withBody(JsonBody.json(criteria)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(pageResult)));
+
+ RoleSearchCriteriaDTO criteriaDTO = new RoleSearchCriteriaDTO();
+ criteriaDTO.pageNumber(1).pageSize(1).name("role1");
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(criteriaDTO)
+ .post("/search")
+ .then()
+ .statusCode(Response.Status.OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(RolePageResultDTO.class);
+
+ Assertions.assertNotNull(output);
+ Assertions.assertEquals(pageResult.getSize(), output.getSize());
+ Assertions.assertEquals(pageResult.getStream().size(), output.getStream().size());
+ Assertions.assertEquals(pageResult.getStream().get(0).getName(), output.getStream().get(0).getName());
+ Assertions.assertEquals(pageResult.getStream().get(0).getDescription(), output.getStream().get(0).getDescription());
+
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void searchRolesByEmptyCriteriaTest() {
+
+ RoleSearchCriteria criteria = new RoleSearchCriteria();
+
+ // create mock rest endpoint
+ mockServerClient.when(request().withPath("/internal/roles/search").withMethod(HttpMethod.POST)
+ .withBody(JsonBody.json(criteria)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON));
+
+ RoleSearchCriteriaDTO criteriaDTO = new RoleSearchCriteriaDTO();
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .body(criteriaDTO)
+ .post("/search")
+ .then()
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode());
+
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void getRoleByIdTest() {
+
+ Role data = new Role();
+ data.name("role1").description("desc1");
+ String id = "test-id";
+
+ // create mock rest endpoint
+ mockServerClient
+ .when(request().withPath("/internal/roles/" + id).withMethod(HttpMethod.GET))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON)
+ .withBody(JsonBody.json(data)));
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .pathParam("id", id)
+ .get("/{id}")
+ .then()
+ .statusCode(Response.Status.OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract().as(RoleDTO.class);
+
+ Assertions.assertNotNull(output);
+ Assertions.assertEquals(data.getName(), output.getName());
+ Assertions.assertEquals(data.getDescription(), output.getDescription());
+
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void getRoleByIdNotFoundTest() {
+ String notFoundId = "notFound";
+ // create mock rest endpoint
+ mockServerClient
+ .when(request().withPath("/internal/roles/" + notFoundId).withMethod(HttpMethod.GET))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NOT_FOUND.getStatusCode()));
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .pathParam("id", notFoundId)
+ .get("/{id}")
+ .then()
+ .statusCode(Response.Status.NOT_FOUND.getStatusCode());
+ Assertions.assertNotNull(output);
+
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void deleteRoleTest() {
+
+ String id = "test-id-1";
+
+ // create mock rest endpoint
+ mockServerClient.when(request().withPath("/internal/roles/" + id).withMethod(HttpMethod.DELETE))
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NO_CONTENT.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON));
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .pathParam("id", id)
+ .delete("/{id}")
+ .then()
+ .statusCode(Response.Status.NO_CONTENT.getStatusCode());
+ }
+
+ @Test
+ void updateRoleTest() {
+
+ String id = "test-id";
+
+ UpdateRoleRequest request = new UpdateRoleRequest();
+ request.name("role1").description("desc1").modificationCount(0);
+
+ mockServerClient.when(request().withPath("/internal/roles/" + id).withMethod(HttpMethod.PUT)
+ .withBody(JsonBody.json(request)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NO_CONTENT.getStatusCode())
+ .withContentType(MediaType.APPLICATION_JSON));
+
+ UpdateRoleRequestDTO requestDTO = new UpdateRoleRequestDTO();
+ requestDTO.name("role1").description("desc1").modificationCount(0);
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .pathParam("id", id)
+ .body(requestDTO)
+ .put("/{id}")
+ .then()
+ .statusCode(Response.Status.NO_CONTENT.getStatusCode());
+
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void updateRoleNotFoundTest() {
+
+ String id = "test-id";
+
+ UpdateRoleRequest request = new UpdateRoleRequest();
+ request.name("role1").description("desc1").modificationCount(0);
+
+ mockServerClient.when(request().withPath("/internal/roles/" + id).withMethod(HttpMethod.PUT)
+ .withBody(JsonBody.json(request)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.NOT_FOUND.getStatusCode()));
+
+ UpdateRoleRequestDTO requestDTO = new UpdateRoleRequestDTO();
+ requestDTO.name("role1").description("desc1").modificationCount(0);
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .pathParam("id", id)
+ .body(requestDTO)
+ .put("/{id}")
+ .then()
+ .statusCode(Response.Status.NOT_FOUND.getStatusCode());
+
+ mockServerClient.clear(MOCKID);
+ }
+
+ @Test
+ void updateRoleValidationErrorTest() {
+
+ String id = "test-id";
+
+ UpdateRoleRequest request = new UpdateRoleRequest();
+ request.description("desc1");
+
+ mockServerClient.when(request().withPath("/internal/roles/" + id).withMethod(HttpMethod.PUT)
+ .withBody(JsonBody.json(request)))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.BAD_REQUEST.getStatusCode()));
+
+ UpdateRoleRequestDTO requestDTO = new UpdateRoleRequestDTO();
+ requestDTO.description("desc1");
+
+ given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .contentType(APPLICATION_JSON)
+ .pathParam("id", id)
+ .body(requestDTO)
+ .put("/{id}")
+ .then()
+ .contentType(APPLICATION_JSON)
+ .statusCode(Response.Status.BAD_REQUEST.getStatusCode());
+
+ mockServerClient.clear(MOCKID);
+ }
+}
diff --git a/src/test/java/org/tkit/onecx/permission/rs/WorkspaceRestControllerIT.java b/src/test/java/org/tkit/onecx/permission/rs/WorkspaceRestControllerIT.java
new file mode 100644
index 0000000..e9b7eb7
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/permission/rs/WorkspaceRestControllerIT.java
@@ -0,0 +1,7 @@
+package org.tkit.onecx.permission.rs;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class WorkspaceRestControllerIT extends WorkspaceRestControllerTest {
+}
diff --git a/src/test/java/org/tkit/onecx/permission/rs/WorkspaceRestControllerTest.java b/src/test/java/org/tkit/onecx/permission/rs/WorkspaceRestControllerTest.java
new file mode 100644
index 0000000..46c0e34
--- /dev/null
+++ b/src/test/java/org/tkit/onecx/permission/rs/WorkspaceRestControllerTest.java
@@ -0,0 +1,96 @@
+package org.tkit.onecx.permission.rs;
+
+import static io.restassured.RestAssured.given;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.core.Response;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.model.JsonBody;
+import org.tkit.onecx.permission.bff.rs.controllers.WorkspaceRestController;
+
+import gen.org.tkit.onecx.permission.bff.rs.internal.model.ProductDTO;
+import gen.org.tkit.onecx.permission.client.model.Product;
+import io.quarkiverse.mockserver.test.InjectMockServerClient;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.common.mapper.TypeRef;
+
+@QuarkusTest
+@TestHTTPEndpoint(WorkspaceRestController.class)
+class WorkspaceRestControllerTest extends AbstractTest {
+
+ @InjectMockServerClient
+ MockServerClient mockServerClient;
+
+ @Test
+ void getAllWorkspaceNamesTest() {
+ ArrayList workspaceNames = new ArrayList<>();
+ workspaceNames.add("workspace1");
+ workspaceNames.add("workspace2");
+ // create mock rest endpoint
+ mockServerClient
+ .when(request().withPath("/v1/workspaces").withMethod(HttpMethod.GET))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withBody(JsonBody.json(workspaceNames)));
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .get()
+ .then()
+ .statusCode(Response.Status.OK.getStatusCode())
+ .extract().as(new TypeRef() {
+ });
+
+ Assertions.assertNotNull(output);
+ Assertions.assertTrue(Arrays.stream(output).toList().contains("workspace1"));
+ Assertions.assertTrue(Arrays.stream(output).toList().contains("workspace2"));
+
+ }
+
+ @Test
+ void getAllProductsByWorkspaceNameTest() {
+ String workspaceName = "workspace1";
+
+ Product product1 = new Product();
+ product1.productName("product1");
+ Product product2 = new Product();
+ product2.productName("product2");
+
+ ArrayList products = new ArrayList<>();
+ products.add(product1);
+ products.add(product2);
+
+ // create mock rest endpoint
+ mockServerClient
+ .when(request().withPath("/v1/workspaces/" + workspaceName + "/products").withMethod(HttpMethod.GET))
+ .withId(MOCKID)
+ .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode())
+ .withBody(JsonBody.json(products)));
+
+ var output = given()
+ .when()
+ .auth().oauth2(keycloakClient.getAccessToken(ADMIN))
+ .header(APM_HEADER_PARAM, ADMIN)
+ .pathParam("workspaceName", workspaceName)
+ .get("/{workspaceName}/products")
+ .then()
+ .statusCode(Response.Status.OK.getStatusCode())
+ .extract().as(new TypeRef() {
+ });
+
+ Assertions.assertNotNull(output);
+ Assertions.assertEquals(Arrays.stream(output).toList().size(), 2);
+ }
+
+}
diff --git a/src/test/resources/mockserver.properties b/src/test/resources/mockserver.properties
new file mode 100644
index 0000000..2a5ad57
--- /dev/null
+++ b/src/test/resources/mockserver.properties
@@ -0,0 +1,16 @@
+mockserver.initializationJsonPath=/mockserver/*.json
+# watch changes in the file
+mockserver.watchInitializationJson=true
+
+
+# Certificate Generation
+# dynamically generated CA key pair (if they don't already exist in specified directory)
+mockserver.dynamicallyCreateCertificateAuthorityCertificate=true
+# save dynamically generated CA key pair in working directory
+mockserver.directoryToSaveDynamicSSLCertificate=.
+# certificate domain name (default "localhost")
+mockserver.sslCertificateDomainName=localhost
+# comma separated list of ip addresses for Subject Alternative Name domain names (default empty list)
+mockserver.sslSubjectAlternativeNameDomains=www.example.com,www.another.com
+# comma separated list of ip addresses for Subject Alternative Name ips (default empty list)
+mockserver.sslSubjectAlternativeNameIps=127.0.0.1
\ No newline at end of file
diff --git a/src/test/resources/mockserver/internal.json b/src/test/resources/mockserver/internal.json
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/resources/mockserver/permissions.json b/src/test/resources/mockserver/permissions.json
new file mode 100644
index 0000000..2d79d51
--- /dev/null
+++ b/src/test/resources/mockserver/permissions.json
@@ -0,0 +1,46 @@
+[
+ {
+ "id": "2",
+ "httpRequest": {
+ "headers": {
+ "apm-principal-token": [ "alice" ]
+ },
+ "path": "/v1/permissions/user/applications/onecx-permission-bff"
+ },
+ "httpResponse": {
+ "body": {
+ "type": "JSON",
+ "json": {
+ "appId": "onecx-permission-bff",
+ "permissions": {
+ "permission": ["read", "write", "delete"],
+ "permissions": ["admin-write","admin-read"]
+ }
+ },
+ "contentType": "application/json"
+ }
+ }
+ },
+ {
+ "id": "3",
+ "httpRequest": {
+ "headers": {
+ "apm-principal-token": [ "bob" ]
+ },
+ "path": "/v1/permissions/user/applications/onecx-permission-bff"
+ },
+ "httpResponse": {
+ "body": {
+ "type": "JSON",
+ "json": {
+ "appId": "onecx-permission-bff",
+ "permissions": {
+ "permission": ["read"],
+ "permissions": ["admin-write","admin-read"]
+ }
+ },
+ "contentType": "application/json"
+ }
+ }
+ }
+]
\ No newline at end of file