From 798c5e21fc3a41e252856824e5eb0f97c2573723 Mon Sep 17 00:00:00 2001 From: "angelo.andreussi" Date: Wed, 21 Feb 2024 11:05:15 +0100 Subject: [PATCH] added env. variable for cors refresh interval and refined cors tests --- assembly/api/docker/Dockerfile | 1 + deployment/docker/compose/docker-compose.yml | 1 + .../qa/integration/steps/DockerSteps.java | 13 +-- .../qa/integration/steps/RestClientSteps.java | 13 ++- .../rest/authentication/RestAuth.feature | 2 +- .../features/rest/cors/RestCORSFilter.feature | 85 ++++++++++++++++--- 6 files changed, 93 insertions(+), 22 deletions(-) diff --git a/assembly/api/docker/Dockerfile b/assembly/api/docker/Dockerfile index da56069c3d8..092f7cb40e6 100644 --- a/assembly/api/docker/Dockerfile +++ b/assembly/api/docker/Dockerfile @@ -47,6 +47,7 @@ ENV JAVA_OPTS "-Dapi.cors.origins.allowed=\${API_CORS_ORIGINS_ALLOWED} \ -Dcrypto.secret.key=\${CRYPTO_SECRET_KEY} \ -Dauthentication.token.expire.after=\${AUTH_TOKEN_TTL} \ -Dauthentication.refresh.token.expire.after=\${REFRESH_AUTH_TOKEN_TTL} \ + -Dapi.cors.refresh.interval=\${CORS_ENDPOINTS_REFRESH_INTERVAL} \ \${JETTY_DEBUG_OPTS} \ \${JETTY_JMX_OPTS}" diff --git a/deployment/docker/compose/docker-compose.yml b/deployment/docker/compose/docker-compose.yml index 2a6cb95c6ab..29d593aa28b 100644 --- a/deployment/docker/compose/docker-compose.yml +++ b/deployment/docker/compose/docker-compose.yml @@ -110,6 +110,7 @@ services: - SWAGGER=${KAPUA_SWAGGER_ENABLE:-true} - AUTH_TOKEN_TTL=${AUTH_TOKEN_TTL:-1800000} - REFRESH_AUTH_TOKEN_TTL=${REFRESH_AUTH_TOKEN_TTL:-18000000} + - CORS_ENDPOINTS_REFRESH_INTERVAL=${CORS_ENDPOINTS_REFRESH_INTERVAL:-60} job-engine: container_name: job-engine image: kapua/kapua-job-engine:${IMAGE_VERSION} diff --git a/qa/integration-steps/src/main/java/org/eclipse/kapua/qa/integration/steps/DockerSteps.java b/qa/integration-steps/src/main/java/org/eclipse/kapua/qa/integration/steps/DockerSteps.java index 10abeeca204..4a1e276ddb8 100644 --- a/qa/integration-steps/src/main/java/org/eclipse/kapua/qa/integration/steps/DockerSteps.java +++ b/qa/integration-steps/src/main/java/org/eclipse/kapua/qa/integration/steps/DockerSteps.java @@ -280,8 +280,8 @@ private void startBaseDockerEnvironmentInternal() throws Exception { } } - @Given("start rest-API container and dependencies with auth token TTL {string}ms and refresh token TTL {string}ms") - public void startApiDockerEnvironment(String tokenTTL, String refreshTokenTTL) throws Exception { + @Given("start rest-API container and dependencies with auth token TTL {string}ms and refresh token TTL {string}ms and cors endpoint refresh interval {int}s") + public void startApiDockerEnvironment(String tokenTTL, String refreshTokenTTL, int corsEndpointRefreshInterval) throws Exception { logger.info("Starting rest-api docker environment..."); stopFullDockerEnvironmentInternal(); try { @@ -303,7 +303,7 @@ public void startApiDockerEnvironment(String tokenTTL, String refreshTokenTTL) t this.wait(WAIT_FOR_JOB_ENGINE); } - startAPIContainer(BasicSteps.API_CONTAINER_NAME, tokenTTL, refreshTokenTTL); + startAPIContainer(BasicSteps.API_CONTAINER_NAME, tokenTTL, refreshTokenTTL, corsEndpointRefreshInterval); synchronized (this) { this.wait(WAIT_FOR_JOB_ENGINE); } @@ -519,9 +519,9 @@ public void startDBContainer(String name) throws DockerException, InterruptedExc } @And("Start API container with name {string}") - public void startAPIContainer(String name, String tokenTTL, String refreshTokenTTL) throws DockerException, InterruptedException { + public void startAPIContainer(String name, String tokenTTL, String refreshTokenTTL, int corsEndpointRefreshInterval) throws DockerException, InterruptedException { logger.info("Starting API container..."); - ContainerConfig dbConfig = getApiContainerConfig(tokenTTL, refreshTokenTTL); + ContainerConfig dbConfig = getApiContainerConfig(tokenTTL, refreshTokenTTL, corsEndpointRefreshInterval); ContainerCreation dbContainerCreation = DockerUtil.getDockerClient().createContainer(dbConfig, name); String containerId = dbContainerCreation.id(); @@ -820,7 +820,7 @@ private ContainerConfig getDbContainerConfig() { .build(); } - private ContainerConfig getApiContainerConfig(String tokenTTL, String refreshTokenTTL) { + private ContainerConfig getApiContainerConfig(String tokenTTL, String refreshTokenTTL, int corsEndpointRefreshInterval) { final Map> portBindings = new HashMap<>(); addHostPort(ALL_IP, portBindings, 8080, 8081); addHostPort(ALL_IP, portBindings, 8443, 8443); @@ -840,6 +840,7 @@ private ContainerConfig getApiContainerConfig(String tokenTTL, String refreshTok //now I set very little TTL access token to help me in the test scenarios "AUTH_TOKEN_TTL=" + tokenTTL, "REFRESH_AUTH_TOKEN_TTL=" + refreshTokenTTL, + "CORS_ENDPOINTS_REFRESH_INTERVAL=" + corsEndpointRefreshInterval, "SWAGGER=true" ) .image("kapua/" + API_IMAGE + ":" + KAPUA_VERSION) diff --git a/qa/integration-steps/src/main/java/org/eclipse/kapua/qa/integration/steps/RestClientSteps.java b/qa/integration-steps/src/main/java/org/eclipse/kapua/qa/integration/steps/RestClientSteps.java index b3688e47b15..a7555231b1c 100644 --- a/qa/integration-steps/src/main/java/org/eclipse/kapua/qa/integration/steps/RestClientSteps.java +++ b/qa/integration-steps/src/main/java/org/eclipse/kapua/qa/integration/steps/RestClientSteps.java @@ -146,6 +146,13 @@ public void restCall(String method, String resource, String json) throws Excepti restCallInternal(method, resource, json, true); } + @When("REST {string} call at {string} with JSON {string} in cross-site mode with origin {string}") + public void restCallCrossSite(String method, String resource, String json, String origin) throws Exception { + restCallInternal(method, resource, json, true, + com.google.common.net.HttpHeaders.ORIGIN, origin, + com.google.common.net.HttpHeaders.SEC_FETCH_SITE, "cross-site"); + } + public void restCallInternal(String method, String resource, String json, boolean authenticateCall, String... additionalHeaders) throws Exception { // Create an instance of HttpClient HttpClient httpClient = HttpClient.newHttpClient(); @@ -210,10 +217,10 @@ else if (method.equals("GET")) { } } - @When("I try to authenticate with a cross-site call with origin {string}") - public void createCustomCrossSiteCall(String origin) throws Exception { + @When("I try to authenticate with a cross-site call with origin {string} using json {string}") + public void createCustomCrossSiteCall(String origin, String authJson) throws Exception { restCallInternal("POST", "/v1/authentication/user", - "{\"password\": \"kapua-password\", \"username\": \"kapua-sys\"}", false, + authJson, false, com.google.common.net.HttpHeaders.ORIGIN, origin, com.google.common.net.HttpHeaders.SEC_FETCH_SITE, "cross-site"); } diff --git a/qa/integration/src/test/resources/features/rest/authentication/RestAuth.feature b/qa/integration/src/test/resources/features/rest/authentication/RestAuth.feature index 3369f262df4..e9bb97e6112 100644 --- a/qa/integration/src/test/resources/features/rest/authentication/RestAuth.feature +++ b/qa/integration/src/test/resources/features/rest/authentication/RestAuth.feature @@ -21,7 +21,7 @@ Feature: REST API tests for User Given Init Jaxb Context And Init Security Context #NB: be aware that sub-sequent tests depends on the value of TTL that you set here - And start rest-API container and dependencies with auth token TTL "3000"ms and refresh token TTL "2000"ms + And start rest-API container and dependencies with auth token TTL "3000"ms and refresh token TTL "2000"ms and cors endpoint refresh interval 60s Scenario: Simple login with username-pw works and I can call another API endpoint without errors First, the authentication via login-pw is tested. diff --git a/qa/integration/src/test/resources/features/rest/cors/RestCORSFilter.feature b/qa/integration/src/test/resources/features/rest/cors/RestCORSFilter.feature index 33561b6ac03..952ba2a51a4 100644 --- a/qa/integration/src/test/resources/features/rest/cors/RestCORSFilter.feature +++ b/qa/integration/src/test/resources/features/rest/cors/RestCORSFilter.feature @@ -16,35 +16,96 @@ #NB: this test depends on tests tagged as @endpoint Feature: REST API tests for User - REST API test of Kapua User API. + REST API tests of Kapua CORS filter logic. @setup - Scenario: Start event broker for all scenarios + Scenario: Initialize Jaxb and security context, then start rest-api container and dependencies Given Init Jaxb Context And Init Security Context - #NB: be aware that sub-sequent tests depends on the value of TTL that you set here -# And start rest-API container and dependencies with auth token TTL "10000"ms and refresh token TTL "20000"ms + #NB: be aware that sub-sequent tests depends on the value of cors endpoint refresh interval that you set here + And start rest-API container and dependencies with auth token TTL "10000000"ms and refresh token TTL "20000"ms and cors endpoint refresh interval 1s - Scenario: The auth from a "same-origin" call + Scenario: The auth from a "same-origin" call should not present, in the response headers fields, the values that represent a "success CORS filter auth. attempt" Given Server with host "127.0.0.1" on port "8081" Given I try to authenticate with a same-site call - Then I expect no "Access-Control-Allow-Origin" header in the response - Then I expect no "Access-Control-Allow-Credentials" header in the response + Then REST response code is 200 + And REST response containing AccessToken + And I expect no "Access-Control-Allow-Origin" header in the response + And I expect no "Access-Control-Allow-Credentials" header in the response Scenario: The success of an authentication attempt from a 'Cross-origin' request depends on the presence of the CORS endpoint used in the 'origin' header in the scope Given Server with host "127.0.0.1" on port "8081" - Given I try to authenticate with a cross-site call with origin "https://api-sbx.everyware.io" + Given I try to authenticate with a cross-site call with origin "https://api-sbx.everyware.io" using json "{\"password\": \"kapua-password\", \"username\": \"kapua-sys\"}" + #auth is "formally" right (response code 200) but... + Then REST response code is 200 + #...CORS headers are not present Then I expect no "Access-Control-Allow-Origin" header in the response Then I expect no "Access-Control-Allow-Credentials" header in the response + #now I insert the CORS endpoint, using directly services and not rest-API because here I want to test ONLY CORS logic and not other rest APIs. Also, this 'endpoint service' has been tested previously on @endpoint tests Given I login as user with name "kapua-sys" and password "kapua-password" When I create a CORS filter with schema "https", domain "api-sbx.everyware.io" and port 443 - Then I wait 3 seconds - Given I try to authenticate with a cross-site call with origin "https://api-sbx.everyware.io" + Then I wait 2 seconds + Given I try to authenticate with a cross-site call with origin "https://api-sbx.everyware.io" using json "{\"password\": \"kapua-password\", \"username\": \"kapua-sys\"}" + Then REST response code is 200 Then I expect "Access-Control-Allow-Origin" header in the response with value "https://api-sbx.everyware.io" Then I expect "Access-Control-Allow-Credentials" header in the response with value "true" When I delete all CORS filters - Then I wait 3 seconds - Given I try to authenticate with a cross-site call with origin "https://api-sbx.everyware.io" + Then I wait 2 seconds + Given I try to authenticate with a cross-site call with origin "https://api-sbx.everyware.io" using json "{\"password\": \"kapua-password\", \"username\": \"kapua-sys\"}" + Then REST response code is 200 + Then I expect no "Access-Control-Allow-Credentials" header in the response Then I expect no "Access-Control-Allow-Origin" header in the response + + + Scenario: Trying to define CORS endpoints in child account and seeing if they mask the ones defined in the father + + Given Server with host "127.0.0.1" on port "8081" + Given I login as user with name "kapua-sys" and password "kapua-password" + When I create a CORS filter with schema "https", domain "api-sbx.everyware.io" and port 443 + Then I wait 2 seconds + Then I create an account with name "acc1", organization name "acc1" and email address "acc1@org.com" + And I configure user service + | type | name | value | + | boolean | infiniteChildEntities | true | + | integer | maxNumberChildEntities | 0 | + When A generic user + | name | displayName | email | phoneNumber | status | userType | + | user1 | Test User A | kapua_a@kapua.com | +386 31 323 444 | ENABLED | INTERNAL | + And I add credentials + | name | password | enabled | + | user1 | ToManySecrets123# | true | + And Add permissions to the last created user + | domain | action | + | endpoint_info | read | + | endpoint_info | write | + | user | read | + Then I logout + When REST POST call at "/v1/authentication/user" with JSON "{\"password\": \"ToManySecrets123#\", \"username\": \"user1\"}" + Then REST response code is 200 + Then REST response containing AccessToken + Given I try to authenticate with a cross-site call with origin "https://api-sbx.everyware.io" using json "{\"password\": \"ToManySecrets123#\", \"username\": \"user1\"}" + Then REST response code is 200 + Then I expect "Access-Control-Allow-Origin" header in the response with value "https://api-sbx.everyware.io" + Then I expect "Access-Control-Allow-Credentials" header in the response with value "true" + #now I mask the parent account CORS endpoints creating a new one in this child account + Given I login as user with name "user1" and password "ToManySecrets123#" + When I create a CORS filter with schema "https", domain "api-sbx.asdsadas.io" and port 443 + Then I wait 2 seconds + Given I try to authenticate with a cross-site call with origin "https://api-sbx.everyware.io" using json "{\"password\": \"ToManySecrets123#\", \"username\": \"user1\"}" + Then REST response code is 200 + And REST response containing AccessToken + #Authentication has no defined scopeID in session so parent CORS is used in CORS filter matching... + And I expect "Access-Control-Allow-Origin" header in the response with value "https://api-sbx.everyware.io" + And I expect "Access-Control-Allow-Credentials" header in the response with value "true" + #...but for this call there is a scopeID in session (after auth) so there is masking of CORS endpoint + When REST "GET" call at "/v1/_/users?offset=0&limit=50" with JSON "" in cross-site mode with origin "https://api-sbx.everyware.io" + Then REST response code is 200 + And I expect no "Access-Control-Allow-Origin" header in the response + And I expect no "Access-Control-Allow-Credentials" header in the response + + + + +