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..f5a5b68 --- /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-shell 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..ea54cef --- /dev/null +++ b/pom.xml @@ -0,0 +1,228 @@ + + + 4.0.0 + + org.tkit.onecx + onecx-quarkus3-parent + 0.37.0 + + + onecx-shell-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.shell.bff.rs.internal + gen.org.tkit.onecx.shell.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 + + + 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-external-v1.yaml + true + + + + theme-svc-external-v1 + generate-resources + + wget + + + + https://raw.githubusercontent.com/onecx/onecx-theme-svc/main/src/main/openapi/onecx-theme-v1.yaml + + target/tmp/openapi + onecx-theme-svc-external-v1.yaml + true + + + + permission-svc-external-v1 + generate-resources + + wget + + + https://raw.githubusercontent.com/onecx/onecx-permission-svc/main/src/main/openapi/onecx-permission-v1.yaml + target/tmp/openapi + onecx-permission-svc-v1.yaml + true + + + + user-profile-svc-external-v1 + generate-resources + + wget + + + https://raw.githubusercontent.com/onecx/onecx-user-profile-svc/main/src/main/openapi/onecx-userprofile-v1-openapi.yaml + target/tmp/openapi + onecx-user-profile-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..e15bbcb --- /dev/null +++ b/src/main/docker/Dockerfile.jvm @@ -0,0 +1,7 @@ +FROM ghcr.io/onecx/docker-quarkus-jvm:0.5.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..e622cd6 --- /dev/null +++ b/src/main/docker/Dockerfile.native @@ -0,0 +1,3 @@ +FROM ghcr.io/onecx/docker-quarkus-native:0.3.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..7f13a29 --- /dev/null +++ b/src/main/helm/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: onecx-shell-bff +version: 0.0.0 +appVersion: 0.0.0 +description: Onecx shell bff +keywords: + - shell +sources: + - https://github.com/onecx/onecx-shell-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..dbee734 --- /dev/null +++ b/src/main/helm/values.yaml @@ -0,0 +1,14 @@ +app: + name: bff + image: + repository: "onecx/onecx-shell-bff" + operator: + # Permission + permission: + enabled: true + spec: + permissions: + example: + 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 \ No newline at end of file diff --git a/src/main/openapi/openapi-bff.yaml b/src/main/openapi/openapi-bff.yaml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..a99e21f --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,90 @@ +# 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_workspace_svc.url=http://onecx-workspace-svc:8080 +%prod.quarkus.rest-client.onecx_theme_svc.url=http://onecx-theme-svc:8080 +%prod.quarkus.rest-client.onecx_permission_svc.url=http://onecx-permission-svc:8080 +%prod.quarkus.rest-client.onecx_user_profile_svc.url=http://onecx-user-profile-svc:8080 + +%prod.quarkus.oidc-client.client-id=${quarkus.application.name} + +# DEV +%dev.quarkus.rest-client.onecx_workspace_svc.url=http://onecx-workspace-svc +%dev.quarkus.rest-client.onecx_theme_svc.url=http://onecx-theme-svc +%dev.quarkus.rest-client.onecx_permission_svc.url=http://onecx-permission-svc +%dev.quarkus.rest-client.onecx_user_profile_svc.url=http://onecx-user-profile-svc + + +%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_permission.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 + +# workspace v1 client +quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.config-key=onecx_workspace_svc +quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.base-package=gen.org.tkit.onecx.workspace.client +quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.return-response=true +quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +# theme v1 client +quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.config-key=onecx_theme_svc +quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.base-package=gen.org.tkit.onecx.theme.client +quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.return-response=true +quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +# permission v1 client +quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.config-key=onecx_permission_svc +quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.base-package=gen.org.tkit.onecx.permission.client +quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.return-response=true +quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +# user profile v1 client +quarkus.openapi-generator.codegen.spec.onecx_user_profile_svc_v1_yaml.config-key=onecx_user_profile_svc +quarkus.openapi-generator.codegen.spec.onecx_user_profile_svc_v1_yaml.base-package=gen.org.tkit.onecx.user.profile.client +quarkus.openapi-generator.codegen.spec.onecx_user_profile_svc_v1_yaml.return-response=true +quarkus.openapi-generator.codegen.spec.onecx_user_profile_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_workspace_svc.url=${quarkus.mockserver.endpoint} +%test.quarkus.rest-client.onecx_theme_svc.url=${quarkus.mockserver.endpoint} +%test.quarkus.rest-client.onecx_permission_svc.url=${quarkus.mockserver.endpoint} +%test.quarkus.rest-client.onecx_user_profile_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_workspace_svc.providers=io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter +%test.quarkus.rest-client.onecx_theme_svc.providers=io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter +%test.quarkus.rest-client.onecx_permission_svc.providers=io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter +%test.quarkus.rest-client.onecx_user_profile_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} +%test.onecx.permissions.product-name=applications + +# PIPE CONFIG + diff --git a/src/test/java/org/tkit/onecx/shell/bff/rs/AbstractTest.java b/src/test/java/org/tkit/onecx/shell/bff/rs/AbstractTest.java new file mode 100644 index 0000000..e7019ca --- /dev/null +++ b/src/test/java/org/tkit/onecx/shell/bff/rs/AbstractTest.java @@ -0,0 +1,37 @@ +package org.tkit.onecx.shell.bff.rs; + +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; +import org.eclipse.microprofile.config.ConfigProvider; + +@QuarkusTestResource(MockServerTestResource.class) +public abstract class AbstractTest { + + 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/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/mandatory_tokens.json b/src/test/resources/mockserver/mandatory_tokens.json new file mode 100644 index 0000000..60161cc --- /dev/null +++ b/src/test/resources/mockserver/mandatory_tokens.json @@ -0,0 +1,36 @@ +[ + + { + "id": "apm_not_available", + "httpRequest": { + "headers": { + "!apm-principal-token": [ ".*" ] + } + }, + "httpResponse": { + "statusCode": 500, + "body": { + "type": "JSON", + "json": {}, + "contentType": "application/json" + } + } + }, + { + "id": "bearer_not_available", + "httpRequest": { + "headers": { + "!Authorization": [ ".*" ] + }, + "!path": "/v1/permissions/*" + }, + "httpResponse": { + "statusCode": 500, + "body": { + "type": "JSON", + "json": {}, + "contentType": "application/json" + } + } + } +] \ No newline at end of file diff --git a/src/test/resources/mockserver/permissions.json b/src/test/resources/mockserver/permissions.json new file mode 100644 index 0000000..7918f68 --- /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-shell-bff" + }, + "httpResponse": { + "body": { + "type": "JSON", + "json": { + "appId": "onecx-shell-bff", + "permissions": { + "example": ["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-shell-bff" + }, + "httpResponse": { + "body": { + "type": "JSON", + "json": { + "appId": "onecx-shell-bff", + "permissions": { + "example": ["read"], + "permissions": ["admin-write","admin-read"] + } + }, + "contentType": "application/json" + } + } + } +] \ No newline at end of file