diff --git a/Jenkinsfile b/Jenkinsfile index d51c4e95..08efef0d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -25,7 +25,7 @@ pipeline { } } - stage('Unit Tests and CodeCoverage Test'){ + stage('Unit Tests and Code Coverage Test'){ steps{ script{ sh 'docker compose -f docker-compose.test.yml up test' @@ -37,22 +37,22 @@ pipeline { jacoco classPattern: 'target/classes', execPattern: 'target/jacoco.exec', sourcePattern: 'src/main/java', exclusionPattern: 'iudx/file/server/apiserver/FileServerVerticle.class,iudx/file/server/apiserver/FileServerVerticle**,iudx/file/server/authenticator/AuthenticationService.class,iudx/file/server/database/DatabaseService.class,**/JwtDataConverter.class,**/*VertxEBProxy.class,**/Constants.class,**/*VertxProxyHandler.class,**/*Verticle.class,iudx/file/server/deploy/**,iudx/file/server/databroker/DataBrokerServiceImpl.class' } post{ - always { - recordIssues( - enabledForFailure: true, - blameDisabled: true, - forensicsDisabled: true, - qualityGates: [[threshold:0, type: 'TOTAL', unstable: false]], - tool: checkStyle(pattern: 'target/checkstyle-result.xml') - ) - recordIssues( - enabledForFailure: true, - blameDisabled: true, - forensicsDisabled: true, - qualityGates: [[threshold:4, type: 'TOTAL', unstable: false]], - tool: pmdParser(pattern: 'target/pmd.xml') - ) - } + always { + recordIssues( + enabledForFailure: true, + blameDisabled: true, + forensicsDisabled: true, + qualityGates: [[threshold:0, type: 'TOTAL', unstable: false]], + tool: checkStyle(pattern: 'target/checkstyle-result.xml') + ) + recordIssues( + enabledForFailure: true, + blameDisabled: true, + forensicsDisabled: true, + qualityGates: [[threshold:4, type: 'TOTAL', unstable: false]], + tool: pmdParser(pattern: 'target/pmd.xml') + ) + } failure{ script{ sh 'docker compose -f docker-compose.test.yml down --remove-orphans' @@ -67,10 +67,9 @@ pipeline { } } - stage('Start File server for Integration Tests'){ + stage('Start File-Server for Integration Tests'){ steps{ script{ - sh 'scp src/test/resources/iudx-file-server-api.Release-v5.0.0.postman_collection.json jenkins@jenkins-master:/var/lib/jenkins/iudx/fs/Newman/' sh 'docker compose -f docker-compose.test.yml up -d integTest' sh 'sleep 45' } @@ -84,24 +83,34 @@ pipeline { } } - stage('Integration tests & OWASP ZAP pen test'){ + stage('Integration Tests and OWASP ZAP pen test'){ steps{ node('built-in') { script{ - startZap ([host: 'localhost', port: 8090, zapHome: '/var/lib/jenkins/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool/OWASP_ZAP/ZAP_2.11.0']) - sh 'curl http://127.0.0.1:8090/JSON/pscan/action/disableScanners/?ids=10096' - sh 'HTTP_PROXY=\'127.0.0.1:8090\' newman run /var/lib/jenkins/iudx/fs/Newman/iudx-file-server-api.Release-v5.0.0.postman_collection.json -e /home/ubuntu/configs/fs-postman-env.json -n 2 --insecure -r htmlextra --reporter-htmlextra-export /var/lib/jenkins/iudx/fs/Newman/report/report.html --reporter-htmlextra-skipSensitiveData' - runZapAttack() + startZap ([host: '0.0.0.0', port: 8090, zapHome: '/var/lib/jenkins/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool/OWASP_ZAP/ZAP_2.11.0']) + sh 'curl http://0.0.0.0:8090/JSON/pscan/action/disableScanners/?ids=10096' } } + script{ + sh 'scp /home/ubuntu/configs/fs-config-test.json ./example-configs/config-test.json' + sh 'mvn test-compile failsafe:integration-test -DskipUnitTests=true -DintTestProxyHost=jenkins-master-priv -DintTestProxyPort=8090 -DintTestHost=jenkins-slave1 -DintTestPort=8443' + } + node('built-in') { + script{ + runZapAttack() + } + } } post{ always{ + xunit ( + thresholds: [ skipped(failureThreshold: '0'), failed(failureThreshold: '0') ], + tools: [ JUnit(pattern: 'target/failsafe-reports/*.xml') ] + ) node('built-in') { script{ archiveZap failHighAlerts: 1, failMediumAlerts: 1, failLowAlerts: 1 - } - publishHTML([allowMissing: false, alwaysLinkToLastBuild: true, keepAll: true, reportDir: '/var/lib/jenkins/iudx/fs/Newman/report/', reportFiles: 'report.html', reportTitles: '', reportName: 'Integration Test Report']) + } } } failure{ @@ -156,19 +165,16 @@ pipeline { } stage('Integration test on swarm deployment') { steps { - node('built-in') { script{ - sh 'newman run /var/lib/jenkins/iudx/fs/Newman/iudx-file-server-api.Release-v5.0.0.postman_collection.json -e /home/ubuntu/configs/cd/fs-postman-env.json --insecure -r htmlextra --reporter-htmlextra-export /var/lib/jenkins/iudx/fs/Newman/report/cd-report.html --reporter-htmlextra-skipSensitiveData' + sh 'mvn test-compile failsafe:integration-test -DskipUnitTests=true -DintTestDepl=true' } - } } post{ always{ - node('built-in') { - script{ - publishHTML([allowMissing: false, alwaysLinkToLastBuild: true, keepAll: true, reportDir: '/var/lib/jenkins/iudx/fs/Newman/report/', reportFiles: 'cd-report.html', reportTitles: '', reportName: 'Docker-Swarm Integration Test Report']) - } - } + xunit ( + thresholds: [ skipped(failureThreshold: '0'), failed(failureThreshold: '0') ], + tools: [ JUnit(pattern: 'target/failsafe-reports/*.xml') ] + ) } failure{ error "Test failure. Stopping pipeline execution!" diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 8f226a2e..d33d0987 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -9,7 +9,6 @@ services: - LOG_LEVEL=INFO volumes: - /home/ubuntu/configs/fs-config-test.json:/usr/share/app/secrets/all-verticles-configs/config-dev.json - - /home/ubuntu/configs/keystore-file.jks:/usr/share/app/secrets/keystore-file.jks - ./docker/runTests.sh:/usr/share/app/docker/runTests.sh - ./example-configs/:/usr/share/app/example-configs - ./src/:/usr/share/app/src @@ -27,7 +26,6 @@ services: - LOG_LEVEL=INFO volumes: - /home/ubuntu/configs/fs-config-test.json:/usr/share/app/secrets/configs/config.json - - /home/ubuntu/configs/keystore-file.jks:/usr/share/app/secrets/keystore-file.jks command: bash -c "exec java $$FS_JAVA_OPTS -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.Log4j2LogDelegateFactory -jar ./fatjar.jar --host $$(hostname) -c secrets/configs/config.json" ports: - "8443:8443" diff --git a/pom.xml b/pom.xml index f466bcb6..f82d7d9a 100644 --- a/pom.xml +++ b/pom.xml @@ -7,34 +7,34 @@ iudx-file-server - 4.3.2 + 4.5.4 11 4.0.2 - 1.9.2 - 5.3.0 + 1.12.3 + 5.6.0 - 5.8.2 - 5.8.2 - 5.8.2 - 1.17.3 - 1.17.3 - 1.17.3 - 2.17.1 - 3.4.4 - 1.17.3 - 8.3.3 + 5.10.2 + 5.10.2 + 5.10.2 + 1.19.6 + 1.19.6 + 1.19.6 + 2.23.0 + 4.0.0 + 1.19.6 + 8.12.2 - 0.8.8 + 0.8.11 - 10.5.0 - 3.3.0 - 3.4.0 - 3.0.0 - 3.0.0-M7 - 3.1.2 - 3.10.1 - 3.17.0 - 3.0.0-M7 + 10.13.0 + 3.5.2 + 3.6.3 + 3.2.0 + 3.2.5 + 3.3.1 + 3.12.1 + 3.21.2 + 3.2.5 iudx.file.server.deploy.Deployer iudx.file.server.deploy.DeployerDev @@ -84,6 +84,11 @@ io.vertx vertx-web-client + + com.google.guava + guava + 33.0.0-jre + org.apache.maven.plugins maven-checkstyle-plugin @@ -110,6 +115,13 @@ io.vertx vertx-junit5 + + + io.rest-assured + rest-assured + 5.4.0 + test + io.vertx vertx-unit @@ -119,15 +131,19 @@ org.apache.logging.log4j log4j-core + ${log4j2.version} + compile org.apache.logging.log4j log4j-api + ${log4j2.version} org.apache.logging.log4j - log4j-slf4j-impl + log4j-slf4j2-impl + ${log4j2.version} @@ -190,7 +206,7 @@ org.postgresql postgresql - 42.4.3 + 42.7.2 test @@ -211,12 +227,24 @@ co.elastic.clients elasticsearch-java - 8.3.3 + 8.12.2 + + + + com.fasterxml.jackson.core + jackson-core + 2.16.1 com.fasterxml.jackson.core jackson-databind - 2.12.7.1 + 2.16.1 + + + + com.fasterxml.jackson.core + jackson-annotations + 2.16.1 @@ -227,13 +255,13 @@ org.mockito mockito-inline - 3.11.1 + 5.2.0 test org.mockito mockito-junit-jupiter - 3.11.1 + 5.10.0 test diff --git a/src/main/java/iudx/file/server/apiserver/FileServerVerticle.java b/src/main/java/iudx/file/server/apiserver/FileServerVerticle.java index 4facde9b..59db35e2 100644 --- a/src/main/java/iudx/file/server/apiserver/FileServerVerticle.java +++ b/src/main/java/iudx/file/server/apiserver/FileServerVerticle.java @@ -145,7 +145,7 @@ public void start() throws Exception { requestHandler.next(); }); - webClientFactory = new WebClientFactory(vertx, config()); + webClientFactory = new WebClientFactory(vertx); // authService = new AuthServiceImpl(vertx, webClientFactory, config()); catalogueService = new CatalogueServiceImpl(webClientFactory, config()); diff --git a/src/main/java/iudx/file/server/authenticator/AuthenticationVerticle.java b/src/main/java/iudx/file/server/authenticator/AuthenticationVerticle.java index 90b622c1..ac135a61 100644 --- a/src/main/java/iudx/file/server/authenticator/AuthenticationVerticle.java +++ b/src/main/java/iudx/file/server/authenticator/AuthenticationVerticle.java @@ -48,7 +48,7 @@ public static WebClient createWebClient(Vertx vertxObj, JsonObject config, boole @Override public void start() { - webClientFactory = new WebClientFactory(vertx, config()); + webClientFactory = new WebClientFactory(vertx); catalogueService = new CatalogueServiceImpl(webClientFactory, config()); getJwtPublicKey(vertx, config()) .onSuccess( diff --git a/src/main/java/iudx/file/server/common/WebClientFactory.java b/src/main/java/iudx/file/server/common/WebClientFactory.java index 2ddc22d1..7df4ce9c 100644 --- a/src/main/java/iudx/file/server/common/WebClientFactory.java +++ b/src/main/java/iudx/file/server/common/WebClientFactory.java @@ -2,7 +2,6 @@ import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; -import io.vertx.core.net.JksOptions; import io.vertx.ext.web.client.WebClient; import io.vertx.ext.web.client.WebClientOptions; import org.apache.logging.log4j.LogManager; @@ -13,47 +12,37 @@ public class WebClientFactory { private static final Logger LOGGER = LogManager.getLogger(WebClientFactory.class); private final Vertx vertx; - private final JsonObject config; - public WebClientFactory(final Vertx vertx, final JsonObject config) { + public WebClientFactory(final Vertx vertx) { this.vertx = vertx; - this.config = config; } public WebClient getWebClientFor(final ServerType serverType) { if (serverType.equals(ServerType.FILE_SERVER)) { - return getFileServerWebClient(vertx, config); + return getFileServerWebClient(vertx); } else if (serverType.equals(ServerType.RESOURCE_SERVER)) { - return getRsServerWebClient(vertx, config); + return getRsServerWebClient(vertx); } else { LOGGER.error("Unknown type passed." + serverType); return null; } } - private WebClient getFileServerWebClient(final Vertx vertx, final JsonObject config) { + private WebClient getFileServerWebClient(final Vertx vertx) { WebClientOptions options = new WebClientOptions() .setTrustAll(true) .setVerifyHost(false) - .setSsl(true) - .setKeyStoreOptions( - new JksOptions() - .setPath(config.getString("file-keystore")) - .setPassword(config.getString("file-keystorePassword"))); + .setSsl(true); return WebClient.create(vertx, options); } - private WebClient getRsServerWebClient(final Vertx vertx, final JsonObject config) { + private WebClient getRsServerWebClient(final Vertx vertx) { WebClientOptions options = new WebClientOptions() .setTrustAll(true) .setVerifyHost(false) - .setSsl(true) - .setKeyStoreOptions( - new JksOptions() - .setPath(config.getString("rs-keystore")) - .setPassword(config.getString("rs-keystorePassword"))); + .setSsl(true); return WebClient.create(vertx, options); } } diff --git a/src/test/java/iudx/file/server/apiserver/integrationTests/RestAssuredConfiguration.java b/src/test/java/iudx/file/server/apiserver/integrationTests/RestAssuredConfiguration.java new file mode 100644 index 00000000..4065b2e3 --- /dev/null +++ b/src/test/java/iudx/file/server/apiserver/integrationTests/RestAssuredConfiguration.java @@ -0,0 +1,136 @@ +package iudx.file.server.apiserver.integrationTests; + +import io.restassured.RestAssured; +import io.vertx.core.Vertx; +import io.vertx.core.json.JsonObject; +import iudx.file.server.authenticator.TokenSetup; +import iudx.file.server.configuration.Configuration; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.restassured.RestAssured.*; +import static iudx.file.server.authenticator.TokensForITs.*; + +/** + * JUnit5 extension to allow {@link RestAssured} configuration to be injected into all integration + * tests using {@link org.junit.jupiter.api.extension.ExtendWith}. + */ +public class RestAssuredConfiguration implements BeforeAllCallback { + + private static final Logger logger = LoggerFactory.getLogger(RestAssuredConfiguration.class); + private static String rsId; + private static String openRsId; + private static String openRsGroupId; + private static String nonExistingArchiveId; + private static String fileDownloadURL; + + // Getter methods for configuration values + public static String getRsId() { + return rsId; + } + + public static String getOpenRsId() { + return openRsId; + } + + public static String getOpenRsGroupId() { + return openRsGroupId; + } + + public static String getNonExistingArchiveId() { + return nonExistingArchiveId; + } + + public static String getFileDownloadURL() { + return fileDownloadURL; + } + + @Override + public void beforeAll(ExtensionContext context) { + Vertx vertx = Vertx.vertx(); + Configuration fileServerConfig = new Configuration(); + JsonObject config = fileServerConfig.configLoader(0, vertx); + JsonObject testValues = config.getJsonObject("testValues"); + Boolean isSsl = config.getBoolean("ssl"); + + logger.debug(config.encodePrettily()); + rsId = testValues.getString("rsId"); + openRsId = testValues.getString("openRsId"); + openRsGroupId = testValues.getString("openRsGroupId"); + nonExistingArchiveId = testValues.getString("nonExistingArchiveId"); + fileDownloadURL = testValues.getString("fileDownloadURL"); + + JsonObject config2 = fileServerConfig.configLoader(1, vertx); + String authServerHost = config2.getString("authHost"); + + boolean testOnDepl = Boolean.parseBoolean(System.getProperty("intTestDepl")); + if (testOnDepl) { + String testHost = config.getString("host"); + baseURI = "https://" + testHost; + port = 443; + } else { + String testHost = System.getProperty("intTestHost"); + //String testHost = config.getString("host"); + String httpProtocol = isSsl ? "https://" : "http://"; + if (testHost != null) { + baseURI = httpProtocol + testHost; + } else { + baseURI = httpProtocol + "localhost"; + } + + String testPort = System.getProperty("intTestPort"); + + if (testPort != null) { + port = Integer.parseInt(testPort); + } else { + port = 8443; + } + } + + basePath = "/iudx/v1"; + String dxAuthBasePath = "auth/v1"; + String authEndpoint = "https://" + authServerHost + "/" + dxAuthBasePath + "/token"; + String proxyHost = System.getProperty("intTestProxyHost"); + String proxyPort = System.getProperty("intTestProxyPort"); + + JsonObject clientCredentials = config2.getJsonObject("clientCredentials"); + String clientId = clientCredentials.getString("clientID"); + String clientSecret = clientCredentials.getString("clientSecret"); + String delegationId = clientCredentials.getString("delegationId"); + if (proxyHost != null && proxyPort != null) { + proxy(proxyHost, Integer.parseInt(proxyPort)); + } + logger.info("setting up the tokens"); + TokenSetup.setupTokens(authEndpoint, clientId, clientSecret, delegationId); + + // Wait for tokens to be available before proceeding + waitForTokens(); + enableLoggingOfRequestAndResponseIfValidationFails(); + } + + private void waitForTokens() { + int maxAttempts = 5; + int attempt = 0; + + // Keep trying to get tokens until they are available or max attempts are reached + while ((secureResourceToken == null || adminToken == null || openResourceToken == null || delegateToken == null) && attempt < maxAttempts) { + logger.info("Waiting for tokens to be available. Attempt: " + (attempt + 1)); + // Introduce a delay between attempts + try { + Thread.sleep(3000); // Adjust the delay as we aneeded + } catch (InterruptedException e) { + e.printStackTrace(); + } + attempt++; + } + + if (secureResourceToken == null || adminToken == null || openResourceToken == null || delegateToken == null) { + // Log an error or throw an exception if tokens are still not available + throw new RuntimeException("Failed to retrieve tokens after multiple attempts."); + } else { + logger.info("Tokens are now available. Proceeding with RestAssured configuration."); + } + } +} diff --git a/src/test/java/iudx/file/server/apiserver/integrationTests/files/FileServerIT.java b/src/test/java/iudx/file/server/apiserver/integrationTests/files/FileServerIT.java new file mode 100644 index 00000000..7d6a9559 --- /dev/null +++ b/src/test/java/iudx/file/server/apiserver/integrationTests/files/FileServerIT.java @@ -0,0 +1,607 @@ +package iudx.file.server.apiserver.integrationTests.files; + +import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; +import io.vertx.core.json.JsonObject; +import iudx.file.server.apiserver.integrationTests.RestAssuredConfiguration; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +import static io.restassured.RestAssured.baseURI; +import static io.restassured.RestAssured.given; +import static iudx.file.server.authenticator.TokensForITs.*; +import static org.hamcrest.Matchers.*; + +@ExtendWith(RestAssuredConfiguration.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class FileServerIT { + private static final Logger LOGGER = LogManager.getLogger(FileServerIT.class); + private static final String rsId = RestAssuredConfiguration.getRsId(); + private static final String openRsId = RestAssuredConfiguration.getOpenRsId(); + private static final String openRsGroupId = RestAssuredConfiguration.getOpenRsGroupId(); + private static final String nonExistingArchiveId = RestAssuredConfiguration.getNonExistingArchiveId(); + private static final String fileDownloadURL = RestAssuredConfiguration.getFileDownloadURL(); + private static String sampleFileId; + private static String archiveFileId; + private static String externalStorageFileId; + // Create a temporary file and get its reference + File tempFile = createTempFileWithContent(); + String invalidFileId = "_abced"; + boolean isSample = true; + String invalidToken = "abc"; + + private File createTempFileWithContent() { + // Create a temporary file + try { + tempFile = File.createTempFile("test", ".txt"); + } catch (IOException e) { + throw new RuntimeException("Failed to create a temporary file", e); + } + + // Write content to the file + try (FileWriter writer = new FileWriter(tempFile)) { + writer.write("This is the content of the file for testing purposes."); + } catch (IOException e) { + throw new RuntimeException("Failed to write content to the temporary file", e); + } + + return tempFile; + } + + //File Upload + @Test + @Order(1) + @DisplayName("200 (Success) DX file upload - Resource level (sample)") + public void sampleFileUploadSuccessTest() { + JsonObject respJson = new JsonObject( + given().relaxedHTTPSValidation() + .multiPart("file", tempFile, "text/plain") + .formParam("id", rsId) + .formParam("isSample", isSample) + .header("token", delegateToken) + .when() + .post("/upload") + .then() + .statusCode(200) + //.log().body() + .contentType(ContentType.JSON) + .body("results", notNullValue()) + .body("results[0].fileId", notNullValue()) + .extract() + .asString()); + sampleFileId = respJson.getJsonArray("results").getJsonObject(0).getString("fileId"); + + } + + @Test + @Order(2) + @DisplayName("401 (not authorized) DX file upload - Resource level (sample)") + public void unauthorisedSampleFileUploadTest() { + + given().relaxedHTTPSValidation() + .multiPart("file", tempFile, "text/plain") + .formParam("id", rsId) + .formParam("isSample", isSample) + .header("token", invalidToken) + .when() + .post("/upload") + .then() + .statusCode(401) + //.log().body() + .contentType(ContentType.JSON) + .body("type", equalTo("urn:dx:rs:invalidAuthorizationToken")) + .body("title", equalTo("Not authorized")) + .body("detail", equalTo("Token is invalid")); + } + + @Test + @Order(3) + @DisplayName("200 (Success) - Archive Resource Level") + public void archiveFileUploadSuccessTest() { + // Create a temporary file and get its reference + //File tempFile = createTempFileWithContent(); + JsonObject respJson = new JsonObject(given().relaxedHTTPSValidation() + .multiPart("file", tempFile, "text/plain") + .formParam("id", rsId) + .formParam("startTime", "2020-09-05T00:00:00Z") + .formParam("endTime", "2020-09-15T00:00:00Z") + .formParam("geometry", "point") + .formParam("coordinates", "[72.81,21.16]") + .header("token", delegateToken) + .when() + .post("/upload") + .then() + .statusCode(200) + //.log().body() + .contentType(ContentType.JSON) + .body("type", equalTo("urn:dx:rs:success")) + .body("title", equalTo("Success")) + .body("results", notNullValue()) + .body("results[0].fileId", notNullValue()) + .extract() + .asString()); + archiveFileId = respJson.getJsonArray("results").getJsonObject(0).getString("fileId"); + + } + + @Test + @Order(4) + @DisplayName("401 (not authorized) DX file upload - Resource level (Archive)") + public void unauthorisedArchiveFileTest() { + // Create a temporary file and get its reference + //File tempFile = createTempFileWithContent(); + + given().relaxedHTTPSValidation() + .multiPart("file", tempFile, "text/plain") + .formParam("id", rsId) + .formParam("startTime", "2020-09-05T00:00:00Z") + .formParam("endTime", "2020-09-15T00:00:00Z") + .formParam("geometry", "point") + .formParam("coordinates", "[72.81,21.16]") + .header("token", invalidToken) + .when() + .post("/upload") + .then() + .statusCode(401) + //.log().body() + .contentType(ContentType.JSON) + .body("type", equalTo("urn:dx:rs:invalidAuthorizationToken")) + .body("title", equalTo("Not authorized")) + .body("detail", equalTo("Token is invalid")); + } + + @Test + @Order(5) + @DisplayName("400 (No id param in request) DX file upload") + public void invalidParamFileUploadTest() { + // Create a temporary file and get its reference + //File tempFile = createTempFileWithContent(); + + given().relaxedHTTPSValidation() + .multiPart("file", tempFile, "text/plain") + .formParam("id1", rsId) + .formParam("isSample", isSample) + .header("token", delegateToken) + .when() + .post("/upload") + .then() + .statusCode(400) + //.log().body() + .contentType(ContentType.JSON) + .body("type", equalTo("urn:dx:rs:invalidPayloadFormat")) + .body("title", equalTo("Bad Request")) + .body("detail", equalTo("Validation error : null or blank value for required mandatory field")); + } + + @Test + @Order(6) + @DisplayName("400 (Invalid isSample value) DX file upload") + public void invalidIsSampleFileUploadTest() { + // Create a temporary file and get its reference + //File tempFile = createTempFileWithContent(); + given().relaxedHTTPSValidation() + .multiPart("file", tempFile, "text/plain") + .formParam("id", rsId) + .formParam("isSample", "true1") + .header("token", delegateToken) + .when() + .post("/upload") + .then() + .statusCode(400) + //.log().body() + .contentType(ContentType.JSON) + .body("type", equalTo("urn:dx:rs:invalidAttributeValue")) + .body("title", equalTo("Bad Request")) + .body("detail", equalTo("Validation error : Invalid isSample field value [ true1 ]")); + } + + @Test + @Order(7) + @DisplayName("200 (Success) DX file upload - Resource level (External Storage)") + public void externalStorageFileUploadSuccessTest() { + // Create a temporary file and get its reference + //File tempFile = createTempFileWithContent(); + JsonObject respJson = new JsonObject(given().relaxedHTTPSValidation() + .multiPart("file", tempFile, "text/plain") + .formParam("id", rsId) + .formParam("startTime", "2020-09-05T00:00:00Z") + .formParam("endTime", "2020-09-15T00:00:00Z") + .formParam("geometry", "point") + .formParam("coordinates", "[72.81,21.16]") + .formParam("file-download-url", fileDownloadURL) + .header("token", delegateToken) + .when() + .post("/upload") + .then() + .statusCode(200) + //.log().body() + .contentType(ContentType.JSON) + .body("type", equalTo("urn:dx:rs:success")) + .body("title", equalTo("Success")) + .body("results", notNullValue()) + .body("results[0].fileId", notNullValue()) + .extract() + .asString()); + externalStorageFileId = respJson.getJsonArray("results").getJsonObject(0).getString("fileId"); + } + + // File Download + @Test + @Order(8) + @DisplayName("200 (Success) DX file download - RL (Sample file )") + public void sampleFileDownloadSuccessTest() { + given().relaxedHTTPSValidation() + .param("file-id", sampleFileId) + .header("token", openResourceToken) + .when() + .get("/download") + .then() + //.log().body() + .statusCode(200); + } + + @Test + @Order(9) + @DisplayName("400 (invalid file id) DX file download") + public void invalidIdSampleFileDownloadTest() { + given().relaxedHTTPSValidation() + .header("token", openResourceToken) + .param("file-id", invalidFileId) + .when() + .get("/download") + .then() + .statusCode(400) + //.log().body() + .contentType(ContentType.JSON) + .body("type", equalTo("urn:dx:rs:invalidAttributeValue")) + .body("title", equalTo("Bad Request")) + .body("detail", equalTo("Validation error : invalid file id [ " + invalidFileId + " ]")); + } + + @Test + @Order(10) + @DisplayName("200 (Success) DX file download -Resource level (Archive file )") + public void archiveFileDownloadSuccessTest() { + given().relaxedHTTPSValidation() + .param("file-id", archiveFileId) + .header("token", secureResourceToken) + .when() + .get("/download") + .then() + //.log().body() + .statusCode(200); + } + + @Test + @Order(11) + @DisplayName("401 (not authorized) DX file download - RL (Archive file )") + public void unauthorisedArchiveFileDownloadTest() { + given().relaxedHTTPSValidation() + .header("token", invalidToken) + .param("file-id", archiveFileId) + .when() + .get("/download") + .then() + .statusCode(401) + //.log().body() + .contentType(ContentType.JSON) + .body("type", equalTo("urn:dx:rs:invalidAuthorizationToken")) + .body("title", equalTo("Not authorized")) + .body("detail", equalTo("Token is invalid")); + } + + @Test + @Order(12) + @DisplayName("404 (Not Found) DX file download -Resource level (Archive file )") + public void nonExistingArchiveFileDownloadTest() { + given().relaxedHTTPSValidation() + .header("token", secureResourceToken) + .param("file-id", nonExistingArchiveId) + .when() + .get("/download") + .then() + .statusCode(404) + //.log().body() + .contentType(ContentType.JSON) + .body("type", equalTo("urn:dx:rs:resourceNotFound")) + .body("title", equalTo("Not Found")) + .body("detail", equalTo("Document of given id does not exist")); + } + + //For query APIs + @Test + @Order(13) + @DisplayName("200 (Success) Search for Files of an open resource (using openToken)") + void GetResourceLevelSample() { + given().relaxedHTTPSValidation() + .header("token", openResourceToken) + .param("id", openRsId) + .param("time", "2020-09-10T00:00:00Z") + .param("endTime", "2020-09-15T00:00:00Z") + .param("timerel", "between") + .when() + .get(baseURI + "/ngsi-ld/v1/temporal/entities") + .then() + .statusCode(200) + .body("type", is(200)) + .body("title", is("urn:dx:rs:success")); + } + + @Test + @Order(14) + @DisplayName("200 (Success) Search for Files of an open resource group (using openToken)") + void GetResourceGroupSample() { + given().relaxedHTTPSValidation() + .header("token", openResourceToken) + .param("id", openRsGroupId) + .param("time", "2020-09-10T00:00:00Z") + .param("endTime", "2020-09-15T00:00:00Z") + .param("timerel", "between") + .when() + .get(baseURI + "/ngsi-ld/v1/temporal/entities") + .then() + .statusCode(200) + .body("type", is(200)) + .body("title", is("urn:dx:rs:success")); + } + + @Test + @Order(15) + @DisplayName("401 (Not Authorized) Search for Files") + void SearchForFilesUnAuth() { + given().relaxedHTTPSValidation() + .param("id", "8b95ab80-2aaf-4636-a65e-7f2563d0d371") + .param("time", "2020-09-10T00:00:00Z") + .param("endTime", "2020-09-15T00:00:00Z") + .param("timerel", "between") + .when() + .get(baseURI + "/ngsi-ld/v1/temporal/entities") + .then() + .statusCode(401) + .body("type", is("urn:dx:rs:missingAuthorizationToken")) + .body("title", is("Not authorized")) + .body("detail", is("Token needed and not present")); + } + + @Test + @Order(16) + @DisplayName("200 (Success) Search for Files using Spatial [geo(circle)]") + void searchForFilesUsingSpatialGeoCircle() { + String georel = "near;maxDistance=10000"; + String geometry = "point"; + String coordinates = "[72.79,21.16]"; + RequestSpecification requestSpec = given().relaxedHTTPSValidation().urlEncodingEnabled(false); + given().relaxedHTTPSValidation() + .header("token", secureResourceToken) + .spec(requestSpec) + .queryParam("id", rsId) + .queryParam("georel", georel) + .queryParam("geometry", geometry) + .queryParam("coordinates", coordinates) + .when() + .get(baseURI + "/ngsi-ld/v1/entities") + .then() + .statusCode(200) + .body("type", is(200)) + .body("title", is("urn:dx:rs:success")) + .extract() + .response(); + } + + @Test + @Order(17) + @DisplayName("200 (Success) Search for Files using Spatial [geo(Polygon)]") + void SearchForFilesUsingSpatialGeoPolygon() { + given().relaxedHTTPSValidation() + .header("token", secureResourceToken) + .param("id", rsId) + .param("georel", "within") + .param("geometry", "polygon") + .param("coordinates", "[[[72.7815,21.1726],[72.7856,21.1519],[72.807,21.1527],[72.8170,21.1680],[72.800,21.1808],[72.7815,21.1726]]]") + .when() + .get(baseURI + "/ngsi-ld/v1/entities") + .then() + .statusCode(200) + .body("type", is(200)) + .body("title", is("urn:dx:rs:success")); + } + + @Test + @Order(18) + @DisplayName("200 (Success) Complex Search [temporal+geo(Circle) search]") + void ComplexSearchForFilesUsingTemporalPlusGeoPolygon() { + given().relaxedHTTPSValidation() + .header("token", secureResourceToken) + .param("id", rsId) + .param("time", "2020-09-10T00:00:00Z") + .param("endTime", "2020-09-15T00:00:00Z") + .param("timerel", "between") + .param("georel", "near;maxDistance=10000") + .param("geometry", "point") + .param("coordinates", "[72.79,21.16]") + .when() + .get(baseURI + "/ngsi-ld/v1/temporal/entities") + .then() + .statusCode(200) + .body("type", is(200)) + .body("title", is("urn:dx:rs:success")); + } + + @Test + @Order(19) + @DisplayName("401 (not authorized) Complex Search [temporal+geo(Circle) search]") + void ComplexSearchForFilesUsingTemporalPlusGeoPolygonUnAuth() { + given().relaxedHTTPSValidation() + .param("id", rsId) + .param("time", "2020-09-10T00:00:00Z") + .param("endTime", "2020-09-15T00:00:00Z") + .param("timerel", "between") + .param("georel", "near;maxDistance=10000") + .param("geometry", "point") + .param("coordinates", "[72.79,21.16]") + .when() + .get(baseURI + "/ngsi-ld/v1/temporal/entities") + .then() + .statusCode(401) + .body("type", is("urn:dx:rs:missingAuthorizationToken")) + .body("title", is("Not authorized")) + .body("detail", is("Token needed and not present")); + } + + @Test + @Order(20) + @DisplayName("200 (Success) List metadata of an open resource (using openToken)") + void ListMetaDataOfOpenResource() { + given().relaxedHTTPSValidation() + .header("token", openResourceToken) + .param("id", openRsId) + .when() + .get("/list") + .then() + .statusCode(200) + .body("type", is(200)) + .body("title", is("urn:dx:rs:success")); + } + + @Test + @Order(21) + @DisplayName("200 (Success) List metadata of an open resource group (using openToken)") + void ListMetaDataOfOpenResourceGroup() { + given().relaxedHTTPSValidation() + .header("token", openResourceToken) + .param("id", openRsGroupId) + .when() + .get("/list") + .then() + .statusCode(200) + .body("type", is(200)) + .body("title", is("urn:dx:rs:success")); + } + + @Test + @Order(22) + @DisplayName("200 (Success) List metadata of a secure resource (using secureToken)") + void ListMetaDataOfSecureResource() { + given().relaxedHTTPSValidation() + .header("token", secureResourceToken) + .param("id", rsId) + .when() + .get("/list") + .then() + .statusCode(200) + .body("type", is(200)) + .body("title", is("urn:dx:rs:success")); + } + + @Test + @Order(23) + @DisplayName("401 (Not authorized) List metadata ") + void ListMetaDataUnAuth() { + given().relaxedHTTPSValidation() + .param("id", rsId) + .when() + .get("/list") + .then() + .statusCode(401) + .body("type", is("urn:dx:rs:missingAuthorizationToken")) + .body("title", is("Not authorized")) + .body("detail", is("Token needed and not present")); + } + + @Test + @Order(24) + @DisplayName("200 (Success) DX file delete -RL (sample file)") + void DeleteRLSampleFile() { + LOGGER.debug("rl sample..." + sampleFileId); + given().relaxedHTTPSValidation() + .header("token", delegateToken) + .param("file-id", sampleFileId) + .when() + .delete("/delete") + .then() + .statusCode(200) + .body("type", is("urn:dx:rs:success")) + .body("title", is("Successful Operation")); + } + + @Test + @Order(25) + @DisplayName("404 (Not Found) DX file delete - RL (sample file)") + void DeleteRLSampleFileNotFound() { + given().relaxedHTTPSValidation() + .header("token", delegateToken) + .param("file-id", nonExistingArchiveId) + .when() + .delete("/delete") + .then() + .statusCode(404) + .body("type", is("urn:dx:rs:resourceNotFound")) + .body("title", is("Not Found")) + .body("detail", is("Document of given id does not exist")); + } + + @Test + @Order(26) + @DisplayName("200 (Success) DX file delete - RL (Archive file)") + void DeleteRLSampleArchiveFile() { + LOGGER.debug("rl archive file id..." + archiveFileId); + given().relaxedHTTPSValidation() + .header("token", delegateToken) + .param("file-id", archiveFileId) + .when() + .delete("/delete") + .then() + .statusCode(200) + .body("type", is("urn:dx:rs:success")) + .body("title", is("Successful Operation")); + } + + @Test + @Order(27) + @DisplayName("404 (Not Found) DX file delete - RL (Archive file)") + void DeleteRLSampleArchiveFileNotFound() { + given().relaxedHTTPSValidation() + .header("token", delegateToken) + .param("file-id", nonExistingArchiveId) + .when() + .delete("/delete") + .then() + .statusCode(404) + .body("type", is("urn:dx:rs:resourceNotFound")) + .body("title", is("Not Found")) + .body("detail", is("Document of given id does not exist")); + } + + @Test + @Order(28) + @DisplayName("200 (Success) DX file delete - RL (External Storage)") + void DeleteRLSampleExternalFile() { + LOGGER.debug("rl external storage file id..." + externalStorageFileId); + given().relaxedHTTPSValidation() + .header("token", delegateToken) + .param("file-id", externalStorageFileId) + .when() + .delete("/delete") + .then() + .statusCode(200) + .body("type", is("urn:dx:rs:success")) + .body("title", is("Successful Operation")); + } + + + @AfterEach + public void tearDown() { + // Introduce a delay + try { + Thread.sleep(1000); // 1 second delay + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/src/test/java/iudx/file/server/authenticator/JwtAuthServiceTest.java b/src/test/java/iudx/file/server/authenticator/JwtAuthServiceTest.java index 6620737d..4350fec7 100644 --- a/src/test/java/iudx/file/server/authenticator/JwtAuthServiceTest.java +++ b/src/test/java/iudx/file/server/authenticator/JwtAuthServiceTest.java @@ -112,7 +112,7 @@ static void init(Vertx vertx, VertxTestContext testContext) { // test JWTAuth jwtAuth = JWTAuth.create(vertx, jwtAuthOptions); - webClientFactory = new WebClientFactory(vertx, authConfig); + webClientFactory = new WebClientFactory(vertx); // catalogueService = new CatalogueServiceImpl(vertx, webClientFactory, authConfig); catalogueServiceMock = mock(CatalogueServiceImpl.class); cacheServiceMock = mock(CacheService.class); diff --git a/src/test/java/iudx/file/server/authenticator/TokenSetup.java b/src/test/java/iudx/file/server/authenticator/TokenSetup.java new file mode 100644 index 00000000..6685795d --- /dev/null +++ b/src/test/java/iudx/file/server/authenticator/TokenSetup.java @@ -0,0 +1,145 @@ +package iudx.file.server.authenticator; + +import io.vertx.core.*; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.client.WebClient; +import io.vertx.ext.web.client.WebClientOptions; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static iudx.file.server.authenticator.TokensForITs.*; + +public class TokenSetup { + private static final Logger logger = LoggerFactory.getLogger(TokenSetup.class); + private static WebClient webClient; + + public static void setupTokens(String authEndpoint, String clientId, String clientSecret, String delegationId) { + // Fetch tokens asynchronously and wait for all completions + CompositeFuture.all( + fetchToken("openResourceToken", authEndpoint, clientId, clientSecret), + fetchToken("secureResourceToken", authEndpoint, clientId,clientSecret), + fetchToken("adminToken", authEndpoint, clientId,clientSecret), + fetchToken("delegateToken", authEndpoint, clientId,clientSecret, delegationId) + + ).onComplete(result -> { + if (result.succeeded()) { + logger.info("Tokens setup completed successfully"); + webClient.close(); + } else { + // Handle failure, e.g., log the error + logger.error("Error- {}", result.cause().getMessage()); + webClient.close(); + } + }); + } + + private static Future fetchToken(String userType, String authEndpoint, String clientID, String clientSecret) { + Promise promise = Promise.promise(); + JsonObject jsonPayload = getPayload(userType); + // Create a WebClient to make the HTTP request + webClient = WebClient.create(Vertx.vertx(), new WebClientOptions().setSsl(true)); + logger.info("Auth endpoint: {}", authEndpoint); + webClient.postAbs(authEndpoint) + .putHeader("Content-Type", "application/json") + .putHeader("clientID", clientID) + .putHeader("clientSecret", clientSecret) + .sendJson(jsonPayload) + .compose(response -> { + if (response.statusCode() == 200) { + JsonObject jsonResponse = response.bodyAsJsonObject(); + String accessToken = jsonResponse.getJsonObject("results").getString("accessToken"); + // Store the token based on user type + switch (userType) { + case "secureResourceToken": + secureResourceToken = accessToken; + break; + case "openResourceToken": + openResourceToken = accessToken; + break; + case "adminToken": + adminToken = accessToken; + } + promise.complete(accessToken); + } else { + promise.fail("Failed to get token. Status code: " + response.statusCode()); + } + return Future.succeededFuture(); + }) + .onFailure(throwable -> { + throwable.printStackTrace(); + promise.fail(throwable); + }); +// .onComplete(result -> { +// webClient.close(); +// }); + + return promise.future(); + } + + private static Future fetchToken(String userType, String authEndpoint, String clientID, String clientSecret, String delegationId) { + Promise promise = Promise.promise(); + JsonObject jsonPayload = getPayload(userType); + // Create a WebClient to make the HTTP request for fetching delegate and adaptor tokens + webClient = WebClient.create(Vertx.vertx(), new WebClientOptions().setSsl(true)); + + webClient.postAbs(authEndpoint) + .putHeader("Content-Type", "application/json") + .putHeader("clientID", clientID) + .putHeader("clientSecret", clientSecret) + .putHeader("delegationId", delegationId) + .sendJson(jsonPayload) + .compose(response -> { + if (response.statusCode() == 200) { + JsonObject jsonResponse = response.bodyAsJsonObject(); + String accessToken = jsonResponse.getJsonObject("results").getString("accessToken"); + // Store the token based on user type + if (userType.equals("delegateToken")) { + delegateToken = accessToken; + } + promise.complete(accessToken); + } else { + promise.fail("Failed to get token. Status code: " + response.statusCode()); + } + return Future.succeededFuture(); + }) + .onFailure(throwable -> { + throwable.printStackTrace(); + promise.fail(throwable); + }); +// .onComplete(result -> { +// webClient.close(); +// }); + + return promise.future(); + } + + @NotNull + private static JsonObject getPayload(String userType) { + JsonObject jsonPayload = new JsonObject(); + switch (userType) { + case "openResourceToken": + jsonPayload.put("itemId", "b58da193-23d9-43eb-b98a-a103d4b6103c"); + jsonPayload.put("itemType", "resource"); + jsonPayload.put("role", "consumer"); + break; + case "secureResourceToken": + jsonPayload.put("itemId", "83c2e5c2-3574-4e11-9530-2b1fbdfce832"); + jsonPayload.put("itemType", "resource"); + jsonPayload.put("role", "consumer"); + break; + case "adminToken": + jsonPayload.put("itemId", "rs.iudx.io"); + jsonPayload.put("itemType", "resource_server"); + jsonPayload.put("role", "admin"); + break; + case "delegateToken": + jsonPayload.put("itemId", "83c2e5c2-3574-4e11-9530-2b1fbdfce832"); + jsonPayload.put("itemType", "resource"); + jsonPayload.put("role", "delegate"); + break; + } + return jsonPayload; + } + +} diff --git a/src/test/java/iudx/file/server/authenticator/TokensForITs.java b/src/test/java/iudx/file/server/authenticator/TokensForITs.java new file mode 100644 index 00000000..5a5cb257 --- /dev/null +++ b/src/test/java/iudx/file/server/authenticator/TokensForITs.java @@ -0,0 +1,9 @@ +package iudx.file.server.authenticator; + +public class TokensForITs { + public static String secureResourceToken ; + public static String openResourceToken ; + public static String adminToken; + public static String delegateToken; + +} diff --git a/src/test/java/iudx/file/server/common/WebClientFactoryTest.java b/src/test/java/iudx/file/server/common/WebClientFactoryTest.java index db7a50dc..bbec8c49 100644 --- a/src/test/java/iudx/file/server/common/WebClientFactoryTest.java +++ b/src/test/java/iudx/file/server/common/WebClientFactoryTest.java @@ -26,7 +26,7 @@ public void setup(Vertx vertx) { config = new JsonObject(); config.put("catalogueHost", "abcdefg"); config.put("cataloguePort", 123); - webClientFactory = new WebClientFactory(vertx, config); + webClientFactory = new WebClientFactory(vertx); } @DisplayName("Test getWebClientFor method for Unknown serverType") diff --git a/src/test/java/iudx/file/server/mocks/FileUploadMock.java b/src/test/java/iudx/file/server/mocks/FileUploadMock.java index d5cb0579..41417951 100644 --- a/src/test/java/iudx/file/server/mocks/FileUploadMock.java +++ b/src/test/java/iudx/file/server/mocks/FileUploadMock.java @@ -1,5 +1,6 @@ package iudx.file.server.mocks; +import io.vertx.core.Future; import io.vertx.ext.web.FileUpload; public class FileUploadMock implements FileUpload{ @@ -43,4 +44,9 @@ public String charSet() { public boolean cancel() { return false; } + + @Override + public Future delete() { + return Future.succeededFuture(); + } }