From d9d93fda34e3604b55a170088c11924811ad4050 Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Fri, 6 Dec 2024 13:37:03 +1100 Subject: [PATCH 01/11] Added API endpoint to serve dummy config --- .../com/uid2/core/vertx/CoreVerticle.java | 29 +++++++++++++++++++ .../java/com/uid2/core/vertx/Endpoints.java | 3 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/uid2/core/vertx/CoreVerticle.java b/src/main/java/com/uid2/core/vertx/CoreVerticle.java index 50485580..373c888b 100644 --- a/src/main/java/com/uid2/core/vertx/CoreVerticle.java +++ b/src/main/java/com/uid2/core/vertx/CoreVerticle.java @@ -21,6 +21,9 @@ import com.uid2.shared.secure.*; import com.uid2.shared.vertx.RequestCapturingHandler; import com.uid2.shared.vertx.VertxUtils; +import io.vertx.config.ConfigRetriever; +import io.vertx.config.ConfigRetrieverOptions; +import io.vertx.config.ConfigStoreOptions; import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; import io.vertx.core.http.HttpHeaders; @@ -192,6 +195,7 @@ private Router createRoutesSetup() { router.get(Endpoints.OPERATORS_REFRESH.toString()).handler(auth.handle(attestationMiddleware.handle(this::handleOperatorRefresh), Role.OPTOUT_SERVICE)); router.get(Endpoints.PARTNERS_REFRESH.toString()).handler(auth.handle(attestationMiddleware.handle(this::handlePartnerRefresh), Role.OPTOUT_SERVICE)); router.get(Endpoints.OPS_HEALTHCHECK.toString()).handler(this::handleHealthCheck); + router.get(Endpoints.CONFIG.toString()).handler(this::handleGetConfig); if (Optional.ofNullable(ConfigStore.Global.getBoolean("enable_test_endpoints")).orElse(false)) { router.route(Endpoints.ATTEST_GET_TOKEN.toString()).handler(auth.handle(this::handleTestGetAttestationToken, Role.OPERATOR)); @@ -200,6 +204,31 @@ private Router createRoutesSetup() { return router; } + private void handleGetConfig(RoutingContext rc) { + String dummyConfigPath = "conf/dummy-config.json"; + + ConfigStoreOptions dummyFileStore = new ConfigStoreOptions() + .setType("file") + .setConfig(new JsonObject().put("path", dummyConfigPath)); + + ConfigRetrieverOptions retrieverOptions = new ConfigRetrieverOptions().addStore(dummyFileStore); + + ConfigRetriever retriever = ConfigRetriever.create(vertx, retrieverOptions); + + retriever.getConfig().onComplete(ar -> { + if (ar.succeeded()) { + rc.response() + .putHeader("content-type", "application/json") + .end(ar.result().encodePrettily()); + } else { + rc.response() + .setStatusCode(500) + .end("Failed to retrieve configuration"); + } + }); + } + + private void handleHealthCheck(RoutingContext rc) { if (HealthManager.instance.isHealthy()) { rc.response().end("OK"); diff --git a/src/main/java/com/uid2/core/vertx/Endpoints.java b/src/main/java/com/uid2/core/vertx/Endpoints.java index 64f48318..73b161a5 100644 --- a/src/main/java/com/uid2/core/vertx/Endpoints.java +++ b/src/main/java/com/uid2/core/vertx/Endpoints.java @@ -20,7 +20,8 @@ public enum Endpoints { SERVICES_REFRESH("/services/refresh"), SERVICE_LINKS_REFRESH("/service_links/refresh"), OPERATORS_REFRESH("/operators/refresh"), - PARTNERS_REFRESH("/partners/refresh"); + PARTNERS_REFRESH("/partners/refresh"), + CONFIG("/config"); private final String path; From 37016bc9079bb6105462ddc6b96afb6e5dd6e05f Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Tue, 10 Dec 2024 09:00:05 +1100 Subject: [PATCH 02/11] Added unit test to test config endpoint --- .../com/uid2/core/vertx/TestCoreVerticle.java | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java index dc271e95..d4679073 100644 --- a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java +++ b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java @@ -1,4 +1,5 @@ package com.uid2.core.vertx; +import com.google.gson.JsonParser; import com.uid2.core.model.ConfigStore; import com.uid2.core.model.SecretStore; import com.uid2.core.service.*; @@ -9,14 +10,19 @@ import com.uid2.shared.attest.IAttestationTokenService; import com.uid2.shared.attest.JwtService; import com.uid2.shared.auth.*; +import com.uid2.shared.cloud.CloudStorageException; import com.uid2.shared.cloud.ICloudStorage; import com.uid2.shared.secure.AttestationException; import com.uid2.shared.secure.AttestationFailure; import com.uid2.shared.secure.AttestationResult; import com.uid2.shared.secure.ICoreAttestationService; import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider; +import io.vertx.config.ConfigRetriever; +import io.vertx.config.ConfigRetrieverOptions; +import io.vertx.config.ConfigStoreOptions; import io.vertx.core.*; import io.vertx.core.buffer.Buffer; +import io.vertx.core.json.Json; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -39,10 +45,14 @@ import javax.crypto.Cipher; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.SecureRandom; +import java.sql.SQLOutput; import java.time.Instant; import java.util.*; import java.util.concurrent.Callable; @@ -75,7 +85,6 @@ public class TestCoreVerticle { private static final String attestationProtocol = "test-attestation-protocol"; private static final String attestationProtocolPublic = "trusted"; - @BeforeEach void deployVerticle(TestInfo info, Vertx vertx, VertxTestContext testContext) throws Throwable { JsonObject config = new JsonObject(); @@ -874,4 +883,27 @@ void keysRefreshSuccessNoHeaderVersion(Vertx vertx, VertxTestContext testContext } }); } + + @Test + void getConfigSuccess(Vertx vertx, VertxTestContext testContext) throws Exception { + // Load expected config + String expectedConfigString = Files.readString(Paths.get("conf/dummy-config.json")).trim(); + JsonObject expectedConfig = new JsonObject(expectedConfigString); + + // Make HTTP Get request to /config endpoint + this.get(vertx, Endpoints.CONFIG.toString(), ar -> { + if (ar.succeeded()) { + HttpResponse response = ar.result(); + System.out.println("Response: " + response.bodyAsString()); + // Validate response + assertEquals(200, response.statusCode()); + assertEquals("application/json", response.getHeader("content-type")); + JsonObject actualConfig = new JsonObject(response.bodyAsString()); + assertEquals(expectedConfig, actualConfig); + testContext.completeNow(); + } else { + testContext.failNow(ar.cause()); + } + }); + } } From 317c2df771ac8be3d832c6981f05c3f6c44341e3 Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Tue, 7 Jan 2025 12:03:13 +1100 Subject: [PATCH 03/11] Update CoreVerticle config endpoint uses vertx FileSystem rather than ConfigRetriever --- .../java/com/uid2/core/vertx/CoreVerticle.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/uid2/core/vertx/CoreVerticle.java b/src/main/java/com/uid2/core/vertx/CoreVerticle.java index 373c888b..15e66070 100644 --- a/src/main/java/com/uid2/core/vertx/CoreVerticle.java +++ b/src/main/java/com/uid2/core/vertx/CoreVerticle.java @@ -21,11 +21,9 @@ import com.uid2.shared.secure.*; import com.uid2.shared.vertx.RequestCapturingHandler; import com.uid2.shared.vertx.VertxUtils; -import io.vertx.config.ConfigRetriever; -import io.vertx.config.ConfigRetrieverOptions; -import io.vertx.config.ConfigStoreOptions; import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; +import io.vertx.core.file.FileSystem; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerResponse; @@ -207,19 +205,15 @@ private Router createRoutesSetup() { private void handleGetConfig(RoutingContext rc) { String dummyConfigPath = "conf/dummy-config.json"; - ConfigStoreOptions dummyFileStore = new ConfigStoreOptions() - .setType("file") - .setConfig(new JsonObject().put("path", dummyConfigPath)); + FileSystem fs = vertx.fileSystem(); - ConfigRetrieverOptions retrieverOptions = new ConfigRetrieverOptions().addStore(dummyFileStore); - - ConfigRetriever retriever = ConfigRetriever.create(vertx, retrieverOptions); - - retriever.getConfig().onComplete(ar -> { + fs.readFile(dummyConfigPath, ar -> { if (ar.succeeded()) { + String fileContent = ar.result().toString(); + JsonObject configJson = new JsonObject(fileContent); rc.response() .putHeader("content-type", "application/json") - .end(ar.result().encodePrettily()); + .end(configJson.encodePrettily()); } else { rc.response() .setStatusCode(500) From 4e042bfdeed0b94036b3003d55d9eb99b72ba65e Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Tue, 7 Jan 2025 14:11:05 +1100 Subject: [PATCH 04/11] Renamed config endpoint and json file --- .../java/com/uid2/core/vertx/CoreVerticle.java | 6 +++--- src/main/java/com/uid2/core/vertx/Endpoints.java | 2 +- .../java/com/uid2/core/vertx/TestCoreVerticle.java | 14 +++----------- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/uid2/core/vertx/CoreVerticle.java b/src/main/java/com/uid2/core/vertx/CoreVerticle.java index 15e66070..c5a653b9 100644 --- a/src/main/java/com/uid2/core/vertx/CoreVerticle.java +++ b/src/main/java/com/uid2/core/vertx/CoreVerticle.java @@ -193,7 +193,7 @@ private Router createRoutesSetup() { router.get(Endpoints.OPERATORS_REFRESH.toString()).handler(auth.handle(attestationMiddleware.handle(this::handleOperatorRefresh), Role.OPTOUT_SERVICE)); router.get(Endpoints.PARTNERS_REFRESH.toString()).handler(auth.handle(attestationMiddleware.handle(this::handlePartnerRefresh), Role.OPTOUT_SERVICE)); router.get(Endpoints.OPS_HEALTHCHECK.toString()).handler(this::handleHealthCheck); - router.get(Endpoints.CONFIG.toString()).handler(this::handleGetConfig); + router.get(Endpoints.OPERATOR_CONFIG.toString()).handler(this::handleGetConfig); if (Optional.ofNullable(ConfigStore.Global.getBoolean("enable_test_endpoints")).orElse(false)) { router.route(Endpoints.ATTEST_GET_TOKEN.toString()).handler(auth.handle(this::handleTestGetAttestationToken, Role.OPERATOR)); @@ -203,11 +203,11 @@ private Router createRoutesSetup() { } private void handleGetConfig(RoutingContext rc) { - String dummyConfigPath = "conf/dummy-config.json"; + String configPath = "conf/operator-config.json"; FileSystem fs = vertx.fileSystem(); - fs.readFile(dummyConfigPath, ar -> { + fs.readFile(configPath, ar -> { if (ar.succeeded()) { String fileContent = ar.result().toString(); JsonObject configJson = new JsonObject(fileContent); diff --git a/src/main/java/com/uid2/core/vertx/Endpoints.java b/src/main/java/com/uid2/core/vertx/Endpoints.java index 73b161a5..7588f67e 100644 --- a/src/main/java/com/uid2/core/vertx/Endpoints.java +++ b/src/main/java/com/uid2/core/vertx/Endpoints.java @@ -21,7 +21,7 @@ public enum Endpoints { SERVICE_LINKS_REFRESH("/service_links/refresh"), OPERATORS_REFRESH("/operators/refresh"), PARTNERS_REFRESH("/partners/refresh"), - CONFIG("/config"); + OPERATOR_CONFIG("/operator/config"); private final String path; diff --git a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java index d4679073..34f12a65 100644 --- a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java +++ b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java @@ -1,5 +1,4 @@ package com.uid2.core.vertx; -import com.google.gson.JsonParser; import com.uid2.core.model.ConfigStore; import com.uid2.core.model.SecretStore; import com.uid2.core.service.*; @@ -10,19 +9,14 @@ import com.uid2.shared.attest.IAttestationTokenService; import com.uid2.shared.attest.JwtService; import com.uid2.shared.auth.*; -import com.uid2.shared.cloud.CloudStorageException; import com.uid2.shared.cloud.ICloudStorage; import com.uid2.shared.secure.AttestationException; import com.uid2.shared.secure.AttestationFailure; import com.uid2.shared.secure.AttestationResult; import com.uid2.shared.secure.ICoreAttestationService; import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider; -import io.vertx.config.ConfigRetriever; -import io.vertx.config.ConfigRetrieverOptions; -import io.vertx.config.ConfigStoreOptions; import io.vertx.core.*; import io.vertx.core.buffer.Buffer; -import io.vertx.core.json.Json; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -45,14 +39,12 @@ import javax.crypto.Cipher; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.SecureRandom; -import java.sql.SQLOutput; import java.time.Instant; import java.util.*; import java.util.concurrent.Callable; @@ -887,11 +879,11 @@ void keysRefreshSuccessNoHeaderVersion(Vertx vertx, VertxTestContext testContext @Test void getConfigSuccess(Vertx vertx, VertxTestContext testContext) throws Exception { // Load expected config - String expectedConfigString = Files.readString(Paths.get("conf/dummy-config.json")).trim(); + String expectedConfigString = Files.readString(Paths.get("conf/operator-config.json")).trim(); JsonObject expectedConfig = new JsonObject(expectedConfigString); - // Make HTTP Get request to /config endpoint - this.get(vertx, Endpoints.CONFIG.toString(), ar -> { + // Make HTTP Get request to operator config endpoint + this.get(vertx, Endpoints.OPERATOR_CONFIG.toString(), ar -> { if (ar.succeeded()) { HttpResponse response = ar.result(); System.out.println("Response: " + response.bodyAsString()); From 900695c7a3a3f38c9d675442f6e8a954db425c56 Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Tue, 7 Jan 2025 14:48:01 +1100 Subject: [PATCH 05/11] Add operator-config.json --- conf/operator-config.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 conf/operator-config.json diff --git a/conf/operator-config.json b/conf/operator-config.json new file mode 100644 index 00000000..789a9310 --- /dev/null +++ b/conf/operator-config.json @@ -0,0 +1,4 @@ +{ + "name": "Dummy Config", + "version": "1.0.0" +} \ No newline at end of file From d38be531a3d377b41a627058f14a920d196fe997 Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Tue, 7 Jan 2025 14:59:30 +1100 Subject: [PATCH 06/11] Updated CoreVerticle More detailed error message in handleGetConfig method --- src/main/java/com/uid2/core/vertx/CoreVerticle.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/uid2/core/vertx/CoreVerticle.java b/src/main/java/com/uid2/core/vertx/CoreVerticle.java index ce7c7999..cf3a34ed 100644 --- a/src/main/java/com/uid2/core/vertx/CoreVerticle.java +++ b/src/main/java/com/uid2/core/vertx/CoreVerticle.java @@ -217,7 +217,7 @@ private void handleGetConfig(RoutingContext rc) { } else { rc.response() .setStatusCode(500) - .end("Failed to retrieve configuration"); + .end("Failed to retrieve configuration: " + ar.cause().getMessage()); } }); } From 717f4b30f752f96654c4db3e8c9dcbfc67a3c471 Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Tue, 7 Jan 2025 15:19:32 +1100 Subject: [PATCH 07/11] Add config values to operator-config.json --- conf/operator-config.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/conf/operator-config.json b/conf/operator-config.json index 789a9310..817d714d 100644 --- a/conf/operator-config.json +++ b/conf/operator-config.json @@ -1,4 +1,6 @@ { - "name": "Dummy Config", - "version": "1.0.0" + "identity_token_expires_after_seconds": 3600, + "refresh_token_expires_after_seconds": 86400, + "refresh_identity_token_after_seconds": 900, + "sharing_token_expiry_seconds": 2592000 } \ No newline at end of file From bf523c7e6ac4ce055186aa1c17462aaae872ec92 Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Wed, 8 Jan 2025 13:36:59 +1100 Subject: [PATCH 08/11] Addressed review feedback: used HttpHeaders.CONTENT_TYPE --- src/main/java/com/uid2/core/vertx/CoreVerticle.java | 2 +- src/test/java/com/uid2/core/vertx/TestCoreVerticle.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/uid2/core/vertx/CoreVerticle.java b/src/main/java/com/uid2/core/vertx/CoreVerticle.java index cf3a34ed..f36310c7 100644 --- a/src/main/java/com/uid2/core/vertx/CoreVerticle.java +++ b/src/main/java/com/uid2/core/vertx/CoreVerticle.java @@ -212,7 +212,7 @@ private void handleGetConfig(RoutingContext rc) { String fileContent = ar.result().toString(); JsonObject configJson = new JsonObject(fileContent); rc.response() - .putHeader("content-type", "application/json") + .putHeader(HttpHeaders.CONTENT_TYPE, "application/json") .end(configJson.encodePrettily()); } else { rc.response() diff --git a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java index d67bd1d9..e27cc7bc 100644 --- a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java +++ b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java @@ -17,6 +17,7 @@ import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider; import io.vertx.core.*; import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpHeaders; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -889,7 +890,7 @@ void getConfigSuccess(Vertx vertx, VertxTestContext testContext) throws Exceptio System.out.println("Response: " + response.bodyAsString()); // Validate response assertEquals(200, response.statusCode()); - assertEquals("application/json", response.getHeader("content-type")); + assertEquals("application/json", response.getHeader(HttpHeaders.CONTENT_TYPE)); JsonObject actualConfig = new JsonObject(response.bodyAsString()); assertEquals(expectedConfig, actualConfig); testContext.completeNow(); From 2bcff621b470713d783f2e170be26340adda9f45 Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Wed, 8 Jan 2025 17:33:47 +1100 Subject: [PATCH 09/11] Addressed review feedback: testContext.succeeding, handler exceptions - Update tests to use testContext.succeeding to handle failures and exceptions thrown by success case - Error handling to make sure response is sent when an exception is thrown in handleGetConfig. - Inject vertx filesystem into CoreVerticle - Add unit test to cover when operator-config content is invalid JSON --- src/main/java/com/uid2/core/Const.java | 2 + src/main/java/com/uid2/core/Main.java | 4 +- .../com/uid2/core/vertx/CoreVerticle.java | 35 +++++++----- .../TestClientSideKeypairMetadataPath.java | 5 +- .../com/uid2/core/vertx/TestCoreVerticle.java | 56 +++++++++++++------ .../vertx/TestServiceLinkMetadataPath.java | 5 +- .../core/vertx/TestServiceMetadataPath.java | 5 +- .../vertx/TestSiteSpecificMetadataPath.java | 5 +- .../TestSiteSpecificMetadataPathDisabled.java | 6 +- .../core/vertx/TestSitesMetadataPath.java | 6 +- 10 files changed, 91 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/uid2/core/Const.java b/src/main/java/com/uid2/core/Const.java index 22add792..60f2d73c 100644 --- a/src/main/java/com/uid2/core/Const.java +++ b/src/main/java/com/uid2/core/Const.java @@ -17,4 +17,6 @@ public class Config extends com.uid2.shared.Const.Config { public static final String KmsSecretAccessKeyProp = "kms_aws_secret_access_key"; public static final String KmsEndpointProp = "kms_aws_endpoint"; } + + public static final String OPERATOR_CONFIG_PATH = "conf/operator-config.json"; } \ No newline at end of file diff --git a/src/main/java/com/uid2/core/Main.java b/src/main/java/com/uid2/core/Main.java index d57b1cfb..600448b1 100644 --- a/src/main/java/com/uid2/core/Main.java +++ b/src/main/java/com/uid2/core/Main.java @@ -36,6 +36,7 @@ import io.vertx.core.DeploymentOptions; import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; +import io.vertx.core.file.FileSystem; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.impl.HttpUtils; import io.vertx.core.json.JsonObject; @@ -161,7 +162,8 @@ public static void main(String[] args) { ); JwtService jwtService = new JwtService(config); - coreVerticle = new CoreVerticle(cloudStorage, operatorKeyProvider, attestationService, attestationTokenService, enclaveIdProvider, operatorJWTTokenProvider, jwtService, cloudEncryptionKeyProvider); + FileSystem fileSystem = vertx.fileSystem(); + coreVerticle = new CoreVerticle(cloudStorage, operatorKeyProvider, attestationService, attestationTokenService, enclaveIdProvider, operatorJWTTokenProvider, jwtService, cloudEncryptionKeyProvider, fileSystem); } catch (Exception e) { System.out.println("failed to initialize core verticle: " + e.getMessage()); System.exit(-1); diff --git a/src/main/java/com/uid2/core/vertx/CoreVerticle.java b/src/main/java/com/uid2/core/vertx/CoreVerticle.java index f36310c7..018326b8 100644 --- a/src/main/java/com/uid2/core/vertx/CoreVerticle.java +++ b/src/main/java/com/uid2/core/vertx/CoreVerticle.java @@ -82,6 +82,8 @@ public class CoreVerticle extends AbstractVerticle { private final OperatorJWTTokenProvider operatorJWTTokenProvider; private final RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider; + private final FileSystem fileSystem; + public CoreVerticle(ICloudStorage cloudStorage, IAuthorizableProvider authProvider, AttestationService attestationService, @@ -89,7 +91,8 @@ public CoreVerticle(ICloudStorage cloudStorage, IEnclaveIdentifierProvider enclaveIdentifierProvider, OperatorJWTTokenProvider operatorJWTTokenProvider, JwtService jwtService, - RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider) throws Exception { + RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider, + FileSystem fileSystem) throws Exception { this.operatorJWTTokenProvider = operatorJWTTokenProvider; this.healthComponent.setHealthStatus(false, "not started"); @@ -101,6 +104,8 @@ public CoreVerticle(ICloudStorage cloudStorage, this.enclaveIdentifierProvider.addListener(this.attestationService); this.cloudEncryptionKeyProvider = cloudEncryptionKeyProvider; + this.fileSystem = fileSystem; + final String jwtAudience = ConfigStore.Global.get(Const.Config.CorePublicUrlProp); final String jwtIssuer = ConfigStore.Global.get(Const.Config.CorePublicUrlProp); Boolean enforceJwt = ConfigStore.Global.getBoolean(Const.Config.EnforceJwtProp); @@ -132,8 +137,9 @@ public CoreVerticle(ICloudStorage cloudStorage, IAttestationTokenService attestationTokenService, IEnclaveIdentifierProvider enclaveIdentifierProvider, OperatorJWTTokenProvider jwtTokenProvider, - JwtService jwtService) throws Exception { - this(cloudStorage, authorizableProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, jwtTokenProvider, jwtService, null); + JwtService jwtService, + FileSystem fileSystem) throws Exception { + this(cloudStorage, authorizableProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, jwtTokenProvider, jwtService, null, fileSystem); } @Override @@ -203,17 +209,20 @@ private Router createRoutesSetup() { } private void handleGetConfig(RoutingContext rc) { - String configPath = "conf/operator-config.json"; - - FileSystem fs = vertx.fileSystem(); - - fs.readFile(configPath, ar -> { + fileSystem.readFile(com.uid2.core.Const.OPERATOR_CONFIG_PATH, ar -> { if (ar.succeeded()) { - String fileContent = ar.result().toString(); - JsonObject configJson = new JsonObject(fileContent); - rc.response() - .putHeader(HttpHeaders.CONTENT_TYPE, "application/json") - .end(configJson.encodePrettily()); + try { + String fileContent = ar.result().toString(); + JsonObject configJson = new JsonObject(fileContent); + rc.response() + .putHeader(HttpHeaders.CONTENT_TYPE, "application/json") + .end(configJson.encodePrettily()); + } catch (Exception e) { + rc.response() + .setStatusCode(500) + .end("Failed to parse configuration: " + e.getMessage()); + throw new RuntimeException(e); + } } else { rc.response() .setStatusCode(500) diff --git a/src/test/java/com/uid2/core/vertx/TestClientSideKeypairMetadataPath.java b/src/test/java/com/uid2/core/vertx/TestClientSideKeypairMetadataPath.java index 96796eaf..f2284ded 100644 --- a/src/test/java/com/uid2/core/vertx/TestClientSideKeypairMetadataPath.java +++ b/src/test/java/com/uid2/core/vertx/TestClientSideKeypairMetadataPath.java @@ -17,6 +17,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -53,6 +54,7 @@ public class TestClientSideKeypairMetadataPath { private IEnclaveIdentifierProvider enclaveIdentifierProvider; private AttestationService attestationService; + private FileSystem fileSystem; @Mock private OperatorJWTTokenProvider operatorJWTTokenProvider; @@ -70,9 +72,10 @@ void deployVerticle(Vertx vertx, VertxTestContext testContext) throws Throwable ConfigStore.Global.load(config); attestationService = new AttestationService(); + fileSystem = vertx.fileSystem(); SecretStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testGlobalMetadata/test-secrets.json")))); MockitoAnnotations.initMocks(this); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); } diff --git a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java index e27cc7bc..6553db97 100644 --- a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java +++ b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java @@ -17,6 +17,7 @@ import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider; import io.vertx.core.*; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; import io.vertx.core.http.HttpHeaders; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -73,8 +74,11 @@ public class TestCoreVerticle { private JwtService jwtService; @Mock private RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider; + @Mock + private FileSystem fileSystem; private AttestationService attestationService; + private String operatorConfig; private static final String attestationProtocol = "test-attestation-protocol"; private static final String attestationProtocolPublic = "trusted"; @@ -118,7 +122,18 @@ void deployVerticle(TestInfo info, Vertx vertx, VertxTestContext testContext) th } }); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, cloudEncryptionKeyProvider); + operatorConfig = Files.readString(Paths.get(com.uid2.core.Const.OPERATOR_CONFIG_PATH)).trim(); + + when(fileSystem.readFile(anyString(), any())).thenAnswer(invocation -> { + String path = invocation.getArgument(0); + if (Objects.equals(path, com.uid2.core.Const.OPERATOR_CONFIG_PATH)) { + Handler> handler = invocation.getArgument(1); + handler.handle(Future.succeededFuture(Buffer.buffer(operatorConfig))); + } + return null; + }); + + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, cloudEncryptionKeyProvider, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); } @@ -878,25 +893,30 @@ void keysRefreshSuccessNoHeaderVersion(Vertx vertx, VertxTestContext testContext } @Test - void getConfigSuccess(Vertx vertx, VertxTestContext testContext) throws Exception { - // Load expected config - String expectedConfigString = Files.readString(Paths.get("conf/operator-config.json")).trim(); - JsonObject expectedConfig = new JsonObject(expectedConfigString); + void getConfigSuccess(Vertx vertx, VertxTestContext testContext) { + JsonObject expectedConfig = new JsonObject(operatorConfig); + // Make HTTP Get request to operator config endpoint - this.get(vertx, Endpoints.OPERATOR_CONFIG.toString(), ar -> { - if (ar.succeeded()) { - HttpResponse response = ar.result(); - System.out.println("Response: " + response.bodyAsString()); - // Validate response - assertEquals(200, response.statusCode()); - assertEquals("application/json", response.getHeader(HttpHeaders.CONTENT_TYPE)); - JsonObject actualConfig = new JsonObject(response.bodyAsString()); - assertEquals(expectedConfig, actualConfig); + this.get(vertx, Endpoints.OPERATOR_CONFIG.toString(), testContext.succeeding(response -> testContext.verify(() -> { + assertEquals(200, response.statusCode()); + assertEquals("application/json", response.getHeader(HttpHeaders.CONTENT_TYPE)); + JsonObject actualConfig = new JsonObject(response.bodyAsString()); + assertEquals(expectedConfig, actualConfig); + testContext.completeNow(); + }) + )); + } + + @Test + void getConfigInvalidJson(Vertx vertx, VertxTestContext testContext) { + operatorConfig = "invalid config"; + + + this.get(vertx, Endpoints.OPERATOR_CONFIG.toString(), testContext.succeeding(response -> testContext.verify(() -> { + assertEquals(500, response.statusCode()); testContext.completeNow(); - } else { - testContext.failNow(ar.cause()); - } - }); + }) + )); } } diff --git a/src/test/java/com/uid2/core/vertx/TestServiceLinkMetadataPath.java b/src/test/java/com/uid2/core/vertx/TestServiceLinkMetadataPath.java index 9784a51b..224dfcae 100644 --- a/src/test/java/com/uid2/core/vertx/TestServiceLinkMetadataPath.java +++ b/src/test/java/com/uid2/core/vertx/TestServiceLinkMetadataPath.java @@ -17,6 +17,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -52,6 +53,7 @@ public class TestServiceLinkMetadataPath { private IEnclaveIdentifierProvider enclaveIdentifierProvider; private AttestationService attestationService; + private FileSystem fileSystem; @Mock private OperatorJWTTokenProvider operatorJWTTokenProvider; @@ -69,9 +71,10 @@ void deployVerticle(Vertx vertx, VertxTestContext testContext) throws Throwable ConfigStore.Global.load(config); attestationService = new AttestationService(); + fileSystem = vertx.fileSystem(); SecretStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testGlobalMetadata/test-secrets.json")))); MockitoAnnotations.initMocks(this); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); } diff --git a/src/test/java/com/uid2/core/vertx/TestServiceMetadataPath.java b/src/test/java/com/uid2/core/vertx/TestServiceMetadataPath.java index cb2aa75d..0fcf0dfc 100644 --- a/src/test/java/com/uid2/core/vertx/TestServiceMetadataPath.java +++ b/src/test/java/com/uid2/core/vertx/TestServiceMetadataPath.java @@ -17,6 +17,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -52,6 +53,7 @@ public class TestServiceMetadataPath { private IEnclaveIdentifierProvider enclaveIdentifierProvider; private AttestationService attestationService; + private FileSystem fileSystem; @Mock private OperatorJWTTokenProvider operatorJWTTokenProvider; @@ -69,9 +71,10 @@ void deployVerticle(Vertx vertx, VertxTestContext testContext) throws Throwable ConfigStore.Global.load(config); attestationService = new AttestationService(); + fileSystem = vertx.fileSystem(); SecretStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testGlobalMetadata/test-secrets.json")))); MockitoAnnotations.initMocks(this); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); } diff --git a/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPath.java b/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPath.java index dcd7b448..0a2d2dad 100644 --- a/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPath.java +++ b/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPath.java @@ -15,6 +15,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -59,6 +60,7 @@ public class TestSiteSpecificMetadataPath { private JwtService jwtService; private AttestationService attestationService; + private FileSystem fileSystem; // we need trusted to skip the attestation procedure or otherwise the core encpoint call made in this file will // fail at the attestation handler @@ -67,10 +69,11 @@ public class TestSiteSpecificMetadataPath { @BeforeEach void deployVerticle(Vertx vertx, VertxTestContext testContext) throws Throwable { attestationService = new AttestationService(); + fileSystem = vertx.fileSystem(); SecretStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testSiteSpecificMetadata/test-secrets.json")))); ConfigStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testSiteSpecificMetadata/test-configs-provide-private-site-data.json")))); MockitoAnnotations.initMocks(this); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); } diff --git a/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPathDisabled.java b/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPathDisabled.java index 6418a34f..626ae95f 100644 --- a/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPathDisabled.java +++ b/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPathDisabled.java @@ -15,6 +15,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -61,6 +62,8 @@ public class TestSiteSpecificMetadataPathDisabled { private AttestationService attestationService; + private FileSystem fileSystem; + // we need trusted to skip the attestation procedure or otherwise the core encpoint call made in this file will // fail at the attestation handler private static final String attestationProtocol = "trusted"; @@ -68,10 +71,11 @@ public class TestSiteSpecificMetadataPathDisabled { @BeforeEach void deployVerticle(Vertx vertx, VertxTestContext testContext) throws Throwable { attestationService = new AttestationService(); + fileSystem = vertx.fileSystem(); SecretStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testSiteSpecificMetadata/test-secrets.json")))); ConfigStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testSiteSpecificMetadata/test-configs-stop-providing-private-site-data.json")))); MockitoAnnotations.initMocks(this); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); } diff --git a/src/test/java/com/uid2/core/vertx/TestSitesMetadataPath.java b/src/test/java/com/uid2/core/vertx/TestSitesMetadataPath.java index e25e9841..338640d2 100644 --- a/src/test/java/com/uid2/core/vertx/TestSitesMetadataPath.java +++ b/src/test/java/com/uid2/core/vertx/TestSitesMetadataPath.java @@ -17,6 +17,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -56,6 +57,8 @@ public class TestSitesMetadataPath { private AttestationService attestationService; + private FileSystem fileSystem; + @Mock private OperatorJWTTokenProvider operatorJWTTokenProvider; @Mock @@ -72,9 +75,10 @@ void deployVerticle(Vertx vertx, VertxTestContext testContext) throws Throwable ConfigStore.Global.load(config); attestationService = new AttestationService(); + fileSystem = vertx.fileSystem(); SecretStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testGlobalMetadata/test-secrets.json")))); MockitoAnnotations.initMocks(this); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); } From eabb06dbef0fd942c5b69fe64b3d6605cf239c47 Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Wed, 8 Jan 2025 17:38:51 +1100 Subject: [PATCH 10/11] Addressed review feedback: Operator config endpoint authentication --- src/main/java/com/uid2/core/vertx/CoreVerticle.java | 2 +- src/test/java/com/uid2/core/vertx/TestCoreVerticle.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/uid2/core/vertx/CoreVerticle.java b/src/main/java/com/uid2/core/vertx/CoreVerticle.java index 018326b8..eb411fb3 100644 --- a/src/main/java/com/uid2/core/vertx/CoreVerticle.java +++ b/src/main/java/com/uid2/core/vertx/CoreVerticle.java @@ -199,7 +199,7 @@ private Router createRoutesSetup() { router.get(Endpoints.OPERATORS_REFRESH.toString()).handler(auth.handle(attestationMiddleware.handle(this::handleOperatorRefresh), Role.OPTOUT_SERVICE)); router.get(Endpoints.PARTNERS_REFRESH.toString()).handler(auth.handle(attestationMiddleware.handle(this::handlePartnerRefresh), Role.OPTOUT_SERVICE)); router.get(Endpoints.OPS_HEALTHCHECK.toString()).handler(this::handleHealthCheck); - router.get(Endpoints.OPERATOR_CONFIG.toString()).handler(this::handleGetConfig); + router.get(Endpoints.OPERATOR_CONFIG.toString()).handler(auth.handle(this::handleGetConfig, Role.OPERATOR)); if (Optional.ofNullable(ConfigStore.Global.getBoolean("enable_test_endpoints")).orElse(false)) { router.route(Endpoints.ATTEST_GET_TOKEN.toString()).handler(auth.handle(this::handleTestGetAttestationToken, Role.OPERATOR)); diff --git a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java index 6553db97..8a131c24 100644 --- a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java +++ b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java @@ -896,6 +896,7 @@ void keysRefreshSuccessNoHeaderVersion(Vertx vertx, VertxTestContext testContext void getConfigSuccess(Vertx vertx, VertxTestContext testContext) { JsonObject expectedConfig = new JsonObject(operatorConfig); + fakeAuth(Role.OPERATOR); // Make HTTP Get request to operator config endpoint this.get(vertx, Endpoints.OPERATOR_CONFIG.toString(), testContext.succeeding(response -> testContext.verify(() -> { @@ -912,6 +913,7 @@ void getConfigSuccess(Vertx vertx, VertxTestContext testContext) { void getConfigInvalidJson(Vertx vertx, VertxTestContext testContext) { operatorConfig = "invalid config"; + fakeAuth(Role.OPERATOR); this.get(vertx, Endpoints.OPERATOR_CONFIG.toString(), testContext.succeeding(response -> testContext.verify(() -> { assertEquals(500, response.statusCode()); From bb29d9c16797a74a1e49c7c111ff6a1c68a3cd16 Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Thu, 9 Jan 2025 13:46:28 +1100 Subject: [PATCH 11/11] Addressed review feedback: mock filesystem calls handler with failed future --- src/test/java/com/uid2/core/vertx/TestCoreVerticle.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java index 8a131c24..1e5151e5 100644 --- a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java +++ b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java @@ -126,9 +126,11 @@ void deployVerticle(TestInfo info, Vertx vertx, VertxTestContext testContext) th when(fileSystem.readFile(anyString(), any())).thenAnswer(invocation -> { String path = invocation.getArgument(0); + Handler> handler = invocation.getArgument(1); if (Objects.equals(path, com.uid2.core.Const.OPERATOR_CONFIG_PATH)) { - Handler> handler = invocation.getArgument(1); handler.handle(Future.succeededFuture(Buffer.buffer(operatorConfig))); + } else { + handler.handle(Future.failedFuture(new RuntimeException("Failed to read file: " + path))); } return null; });