From d12d0a5346125e76b4dace2c8e7d71eb451a3e6d Mon Sep 17 00:00:00 2001 From: jsteenke <146953549+jsteenke@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:26:36 +0200 Subject: [PATCH] feat: add security and fix tests (#41) * feat: add security and fix tests * fix: formatting --- .../onecx-search-config-svc-extensions.adoc | 34 +++++++++------- pom.xml | 10 +++++ .../search-config-openapi-internal.yaml | 23 +++++++++++ src/main/resources/application.properties | 10 ++++- ...rchConfigControllerInternalTenantTest.java | 15 +++++++ .../SearchConfigControllerInternalTest.java | 40 +++++++++++++++++++ .../search/config/test/SecurityTest.java | 20 ++++++++++ .../search/config/test/SecurityTestIT.java | 7 ++++ 8 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 src/test/java/org/tkit/onecx/search/config/test/SecurityTest.java create mode 100644 src/test/java/org/tkit/onecx/search/config/test/SecurityTestIT.java diff --git a/docs/modules/onecx-search-config-svc/pages/onecx-search-config-svc-extensions.adoc b/docs/modules/onecx-search-config-svc/pages/onecx-search-config-svc-extensions.adoc index 49c36cc..3142e07 100644 --- a/docs/modules/onecx-search-config-svc/pages/onecx-search-config-svc-extensions.adoc +++ b/docs/modules/onecx-search-config-svc/pages/onecx-search-config-svc-extensions.adoc @@ -60,73 +60,73 @@ h| Version | https://quarkus.io/guides/liquibase[Link] | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-liquibase.adoc[Link] -| 3.13.2 +| 3.13.3 | quarkus-smallrye-health | https://quarkus.io/guides/smallrye-health[Link] | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-smallrye-health.adoc[Link] -| 3.13.2 +| 3.13.3 | quarkus-micrometer-registry-prometheus | https://quarkus.io/guides/telemetry-micrometer[Link] | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-micrometer-registry-prometheus.adoc[Link] -| 3.13.2 +| 3.13.3 | quarkus-hibernate-orm | https://quarkus.io/guides/hibernate-orm[Link] | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-hibernate-orm.adoc[Link] -| 3.13.2 +| 3.13.3 | quarkus-resteasy-reactive | https://quarkus.io/guides/resteasy-reactive[Link] | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-resteasy-reactive.adoc[Link] -| 3.13.2 +| 3.13.3 | quarkus-resteasy-reactive-jackson | https://quarkus.io/guides/rest-json[Link] | -| 3.13.2 +| 3.13.3 | quarkus-hibernate-validator | https://quarkus.io/guides/validation[Link] | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-hibernate-validator.adoc[Link] -| 3.13.2 +| 3.13.3 | quarkus-jdbc-postgresql | https://quarkus.io/guides/datasource[Link] | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-jdbc-postgresql.adoc[Link] -| 3.13.2 +| 3.13.3 | quarkus-smallrye-openapi | https://quarkus.io/guides/openapi-swaggerui[Link] | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-smallrye-openapi.adoc[Link] -| 3.13.2 +| 3.13.3 | quarkus-smallrye-jwt | https://quarkus.io/guides/security-jwt-build[Link] | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-smallrye-jwt.adoc[Link] -| 3.13.2 +| 3.13.3 | quarkus-oidc | https://quarkus.io/guides/security-oidc-bearer-token-authentication-tutorial[Link] | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-oidc.adoc[Link] -| 3.13.2 +| 3.13.3 | quarkus-opentelemetry | https://quarkus.io/guides/opentelemetry[Link] | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-opentelemetry.adoc[Link] -| 3.13.2 +| 3.13.3 | tkit-quarkus-security @@ -144,14 +144,20 @@ h| Version | https://quarkus.io/guides/cdi-reference[Link] | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-arc.adoc[Link] -| 3.13.2 +| 3.13.3 | quarkus-container-image-docker | https://quarkus.io/guides/container-image[Link] | https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-container-image-docker.adoc[Link] -| 3.13.2 +| 3.13.3 +| onecx-security + +| +| +| 0.26.0 + |=== \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5f7703b..38680f7 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,10 @@ org.tkit.onecx.quarkus onecx-tenant + + org.tkit.onecx.quarkus + onecx-security + org.tkit.quarkus.lib tkit-quarkus-jpa-tenant @@ -139,6 +143,11 @@ quarkus-test-keycloak-server test + + org.tkit.quarkus.lib + tkit-quarkus-security-test + test + @@ -155,6 +164,7 @@ false true quarkus + onecx-scopes=true / false diff --git a/src/main/openapi/search-config-openapi-internal.yaml b/src/main/openapi/search-config-openapi-internal.yaml index def4121..9abfaeb 100644 --- a/src/main/openapi/search-config-openapi-internal.yaml +++ b/src/main/openapi/search-config-openapi-internal.yaml @@ -10,6 +10,8 @@ tags: paths: /internal/searchConfig: post: + security: + - oauth2: [ ocx-sc:all, ocx-sc:write ] tags: - SearchConfigInternal summary: Creates the search config. @@ -36,6 +38,8 @@ paths: $ref: '#/components/schemas/ProblemDetailResponse' /internal/searchConfig/search: post: + security: + - oauth2: [ ocx-sc:all, ocx-sc:read ] tags: - SearchConfigInternal summary: Finds search configs by criteria. @@ -62,6 +66,8 @@ paths: $ref: '#/components/schemas/ProblemDetailResponse' /internal/searchConfig/load: post: + security: + - oauth2: [ ocx-sc:all, ocx-sc:read ] tags: - SearchConfigInternal summary: Finds search configs by product, app and page. @@ -90,6 +96,8 @@ paths: $ref: '#/components/schemas/ProblemDetailResponse' /internal/searchConfig/{id}: get: + security: + - oauth2: [ ocx-sc:all, ocx-sc:read ] tags: - SearchConfigInternal summary: Finds search configs by it's id. @@ -111,6 +119,8 @@ paths: "404": description: Not found put: + security: + - oauth2: [ ocx-sc:all, ocx-sc:write ] tags: - SearchConfigInternal summary: Updates the search config. @@ -144,6 +154,8 @@ paths: "404": description: Search-config not found delete: + security: + - oauth2: [ ocx-sc:all, ocx-sc:delete ] tags: - SearchConfigInternal summary: Deletes the search config. @@ -165,6 +177,17 @@ paths: schema: $ref: '#/components/schemas/ProblemDetailResponse' components: + securitySchemes: + oauth2: + type: oauth2 + flows: + clientCredentials: + tokenUrl: https://oauth.simple.api/token + scopes: + ocx-sc:all: Grants access to all operations + ocx-sc:read: Grants read access + ocx-sc:write: Grants write access + ocx-sc:delete: Grants access to delete operations schemas: OffsetDateTime: format: date-time diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ffbe5e0..1e46003 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,6 +3,11 @@ quarkus.datasource.db-kind=postgresql quarkus.datasource.jdbc.max-size=8 quarkus.datasource.jdbc.min-size=2 +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 + quarkus.hibernate-orm.database.generation=validate quarkus.hibernate-orm.multitenant=DISCRIMINATOR quarkus.hibernate-orm.jdbc.timezone=UTC @@ -30,8 +35,11 @@ onecx.permission.token.claim.path=realm_access/roles #%dev.tkit.rs.context.tenant-id.mock.default-tenant=test #%dev.tkit.rs.context.tenant-id.mock.data.org1=tenant100 - +# TEST-IT quarkus.test.integration-test-profile=test +quarkus.test.enable-callbacks-for-integration-tests=true + +# TEST %test.onecx.permission.token.verified=true %test.onecx.permission.token.claim.path=groups %test.tkit.rs.context.tenant-id.enabled=true diff --git a/src/test/java/org/tkit/onecx/search/config/rs/internal/controllers/SearchConfigControllerInternalTenantTest.java b/src/test/java/org/tkit/onecx/search/config/rs/internal/controllers/SearchConfigControllerInternalTenantTest.java index d6f1f84..2abb5e9 100644 --- a/src/test/java/org/tkit/onecx/search/config/rs/internal/controllers/SearchConfigControllerInternalTenantTest.java +++ b/src/test/java/org/tkit/onecx/search/config/rs/internal/controllers/SearchConfigControllerInternalTenantTest.java @@ -4,11 +4,13 @@ import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static org.assertj.core.api.Assertions.assertThat; import static org.jboss.resteasy.reactive.RestResponse.Status.*; +import static org.tkit.quarkus.security.test.SecurityTestUtils.getKeycloakClientToken; import java.util.*; import org.junit.jupiter.api.Test; import org.tkit.onecx.search.config.test.AbstractTest; +import org.tkit.quarkus.security.test.GenerateKeycloakClient; import org.tkit.quarkus.test.WithDBData; import gen.org.tkit.onecx.search.config.rs.internal.model.*; @@ -18,6 +20,7 @@ @QuarkusTest @TestHTTPEndpoint(SearchConfigControllerInternal.class) @WithDBData(value = "search-config-data.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true) +@GenerateKeycloakClient(clientName = "testClient", scopes = "ocx-sc:all") class SearchConfigControllerInternalTenantTest extends AbstractTest { private Map setupValues() { @@ -42,6 +45,7 @@ void shouldGetSearchConfigsById() { var dto = given() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .header(APM_HEADER_PARAM, createToken("org1", null)) .get(configId) .then() @@ -54,6 +58,7 @@ void shouldGetSearchConfigsById() { given() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .header(APM_HEADER_PARAM, createToken("org2", null)) .get(configId) .then() @@ -81,6 +86,7 @@ void shouldCreateSearchConfig() { var searchConfigDTO = given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .body(requestBody) .header(APM_HEADER_PARAM, createToken("org2", null)) .post() @@ -99,6 +105,7 @@ void shouldCreateSearchConfig() { given() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .header(APM_HEADER_PARAM, createToken("org1", null)) .get(searchConfigDTO.getId()) .then() @@ -106,6 +113,7 @@ void shouldCreateSearchConfig() { var dto = given() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .header(APM_HEADER_PARAM, createToken("org2", null)) .get(searchConfigDTO.getId()) .then() @@ -134,6 +142,7 @@ void shouldUpdateModificationCount() { given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .header(APM_HEADER_PARAM, createToken("org2", null)) .body(updateRequestBody) .put(searchConfigId) @@ -143,6 +152,7 @@ void shouldUpdateModificationCount() { var searchConfigDTO = given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .header(APM_HEADER_PARAM, createToken("org1", null)) .body(updateRequestBody) .put(searchConfigId) @@ -166,6 +176,7 @@ void shouldDeleteById() { given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .header(APM_HEADER_PARAM, createToken("org2", null)) .delete(configId) .then() @@ -173,6 +184,7 @@ void shouldDeleteById() { given() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .header(APM_HEADER_PARAM, createToken("org1", null)) .get(configId) .then() @@ -181,6 +193,7 @@ void shouldDeleteById() { given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .header(APM_HEADER_PARAM, createToken("org1", null)) .delete(configId) .then() @@ -188,6 +201,7 @@ void shouldDeleteById() { given() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .header(APM_HEADER_PARAM, createToken("org1", null)) .get(configId) .then() @@ -206,6 +220,7 @@ void shouldFindByCriteria() { var responseDTO = given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .header(APM_HEADER_PARAM, createToken("org2", null)) .body(requestBody) .post("/search") diff --git a/src/test/java/org/tkit/onecx/search/config/rs/internal/controllers/SearchConfigControllerInternalTest.java b/src/test/java/org/tkit/onecx/search/config/rs/internal/controllers/SearchConfigControllerInternalTest.java index 800dd0f..6e85ab6 100644 --- a/src/test/java/org/tkit/onecx/search/config/rs/internal/controllers/SearchConfigControllerInternalTest.java +++ b/src/test/java/org/tkit/onecx/search/config/rs/internal/controllers/SearchConfigControllerInternalTest.java @@ -4,11 +4,13 @@ import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static org.assertj.core.api.Assertions.assertThat; import static org.jboss.resteasy.reactive.RestResponse.Status.*; +import static org.tkit.quarkus.security.test.SecurityTestUtils.getKeycloakClientToken; import java.util.*; import org.junit.jupiter.api.Test; import org.tkit.onecx.search.config.test.AbstractTest; +import org.tkit.quarkus.security.test.GenerateKeycloakClient; import org.tkit.quarkus.test.WithDBData; import gen.org.tkit.onecx.search.config.rs.internal.model.*; @@ -18,6 +20,7 @@ @QuarkusTest @TestHTTPEndpoint(SearchConfigControllerInternal.class) @WithDBData(value = "search-config-data.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true) +@GenerateKeycloakClient(clientName = "testClient", scopes = "ocx-sc:all") class SearchConfigControllerInternalTest extends AbstractTest { private Map setupValues() { @@ -42,6 +45,7 @@ void shouldGetSearchConfigsById() { var dto = given() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .get(configId) .then() .statusCode(OK.getStatusCode()) @@ -58,6 +62,7 @@ void shouldNotGetSearchConfigsById() { given() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .get(configId) .then() .statusCode(NOT_FOUND.getStatusCode()); @@ -84,6 +89,7 @@ void shouldCreateSearchConfig() { var searchConfigDTO = given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .body(requestBody) .post() .then() @@ -121,6 +127,7 @@ void shouldNotCreateSearchConfigDuplicate() { var error = given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .body(requestBody) .post() .then() @@ -137,6 +144,7 @@ void shouldNotCreateSearchConfig() { given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .post() .then() .statusCode(BAD_REQUEST.getStatusCode()); @@ -159,6 +167,7 @@ void shouldUpdateModificationCount() { var searchConfigDTO = given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .body(updateRequestBody) .put(searchConfigId) .then() @@ -181,6 +190,7 @@ void shouldNotUpdateSearchConfigWhenBadRequest() { given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .put(configId) .then() .statusCode(BAD_REQUEST.getStatusCode()); @@ -204,6 +214,7 @@ void shouldNotUpdateSearchConfigNotExists() { given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .body(updateRequestBody) .put(searchConfigId) .then() @@ -227,6 +238,7 @@ void shouldNotUpdateSearchConfigOptLock() { given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .body(updateRequestBody) .put(searchConfigId) .then() @@ -235,6 +247,7 @@ void shouldNotUpdateSearchConfigOptLock() { var error = given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .body(updateRequestBody) .put(searchConfigId) .then() @@ -251,6 +264,7 @@ void shouldDeleteById() { given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .delete(configId) .then() .statusCode(NO_CONTENT.getStatusCode()); @@ -268,6 +282,7 @@ void shouldFindByCriteria() { var responseDTO = given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .body(requestBody) .post("/search") .then() @@ -299,6 +314,7 @@ void shouldFindByCriteriaNoMatch() { var responseDTO = given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .body(requestBody) .post("/search") .then() @@ -317,6 +333,7 @@ void shouldFindAllByCriteriaEmpty() { var responseDTO = given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .body(searchConfigSearchCriteria) .post("/search") .then() @@ -333,8 +350,31 @@ void shouldNotFindByCriteriaNullCriteria() { given() .when() .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) .post() .then() .statusCode(BAD_REQUEST.getStatusCode()); } + + @Test + void loadByProductAppAndProduct_shouldReturnSearchConfigLoadResultArray() { + SearchConfigLoadRequestDTO requestBody = new SearchConfigLoadRequestDTO(); + requestBody.setAppId("support-tool-ui"); + requestBody.setProductName("productName1"); + requestBody.setPage("page1"); + + var responseDTO = given() + .when() + .contentType(APPLICATION_JSON) + .auth().oauth2(getKeycloakClientToken("testClient")) + .body(requestBody) + .post("/load") + .then() + .statusCode(OK.getStatusCode()) + .extract() + .body() + .as(List.class); + + assertThat(responseDTO.get(0)).isNotNull(); + } } diff --git a/src/test/java/org/tkit/onecx/search/config/test/SecurityTest.java b/src/test/java/org/tkit/onecx/search/config/test/SecurityTest.java new file mode 100644 index 0000000..901570b --- /dev/null +++ b/src/test/java/org/tkit/onecx/search/config/test/SecurityTest.java @@ -0,0 +1,20 @@ +package org.tkit.onecx.search.config.test; + +import java.util.List; + +import org.tkit.quarkus.security.test.AbstractSecurityTest; +import org.tkit.quarkus.security.test.SecurityTestConfig; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class SecurityTest extends AbstractSecurityTest { + @Override + public SecurityTestConfig getConfig() { + SecurityTestConfig config = new SecurityTestConfig(); + config.addConfig("read", "/internal/searchConfig/search", 400, List.of("ocx-sc:read"), "post"); + config.addConfig("write", "/internal/searchConfig", 400, List.of("ocx-sc:write"), "post"); + config.addConfig("delete", "/internal/searchConfig/id", 204, List.of("ocx-sc:delete"), "delete"); + return config; + } +} diff --git a/src/test/java/org/tkit/onecx/search/config/test/SecurityTestIT.java b/src/test/java/org/tkit/onecx/search/config/test/SecurityTestIT.java new file mode 100644 index 0000000..2d6b0c8 --- /dev/null +++ b/src/test/java/org/tkit/onecx/search/config/test/SecurityTestIT.java @@ -0,0 +1,7 @@ +package org.tkit.onecx.search.config.test; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class SecurityTestIT extends SecurityTest { +} \ No newline at end of file