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