diff --git a/conf/default-config.json b/conf/default-config.json index 224df8906..d4408bc34 100644 --- a/conf/default-config.json +++ b/conf/default-config.json @@ -30,6 +30,8 @@ "salts_metadata_path": "salts/metadata.json", "services_metadata_path": "services/metadata.json", "service_links_metadata_path": "service_links/metadata.json", + "cloud_encryption_keys_metadata_path": "cloud_encryption_keys/metadata.json", + "cloud_encryption_keys_refresh_ms": 300000, "optout_metadata_path": null, "optout_inmem_cache": false, "enclave_platform": null, diff --git a/conf/docker-config.json b/conf/docker-config.json index 25f38e6ae..8afd14869 100644 --- a/conf/docker-config.json +++ b/conf/docker-config.json @@ -31,6 +31,7 @@ "salts_metadata_path": "/com.uid2.core/test/salts/metadata.json", "services_metadata_path": "/com.uid2.core/test/services/metadata.json", "service_links_metadata_path": "/com.uid2.core/test/service_links/metadata.json", + "cloud_encryption_keys_metadata_path": "/com.uid2.core/test/cloud_encryption_keys/metadata.json", "identity_token_expires_after_seconds": 3600, "optout_metadata_path": null, "optout_inmem_cache": false, diff --git a/conf/integ-config.json b/conf/integ-config.json index f1cf90742..3efa3fc7a 100644 --- a/conf/integ-config.json +++ b/conf/integ-config.json @@ -14,6 +14,6 @@ "optout_api_token": "test-operator-key", "optout_api_uri": "http://localhost:8081/optout/replicate", "salts_expired_shutdown_hours": 12, + "cloud_encryption_keys_metadata_path": "http://localhost:8088/cloud_encryption_keys/retrieve", "operator_type": "public" - } \ No newline at end of file diff --git a/conf/local-config.json b/conf/local-config.json index 6a357dba1..d1bf225b5 100644 --- a/conf/local-config.json +++ b/conf/local-config.json @@ -9,6 +9,7 @@ "salts_metadata_path": "/com.uid2.core/test/salts/metadata.json", "services_metadata_path": "/com.uid2.core/test/services/metadata.json", "service_links_metadata_path": "/com.uid2.core/test/service_links/metadata.json", + "cloud_encryption_keys_metadata_path":"/com.uid2.core/test/cloud_encryption_keys/metadata.json", "identity_token_expires_after_seconds": 3600, "refresh_token_expires_after_seconds": 86400, "refresh_identity_token_after_seconds": 900, diff --git a/conf/local-e2e-docker-private-config.json b/conf/local-e2e-docker-private-config.json index 8637e6da3..084e05004 100644 --- a/conf/local-e2e-docker-private-config.json +++ b/conf/local-e2e-docker-private-config.json @@ -11,6 +11,7 @@ "keysets_metadata_path": "http://core:8088/key/keyset/refresh", "keyset_keys_metadata_path": "http://core:8088/key/keyset-keys/refresh", "salts_metadata_path": "http://core:8088/salt/refresh", + "cloud_encryption_keys_metadata_path": "http://core:8088/cloud_encryption_keys/retrieve", "identity_token_expires_after_seconds": 3600, "refresh_token_expires_after_seconds": 86400, "refresh_identity_token_after_seconds": 900, diff --git a/conf/local-e2e-docker-public-config.json b/conf/local-e2e-docker-public-config.json index a145c4d17..49c07e2aa 100644 --- a/conf/local-e2e-docker-public-config.json +++ b/conf/local-e2e-docker-public-config.json @@ -13,6 +13,7 @@ "salts_metadata_path": "http://core:8088/salt/refresh", "services_metadata_path": "http://core:8088/services/refresh", "service_links_metadata_path": "http://core:8088/service_links/refresh", + "cloud_encryption_keys_metadata_path": "http://core:8088/cloud_encryption_keys/retrieve", "identity_token_expires_after_seconds": 3600, "refresh_token_expires_after_seconds": 86400, "refresh_identity_token_after_seconds": 900, diff --git a/conf/local-e2e-private-config.json b/conf/local-e2e-private-config.json index 4ab52330f..3dc31bfdf 100644 --- a/conf/local-e2e-private-config.json +++ b/conf/local-e2e-private-config.json @@ -13,6 +13,7 @@ "salts_metadata_path": "http://localhost:8088/salt/refresh", "services_metadata_path": "http://localhost:8088/services/refresh", "service_links_metadata_path": "http://localhost:8088/service_links/refresh", + "cloud_encryption_keys_metadata_path": "http://core:8088/cloud_encryption_keys/retrieve", "identity_token_expires_after_seconds": 3600, "refresh_token_expires_after_seconds": 86400, "refresh_identity_token_after_seconds": 900, diff --git a/conf/local-e2e-public-config.json b/conf/local-e2e-public-config.json index bfdc8e394..3b591de62 100644 --- a/conf/local-e2e-public-config.json +++ b/conf/local-e2e-public-config.json @@ -13,6 +13,7 @@ "salts_metadata_path": "http://localhost:8088/salt/refresh", "services_metadata_path": "http://localhost:8088/services/refresh", "service_links_metadata_path": "http://localhost:8088/service_links/refresh", + "cloud_encryption_keys_metadata_path": "http://core:8088/cloud_encryption_keys/retrieve", "identity_token_expires_after_seconds": 3600, "refresh_token_expires_after_seconds": 86400, "refresh_identity_token_after_seconds": 900, diff --git a/conf/validator-latest-e2e-docker-public-config.json b/conf/validator-latest-e2e-docker-public-config.json index 8f82b01a4..f607201c6 100644 --- a/conf/validator-latest-e2e-docker-public-config.json +++ b/conf/validator-latest-e2e-docker-public-config.json @@ -14,6 +14,7 @@ "salts_metadata_path": "http://core:8088/salt/refresh", "services_metadata_path": "http://core:8088/services/refresh", "service_links_metadata_path": "http://core:8088/service_links/refresh", + "cloud_encryption_keys_metadata_path": "https://core:8088/cloud_encryption_keys/retrieve", "identity_token_expires_after_seconds": 3600, "refresh_token_expires_after_seconds": 86400, "refresh_identity_token_after_seconds": 900, diff --git a/pom.xml b/pom.xml index 69634018d..c11f47781 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.uid2 uid2-operator - 5.43.4 + 5.43.6-alpha-147-SNAPSHOT UTF-8 @@ -22,7 +22,7 @@ 2.1.0 2.1.0 2.1.0 - 8.0.9 + 8.0.15-alpha-177-SNAPSHOT ${project.version} 21 21 diff --git a/scripts/aws/conf/integ-euid-config.json b/scripts/aws/conf/integ-euid-config.json index 45d3dbe94..22f8a15e7 100644 --- a/scripts/aws/conf/integ-euid-config.json +++ b/scripts/aws/conf/integ-euid-config.json @@ -9,6 +9,7 @@ "service_links_metadata_path": "https://core.integ.euid.eu/service_links/refresh", "optout_metadata_path": "https://optout.integ.euid.eu/optout/refresh", "core_attest_url": "https://core.integ.euid.eu/attest", + "cloud_encryption_keys_metadata_path": "https://core.integ.euid.eu/cloud_encryption_keys/retrieve", "optout_api_uri": "https://optout.integ.euid.eu/optout/replicate", "optout_s3_folder": "optout/", "allow_legacy_api": false diff --git a/scripts/aws/conf/integ-uid2-config.json b/scripts/aws/conf/integ-uid2-config.json index a7272a26a..11b0d6048 100644 --- a/scripts/aws/conf/integ-uid2-config.json +++ b/scripts/aws/conf/integ-uid2-config.json @@ -8,6 +8,7 @@ "services_metadata_path": "https://core-integ.uidapi.com/services/refresh", "service_links_metadata_path": "https://core-integ.uidapi.com/service_links/refresh", "optout_metadata_path": "https://optout-integ.uidapi.com/optout/refresh", + "cloud_encryption_keys_metadata_path": "https://core-integ.uidapi.com/cloud_encryption_keys/retrieve", "core_attest_url": "https://core-integ.uidapi.com/attest", "optout_api_uri": "https://optout-integ.uidapi.com/optout/replicate", "optout_s3_folder": "uid-optout-integ/", diff --git a/scripts/aws/conf/prod-euid-config.json b/scripts/aws/conf/prod-euid-config.json index 0fbf5d69c..7f7ecdef4 100644 --- a/scripts/aws/conf/prod-euid-config.json +++ b/scripts/aws/conf/prod-euid-config.json @@ -9,6 +9,7 @@ "services_metadata_path": "https://core.prod.euid.eu/services/refresh", "service_links_metadata_path": "https://core.prod.euid.eu/service_links/refresh", "optout_metadata_path": "https://optout.prod.euid.eu/optout/refresh", + "cloud_encryption_keys_metadata_path": "https://core.prod.euid.eu/cloud_encryption_keys/retrieve", "core_attest_url": "https://core.prod.euid.eu/attest", "core_api_token": "your-api-token", "optout_s3_path_compat": false, diff --git a/scripts/aws/conf/prod-uid2-config.json b/scripts/aws/conf/prod-uid2-config.json index 5da450033..8dd9b63d5 100644 --- a/scripts/aws/conf/prod-uid2-config.json +++ b/scripts/aws/conf/prod-uid2-config.json @@ -8,6 +8,7 @@ "salts_metadata_path": "https://core-prod.uidapi.com/salt/refresh", "services_metadata_path": "https://core-prod.uidapi.com/services/refresh", "service_links_metadata_path": "https://core-prod.uidapi.com/service_links/refresh", + "cloud_encryption_keys_metadata_path": "https://core-prod.uidapi.com/cloud_encryption_keys/retrieve", "optout_metadata_path": "https://optout-prod.uidapi.com/optout/refresh", "core_attest_url": "https://core-prod.uidapi.com/attest", "core_api_token": "your-api-token", diff --git a/scripts/azure-cc/conf/integ-uid2-config.json b/scripts/azure-cc/conf/integ-uid2-config.json index 2cd4be5c3..f75f3717d 100644 --- a/scripts/azure-cc/conf/integ-uid2-config.json +++ b/scripts/azure-cc/conf/integ-uid2-config.json @@ -7,6 +7,7 @@ "salts_metadata_path": "https://core-integ.uidapi.com/salt/refresh", "services_metadata_path": "https://core-integ.uidapi.com/services/refresh", "service_links_metadata_path": "https://core-integ.uidapi.com/service_links/refresh", + "cloud_encryption_keys_metadata_path": "https://core-integ.uidapi.com/cloud_encryption_keys/retrieve", "optout_metadata_path": "https://optout-integ.uidapi.com/optout/refresh", "core_attest_url": "https://core-integ.uidapi.com/attest", "optout_api_uri": "https://optout-integ.uidapi.com/optout/replicate", diff --git a/scripts/azure-cc/conf/prod-uid2-config.json b/scripts/azure-cc/conf/prod-uid2-config.json index 02e2cde20..3703337a4 100644 --- a/scripts/azure-cc/conf/prod-uid2-config.json +++ b/scripts/azure-cc/conf/prod-uid2-config.json @@ -7,6 +7,7 @@ "salts_metadata_path": "https://core-prod.uidapi.com/salt/refresh", "services_metadata_path": "https://core-prod.uidapi.com/services/refresh", "service_links_metadata_path": "https://core-prod.uidapi.com/service_links/refresh", + "cloud_encryption_keys_metadata_path": "https://core-prod.uidapi.com/cloud_encryption_keys/retrieve", "optout_metadata_path": "https://optout-prod.uidapi.com/optout/refresh", "core_attest_url": "https://core-prod.uidapi.com/attest", "optout_api_uri": "https://optout-prod.uidapi.com/optout/replicate", diff --git a/scripts/gcp/conf/integ-config.json b/scripts/gcp/conf/integ-config.json index d3fb9e9ff..09e93dfcc 100644 --- a/scripts/gcp/conf/integ-config.json +++ b/scripts/gcp/conf/integ-config.json @@ -5,6 +5,7 @@ "salts_metadata_path": "https://core-integ.uidapi.com/salt/refresh", "core_attest_url": "https://core-integ.uidapi.com/attest", "optout_metadata_path": "https://optout-integ.uidapi.com/optout/refresh", + "cloud_encryption_keys_metadata_path": "https://core-integ.uidapi.com/cloud_encryption_keys/retrieve", "optout_api_uri": "https://optout-integ.uidapi.com/optout/replicate", "optout_s3_folder": "optout-v2/", "optout_inmem_cache": true, diff --git a/scripts/gcp/conf/prod-config.json b/scripts/gcp/conf/prod-config.json index 836349c19..32bc0085c 100644 --- a/scripts/gcp/conf/prod-config.json +++ b/scripts/gcp/conf/prod-config.json @@ -6,6 +6,7 @@ "core_attest_url": "https://core-prod.uidapi.com/attest", "optout_metadata_path": "https://optout-prod.uidapi.com/optout/refresh", "optout_api_uri": "https://optout-prod.uidapi.com/optout/replicate", + "cloud_encryption_keys_metadata_path": "https://core-prod.uidapi.com/cloud_encryption_keys/retrieve", "optout_s3_folder": "optout-v2/", "optout_inmem_cache": true, "identity_token_expires_after_seconds": 14400, diff --git a/src/main/java/com/uid2/operator/Main.java b/src/main/java/com/uid2/operator/Main.java index 6b88d715b..04643d21e 100644 --- a/src/main/java/com/uid2/operator/Main.java +++ b/src/main/java/com/uid2/operator/Main.java @@ -8,6 +8,7 @@ import com.uid2.operator.monitoring.IStatsCollectorQueue; import com.uid2.operator.monitoring.OperatorMetrics; import com.uid2.operator.monitoring.StatsCollectorVerticle; +import com.uid2.operator.reader.RotatingCloudEncryptionKeyApiProvider; import com.uid2.operator.service.SecureLinkValidatorService; import com.uid2.operator.service.ShutdownService; import com.uid2.operator.vertx.Endpoints; @@ -22,6 +23,7 @@ import com.uid2.shared.jmx.AdminApi; import com.uid2.shared.optout.OptOutCloudSync; import com.uid2.shared.store.CloudPath; +import com.uid2.shared.store.EncryptedRotatingSaltProvider; import com.uid2.shared.store.RotatingSaltProvider; import com.uid2.shared.store.reader.*; import com.uid2.shared.store.scope.GlobalScope; @@ -81,6 +83,7 @@ public class Main { private IStatsCollectorQueue _statsCollectorQueue; private RotatingServiceStore serviceProvider; private RotatingServiceLinkStore serviceLinkProvider; + private RotatingCloudEncryptionKeyApiProvider cloudEncryptionKeyProvider; public Main(Vertx vertx, JsonObject config) throws Exception { this.vertx = vertx; @@ -132,17 +135,19 @@ public Main(Vertx vertx, JsonObject config) throws Exception { this.fsOptOut = configureCloudOptOutStore(); } + String cloudEncryptionKeyMdPath = this.config.getString(Const.Config.CloudEncryptionKeysMetadataPathProp); + this.cloudEncryptionKeyProvider = new RotatingCloudEncryptionKeyApiProvider(fsStores, new GlobalScope(new CloudPath(cloudEncryptionKeyMdPath))); String sitesMdPath = this.config.getString(Const.Config.SitesMetadataPathProp); String keypairMdPath = this.config.getString(Const.Config.ClientSideKeypairsMetadataPathProp); - this.clientSideKeypairProvider = new RotatingClientSideKeypairStore(fsStores, new GlobalScope(new CloudPath(keypairMdPath))); + this.clientSideKeypairProvider = new RotatingClientSideKeypairStore(fsStores, new GlobalScope(new CloudPath(keypairMdPath)), cloudEncryptionKeyProvider); String clientsMdPath = this.config.getString(Const.Config.ClientsMetadataPathProp); - this.clientKeyProvider = new RotatingClientKeyProvider(fsStores, new GlobalScope(new CloudPath(clientsMdPath))); + this.clientKeyProvider = new RotatingClientKeyProvider(fsStores, new GlobalScope(new CloudPath(clientsMdPath)), cloudEncryptionKeyProvider); String keysetKeysMdPath = this.config.getString(Const.Config.KeysetKeysMetadataPathProp); - this.keysetKeyStore = new RotatingKeysetKeyStore(fsStores, new GlobalScope(new CloudPath(keysetKeysMdPath))); + this.keysetKeyStore = new RotatingKeysetKeyStore(fsStores, new GlobalScope(new CloudPath(keysetKeysMdPath)), cloudEncryptionKeyProvider); String keysetMdPath = this.config.getString(Const.Config.KeysetsMetadataPathProp); - this.keysetProvider = new RotatingKeysetProvider(fsStores, new GlobalScope(new CloudPath(keysetMdPath))); + this.keysetProvider = new RotatingKeysetProvider(fsStores, new GlobalScope(new CloudPath(keysetMdPath)), cloudEncryptionKeyProvider); String saltsMdPath = this.config.getString(Const.Config.SaltsMetadataPathProp); - this.saltProvider = new RotatingSaltProvider(fsStores, saltsMdPath); + this.saltProvider = new EncryptedRotatingSaltProvider(fsStores, cloudEncryptionKeyProvider, new GlobalScope(new CloudPath(saltsMdPath))); this.optOutStore = new CloudSyncOptOutStore(vertx, fsLocal, this.config, operatorKey, Clock.systemUTC()); if (this.validateServiceLinks) { @@ -152,7 +157,7 @@ public Main(Vertx vertx, JsonObject config) throws Exception { this.serviceLinkProvider = new RotatingServiceLinkStore(fsStores, new GlobalScope(new CloudPath(serviceLinkMdPath))); } - this.siteProvider = clientSideTokenGenerate ? new RotatingSiteStore(fsStores, new GlobalScope(new CloudPath(sitesMdPath))) : null; + this.siteProvider = clientSideTokenGenerate ? new RotatingSiteStore(fsStores, new GlobalScope(new CloudPath(sitesMdPath)), cloudEncryptionKeyProvider) : null; if (useStorageMock && coreAttestUrl == null) { if (clientSideTokenGenerate) { @@ -163,6 +168,7 @@ public Main(Vertx vertx, JsonObject config) throws Exception { this.saltProvider.loadContent(); this.keysetProvider.loadContent(); this.keysetKeyStore.loadContent(); + this.cloudEncryptionKeyProvider.loadContent(); if (this.validateServiceLinks) { this.serviceProvider.loadContent(); @@ -302,6 +308,8 @@ private void run() throws Exception { private Future createStoreVerticles() throws Exception { // load metadatas for the first time + cloudEncryptionKeyProvider.loadContent(); + if (clientSideTokenGenerate) { siteProvider.getMetadata(); clientSideKeypairProvider.getMetadata(); @@ -330,6 +338,7 @@ private Future createStoreVerticles() throws Exception { fs.add(createAndDeployRotatingStoreVerticle("auth", clientKeyProvider, "auth_refresh_ms")); fs.add(createAndDeployRotatingStoreVerticle("keyset", keysetProvider, "keyset_refresh_ms")); fs.add(createAndDeployRotatingStoreVerticle("keysetkey", keysetKeyStore, "keysetkey_refresh_ms")); + fs.add(createAndDeployRotatingStoreVerticle("cloud_encryption_keys", cloudEncryptionKeyProvider, "cloud_encryption_keys_refresh_ms")); fs.add(createAndDeployRotatingStoreVerticle("salt", saltProvider, "salt_refresh_ms")); fs.add(createAndDeployCloudSyncStoreVerticle("optout", fsOptOut, optOutCloudSync)); CompositeFuture.all(fs).onComplete(ar -> { diff --git a/src/main/java/com/uid2/operator/reader/ApiStoreReader.java b/src/main/java/com/uid2/operator/reader/ApiStoreReader.java new file mode 100644 index 000000000..fda4c4e6a --- /dev/null +++ b/src/main/java/com/uid2/operator/reader/ApiStoreReader.java @@ -0,0 +1,57 @@ +package com.uid2.operator.reader; + +import com.uid2.shared.cloud.DownloadCloudStorage; +import com.uid2.shared.store.ScopedStoreReader; +import com.uid2.shared.store.parser.Parser; +import com.uid2.shared.store.parser.ParsingResult; +import com.uid2.shared.store.scope.StoreScope; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public class ApiStoreReader extends ScopedStoreReader { + private static final Logger LOGGER = LoggerFactory.getLogger(ApiStoreReader.class); + + public ApiStoreReader(DownloadCloudStorage fileStreamProvider, StoreScope scope, Parser parser, String dataTypeName) { + super(fileStreamProvider, scope, parser, dataTypeName); + } + + + public long loadContent(JsonObject contents) throws Exception { + return loadContent(contents, dataTypeName); + } + + @Override + public long loadContent(JsonObject contents, String dataType) throws IOException { + if (contents == null) { + throw new IllegalArgumentException(String.format("No contents provided for loading data type %s, cannot load content", dataType)); + } + + try { + JsonArray dataArray = contents.getJsonArray(dataType); + if (dataArray == null) { + throw new IllegalArgumentException("No array found in the contents"); + } + + String jsonString = dataArray.toString(); + InputStream inputStream = new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)); + + ParsingResult parsed = parser.deserialize(inputStream); + latestSnapshot.set(parsed.getData()); + + final int count = parsed.getCount(); + latestEntryCount.set(count); + LOGGER.info(String.format("Loaded %d %s", count, dataType)); + return count; + } catch (Exception e) { + LOGGER.error(String.format("Unable to load %s", dataType)); + throw e; + } + } +} diff --git a/src/main/java/com/uid2/operator/reader/RotatingCloudEncryptionKeyApiProvider.java b/src/main/java/com/uid2/operator/reader/RotatingCloudEncryptionKeyApiProvider.java new file mode 100644 index 000000000..2b4d1b10d --- /dev/null +++ b/src/main/java/com/uid2/operator/reader/RotatingCloudEncryptionKeyApiProvider.java @@ -0,0 +1,32 @@ +package com.uid2.operator.reader; + +import com.uid2.shared.cloud.DownloadCloudStorage; +import com.uid2.shared.model.CloudEncryptionKey; +import com.uid2.shared.store.CloudPath; +import com.uid2.shared.store.parser.CloudEncryptionKeyParser; +import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider; +import com.uid2.shared.store.scope.StoreScope; +import io.vertx.core.json.JsonObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Instant; +import java.util.*; + +public class RotatingCloudEncryptionKeyApiProvider extends RotatingCloudEncryptionKeyProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(RotatingCloudEncryptionKeyApiProvider.class); + + public RotatingCloudEncryptionKeyApiProvider(DownloadCloudStorage fileStreamProvider, StoreScope scope) { + super(fileStreamProvider, scope, new ApiStoreReader<>(fileStreamProvider, scope, new CloudEncryptionKeyParser(), "cloud_encryption_keys")); + } + + public RotatingCloudEncryptionKeyApiProvider(DownloadCloudStorage fileStreamProvider, StoreScope scope, ApiStoreReader> reader) { + super(fileStreamProvider, scope, reader); + } + + + @Override + public long getVersion(JsonObject metadata) { + return Instant.now().getEpochSecond(); + } +} diff --git a/src/main/resources/com.uid2.core/test/cloud_encryption_keys/cloud_encryption_keys.json b/src/main/resources/com.uid2.core/test/cloud_encryption_keys/cloud_encryption_keys.json new file mode 100644 index 000000000..b875d6797 --- /dev/null +++ b/src/main/resources/com.uid2.core/test/cloud_encryption_keys/cloud_encryption_keys.json @@ -0,0 +1,73 @@ +[ { + "id" : 1, + "siteId" : 999, + "activates" : 1720641670, + "created" : 1720641670, + "secret" : "mydrCudb2PZOm01Qn0SpthltmexHUAA11Hy1m+uxjVw=" +}, { + "id" : 2, + "siteId" : 999, + "activates" : 1720728070, + "created" : 1720641670, + "secret" : "FtdslrFSsvVXOuhOWGwEI+0QTkCvM8SGZAP3k2u3PgY=" +}, { + "id" : 3, + "siteId" : 999, + "activates" : 1720814470, + "created" : 1720641670, + "secret" : "/7zO6QbKrhZKIV36G+cU9UR4hZUVg5bD+KjbczICjHw=" +}, { + "id" : 4, + "siteId" : 123, + "activates" : 1720641671, + "created" : 1720641671, + "secret" : "XjiqRlWQQJGLr7xfV1qbueKwyzt881GVohuUkQt/ht4=" +}, { + "id" : 5, + "siteId" : 123, + "activates" : 1720728071, + "created" : 1720641671, + "secret" : "QmpIf5NzO+UROjl5XjB/BmF6paefM8n6ub9B2plC9aI=" +}, { + "id" : 6, + "siteId" : 123, + "activates" : 1720814471, + "created" : 1720641671, + "secret" : "40w9UMSYxGm+KldOWOXhBGI8QgjvUUQjivtkP4VpKV8=" +}, { + "id" : 7, + "siteId" : 124, + "activates" : 1720641671, + "created" : 1720641671, + "secret" : "QdwD0kQV1BwmLRD0PH1YpqgaOrgpVTfu08o98mSZ6uE=" +}, { + "id" : 8, + "siteId" : 124, + "activates" : 1720728071, + "created" : 1720641671, + "secret" : "yCVCM/HLf9/6k+aUNrx7w17VbyfSzI8JykLQLSR+CW0=" +}, { + "id" : 9, + "siteId" : 124, + "activates" : 1720814471, + "created" : 1720641671, + "secret" : "JqHl8BrTyx9XpR2lYj/5xvUpzgnibGeomETTwF4rn1U=" +}, { + "id" : 10, + "siteId" : 127, + "activates" : 1720641671, + "created" : 1720641671, + "secret" : "JqiG1b34AvrdO3Aj6cCcjOBJMijrDzTmrR+p9ZtP2es=" +}, { + "id" : 11, + "siteId" : 127, + "activates" : 1720728072, + "created" : 1720641672, + "secret" : "lp1CyHdfc7K0aO5JGpA+Ve5Z/V5LImtGEQwCg/YB0kY=" +}, { + "id" : 12, + "siteId" : 127, + "activates" : 1720814472, + "created" : 1720641672, + "secret" : "G99rFYJF+dnSlk/xG6fuC3WNqQxTLJbDIdVyPMbGQ6s=" +} ] \ No newline at end of file diff --git a/src/main/resources/com.uid2.core/test/cloud_encryption_keys/metadata.json b/src/main/resources/com.uid2.core/test/cloud_encryption_keys/metadata.json new file mode 100644 index 000000000..af9de38c2 --- /dev/null +++ b/src/main/resources/com.uid2.core/test/cloud_encryption_keys/metadata.json @@ -0,0 +1,7 @@ +{ + "version": 1, + "generated": 1620253519, + "cloud_encryption_keys": { + "location": "/com.uid2.core/test/cloud_encryption_keys/cloud_encryption_keys.json" + } +} \ No newline at end of file diff --git a/src/test/java/com/uid2/operator/ApiStoreReaderTest.java b/src/test/java/com/uid2/operator/ApiStoreReaderTest.java new file mode 100644 index 000000000..7cd55fd1c --- /dev/null +++ b/src/test/java/com/uid2/operator/ApiStoreReaderTest.java @@ -0,0 +1,103 @@ +package com.uid2.operator; + +import com.uid2.operator.reader.ApiStoreReader; +import com.uid2.shared.cloud.DownloadCloudStorage; +import com.uid2.shared.store.CloudPath; +import com.uid2.shared.store.parser.Parser; +import com.uid2.shared.store.parser.ParsingResult; +import com.uid2.shared.store.scope.GlobalScope; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + + class ApiStoreReaderTest { + + @Mock + private DownloadCloudStorage mockStorage; + + @Mock + private Parser> mockParser; + + private final CloudPath metadataPath = new CloudPath("test/test-metadata.json"); + private final String dataType = "test-data-type"; + private final GlobalScope scope = new GlobalScope(metadataPath); + + private ApiStoreReader> reader; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + reader = new ApiStoreReader<>(mockStorage, scope, mockParser, dataType); + } + + @Test + void getMetadataPathReturnsPathFromScope() { + CloudPath actual = reader.getMetadataPath(); + assertThat(actual).isEqualTo(metadataPath); + } + + @Test + void loadContentThrowsExceptionWhenContentsAreNull() { + assertThatThrownBy(() -> reader.loadContent(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("No contents provided for loading data type"); + } + + @Test + void loadContentThrowsExceptionWhenArrayNotFound() { + JsonObject contents = new JsonObject(); + assertThatThrownBy(() -> reader.loadContent(contents)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("No array found in the contents"); + } + + @Test + void loadContentSuccessfullyLoadsData() throws Exception { + JsonObject contents = new JsonObject() + .put(dataType, new JsonArray().add("value1").add("value2")); + + List expectedData = Arrays.asList(new TestData("value1"), new TestData("value2")); + when(mockParser.deserialize(any(InputStream.class))) + .thenReturn(new ParsingResult<>(expectedData, expectedData.size())); + + long count = reader.loadContent(contents); + + assertThat(count).isEqualTo(2); + assertThat(reader.getSnapshot()).isEqualTo(expectedData); + } + + private static class TestData { + private final String value; + + TestData(String value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestData testData = (TestData) o; + return value.equals(testData.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + } + } + diff --git a/src/test/java/com/uid2/operator/RotatingCloudEncryptionKeyApiProviderTest.java b/src/test/java/com/uid2/operator/RotatingCloudEncryptionKeyApiProviderTest.java new file mode 100644 index 000000000..254276658 --- /dev/null +++ b/src/test/java/com/uid2/operator/RotatingCloudEncryptionKeyApiProviderTest.java @@ -0,0 +1,102 @@ +package com.uid2.operator; + +import com.uid2.operator.reader.ApiStoreReader; +import com.uid2.operator.reader.RotatingCloudEncryptionKeyApiProvider; +import com.uid2.shared.cloud.DownloadCloudStorage; +import com.uid2.shared.model.CloudEncryptionKey; +import com.uid2.shared.store.CloudPath; +import com.uid2.shared.store.scope.StoreScope; +import io.vertx.core.json.JsonObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class RotatingCloudEncryptionKeyApiProviderTest { + + @Mock + private DownloadCloudStorage mockFileStreamProvider; + + @Mock + private StoreScope mockScope; + + @Mock + private ApiStoreReader> mockApiStoreReader; + + private RotatingCloudEncryptionKeyApiProvider rotatingCloudEncryptionKeyApiProvider; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + rotatingCloudEncryptionKeyApiProvider = new RotatingCloudEncryptionKeyApiProvider(mockFileStreamProvider, mockScope, mockApiStoreReader); + } + + @Test + void testGetMetadata() throws Exception { + JsonObject expectedMetadata = new JsonObject().put("version", 1L); + when(mockApiStoreReader.getMetadata()).thenReturn(expectedMetadata); + + JsonObject metadata = rotatingCloudEncryptionKeyApiProvider.getMetadata(); + assertEquals(expectedMetadata, metadata); + verify(mockApiStoreReader).getMetadata(); + } + + @Test + void testGetMetadataPath() { + CloudPath expectedPath = new CloudPath("test/path"); + when(mockApiStoreReader.getMetadataPath()).thenReturn(expectedPath); + + CloudPath path = rotatingCloudEncryptionKeyApiProvider.getMetadataPath(); + assertEquals(expectedPath, path); + verify(mockApiStoreReader).getMetadataPath(); + } + + @Test + void testLoadContentWithMetadata() throws Exception { + JsonObject metadata = new JsonObject(); + when(mockApiStoreReader.loadContent(metadata, "cloud_encryption_keys")).thenReturn(1L); + + long version = rotatingCloudEncryptionKeyApiProvider.loadContent(metadata); + assertEquals(1L, version); + verify(mockApiStoreReader).loadContent(metadata, "cloud_encryption_keys"); + } + + @Test + void testGetAll() { + Map expectedKeys = new HashMap<>(); + CloudEncryptionKey key = new CloudEncryptionKey(1, 123, 1687635529, 1687808329, "secret"); + expectedKeys.put(1, key); + when(mockApiStoreReader.getSnapshot()).thenReturn(expectedKeys); + + Map keys = rotatingCloudEncryptionKeyApiProvider.getAll(); + assertEquals(expectedKeys, keys); + verify(mockApiStoreReader).getSnapshot(); + } + + @Test + void testGetAllWithNullSnapshot() { + when(mockApiStoreReader.getSnapshot()).thenReturn(null); + + Map keys = rotatingCloudEncryptionKeyApiProvider.getAll(); + assertNotNull(keys); + assertTrue(keys.isEmpty()); + verify(mockApiStoreReader).getSnapshot(); + } + + @Test + void testLoadContent() throws Exception { + JsonObject metadata = new JsonObject().put("version", 1L); + when(mockApiStoreReader.getMetadata()).thenReturn(metadata); + when(mockApiStoreReader.loadContent(metadata, "cloud_encryption_keys")).thenReturn(1L); + + rotatingCloudEncryptionKeyApiProvider.loadContent(); + verify(mockApiStoreReader).getMetadata(); + verify(mockApiStoreReader).loadContent(metadata, "cloud_encryption_keys"); + } +} \ No newline at end of file