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