diff --git a/pom.xml b/pom.xml index bf19bece..d364f8ec 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.uid2 uid2-shared - 7.21.7 + 7.21.12-alpha-165-SNAPSHOT ${project.groupId}:${project.artifactId} Library for all the shared uid2 operations https://github.com/IABTechLab/uid2docs @@ -68,7 +68,7 @@ com.google.cloud libraries-bom - 26.26.0 + 26.50.0 pom import @@ -186,12 +186,22 @@ com.google.auth google-auth-library-oauth2-http - 1.23.0 + 1.30.0 + + + com.google.auth + google-auth-library-credentials + 1.30.0 com.google.cloud google-cloud-logging - 3.20.6 + 3.15.12 + + + com.google.protobuf + protobuf-java + 3.25.5 com.azure diff --git a/src/main/java/com/uid2/shared/Const.java b/src/main/java/com/uid2/shared/Const.java index 865944d1..4345ab8f 100644 --- a/src/main/java/com/uid2/shared/Const.java +++ b/src/main/java/com/uid2/shared/Const.java @@ -63,7 +63,7 @@ public static class Config { public static final String ServiceLinkMetadataPathProp = "service_links_metadata_path"; public static final String SitesMetadataPathProp = "sites_metadata_path"; public static final String OperatorsMetadataPathProp = "operators_metadata_path"; - public static final String S3keysMetadataPathProp = "s3_keys_metadata_path"; + public static final String CloudEncryptionKeysMetadataPathProp = "cloud_encryption_keys_metadata_path"; public static final String SaltsMetadataPathProp = "salts_metadata_path"; public static final String OptOutMetadataPathProp = "optout_metadata_path"; public static final String CoreAttestUrlProp = "core_attest_url"; diff --git a/src/main/java/com/uid2/shared/model/S3Key.java b/src/main/java/com/uid2/shared/model/CloudEncryptionKey.java similarity index 77% rename from src/main/java/com/uid2/shared/model/S3Key.java rename to src/main/java/com/uid2/shared/model/CloudEncryptionKey.java index a23b09ee..f699acc3 100644 --- a/src/main/java/com/uid2/shared/model/S3Key.java +++ b/src/main/java/com/uid2/shared/model/CloudEncryptionKey.java @@ -7,7 +7,7 @@ import java.util.Objects; @JsonPropertyOrder({ "id", "siteId", "activates", "created", "secret" }) -public class S3Key { +public class CloudEncryptionKey { private final int id; private final int siteId; private final long activates; @@ -15,7 +15,7 @@ public class S3Key { private final String secret; @JsonCreator - public S3Key( + public CloudEncryptionKey( @JsonProperty("id") int id, @JsonProperty("site_id") int siteId, @JsonProperty("activates") long activates, @@ -52,12 +52,12 @@ public String getSecret() { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - S3Key s3Key = (S3Key) o; - return id == s3Key.id && - siteId == s3Key.siteId && - activates == s3Key.activates && - created == s3Key.created && - Objects.equals(secret, s3Key.secret); + CloudEncryptionKey cloudEncryptionKey = (CloudEncryptionKey) o; + return id == cloudEncryptionKey.id && + siteId == cloudEncryptionKey.siteId && + activates == cloudEncryptionKey.activates && + created == cloudEncryptionKey.created && + Objects.equals(secret, cloudEncryptionKey.secret); } @Override diff --git a/src/main/java/com/uid2/shared/store/EncryptedScopedStoreReader.java b/src/main/java/com/uid2/shared/store/EncryptedScopedStoreReader.java index 9b24f762..32b2b5d5 100644 --- a/src/main/java/com/uid2/shared/store/EncryptedScopedStoreReader.java +++ b/src/main/java/com/uid2/shared/store/EncryptedScopedStoreReader.java @@ -1,12 +1,11 @@ package com.uid2.shared.store; import com.uid2.shared.cloud.DownloadCloudStorage; -import com.uid2.shared.model.S3Key; +import com.uid2.shared.model.CloudEncryptionKey; import com.uid2.shared.store.parser.Parser; import com.uid2.shared.store.parser.ParsingResult; -import com.uid2.shared.store.scope.EncryptedScope; import com.uid2.shared.store.scope.StoreScope; -import com.uid2.shared.store.reader.RotatingS3KeyProvider; +import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider; import io.vertx.core.json.JsonObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,7 +14,6 @@ import com.uid2.shared.encryption.AesGcm; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Map; @@ -23,11 +21,11 @@ public class EncryptedScopedStoreReader extends ScopedStoreReader { private static final Logger LOGGER = LoggerFactory.getLogger(EncryptedScopedStoreReader.class); - private final RotatingS3KeyProvider s3KeyProvider; + private final RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider; - public EncryptedScopedStoreReader(DownloadCloudStorage fileStreamProvider, StoreScope scope, Parser parser, String dataTypeName, RotatingS3KeyProvider s3KeyProvider) { + public EncryptedScopedStoreReader(DownloadCloudStorage fileStreamProvider, StoreScope scope, Parser parser, String dataTypeName, RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider) { super(fileStreamProvider, scope, parser, dataTypeName); - this.s3KeyProvider = s3KeyProvider; + this.cloudEncryptionKeyProvider = cloudEncryptionKeyProvider; } @Override @@ -52,9 +50,9 @@ protected String getDecryptedContent(String encryptedContent) throws Exception { JsonObject json = new JsonObject(encryptedContent); int keyId = json.getInteger("key_id"); String encryptedPayload = json.getString("encrypted_payload"); - Map s3Keys = s3KeyProvider.getAll(); - S3Key decryptionKey = null; - for (S3Key key : s3Keys.values()) { + Map cloudEncryptionKeys = cloudEncryptionKeyProvider.getAll(); + CloudEncryptionKey decryptionKey = null; + for (CloudEncryptionKey key : cloudEncryptionKeys.values()) { if (key.getId() == keyId) { decryptionKey = key; break; diff --git a/src/main/java/com/uid2/shared/store/parser/CloudEncryptionKeyParser.java b/src/main/java/com/uid2/shared/store/parser/CloudEncryptionKeyParser.java new file mode 100644 index 00000000..81e7ec17 --- /dev/null +++ b/src/main/java/com/uid2/shared/store/parser/CloudEncryptionKeyParser.java @@ -0,0 +1,23 @@ +package com.uid2.shared.store.parser; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.uid2.shared.model.CloudEncryptionKey; +import com.uid2.shared.util.Mapper; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +public class CloudEncryptionKeyParser implements Parser> { + private static final ObjectMapper OBJECT_MAPPER = Mapper.getInstance(); + + @Override + public ParsingResult> deserialize(InputStream inputStream) throws IOException { + CloudEncryptionKey[] cloudEncryptionKeys = OBJECT_MAPPER.readValue(inputStream, CloudEncryptionKey[].class); + Map cloudEncryptionKeysMap = Arrays.stream(cloudEncryptionKeys) + .collect(Collectors.toMap(CloudEncryptionKey::getId, s -> s)); + return new ParsingResult<>(cloudEncryptionKeysMap, cloudEncryptionKeysMap.size()); + } +} diff --git a/src/main/java/com/uid2/shared/store/parser/S3KeyParser.java b/src/main/java/com/uid2/shared/store/parser/S3KeyParser.java deleted file mode 100644 index 029d1cc1..00000000 --- a/src/main/java/com/uid2/shared/store/parser/S3KeyParser.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.uid2.shared.store.parser; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.uid2.shared.model.S3Key; -import com.uid2.shared.util.Mapper; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Map; -import java.util.stream.Collectors; - -public class S3KeyParser implements Parser> { - private static final ObjectMapper OBJECT_MAPPER = Mapper.getInstance(); - - @Override - public ParsingResult> deserialize(InputStream inputStream) throws IOException { - S3Key[] s3Keys = OBJECT_MAPPER.readValue(inputStream, S3Key[].class); - Map s3KeysMap = Arrays.stream(s3Keys) - .collect(Collectors.toMap(S3Key::getId, s -> s)); - return new ParsingResult<>(s3KeysMap, s3KeysMap.size()); - } -} diff --git a/src/main/java/com/uid2/shared/store/reader/RotatingClientKeyProvider.java b/src/main/java/com/uid2/shared/store/reader/RotatingClientKeyProvider.java index 1be05f3e..c50b4bdb 100644 --- a/src/main/java/com/uid2/shared/store/reader/RotatingClientKeyProvider.java +++ b/src/main/java/com/uid2/shared/store/reader/RotatingClientKeyProvider.java @@ -9,7 +9,6 @@ import com.uid2.shared.store.IClientKeyProvider; import com.uid2.shared.store.ScopedStoreReader; import com.uid2.shared.store.parser.ClientParser; -import com.uid2.shared.store.scope.EncryptedScope; import com.uid2.shared.store.scope.StoreScope; import io.vertx.core.json.JsonObject; @@ -49,8 +48,8 @@ public RotatingClientKeyProvider(DownloadCloudStorage fileStreamProvider, StoreS this.authorizableStore = new AuthorizableStore<>(ClientKey.class); } - public RotatingClientKeyProvider(DownloadCloudStorage fileStreamProvider, StoreScope scope, RotatingS3KeyProvider s3KeyProvider) { - this.reader = new EncryptedScopedStoreReader<>(fileStreamProvider, scope, new ClientParser(), "auth keys", s3KeyProvider); + public RotatingClientKeyProvider(DownloadCloudStorage fileStreamProvider, StoreScope scope, RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider) { + this.reader = new EncryptedScopedStoreReader<>(fileStreamProvider, scope, new ClientParser(), "auth keys", cloudEncryptionKeyProvider); this.authorizableStore = new AuthorizableStore<>(ClientKey.class); } diff --git a/src/main/java/com/uid2/shared/store/reader/RotatingS3KeyProvider.java b/src/main/java/com/uid2/shared/store/reader/RotatingCloudEncryptionKeyProvider.java similarity index 67% rename from src/main/java/com/uid2/shared/store/reader/RotatingS3KeyProvider.java rename to src/main/java/com/uid2/shared/store/reader/RotatingCloudEncryptionKeyProvider.java index eb1cca23..3716cdd5 100644 --- a/src/main/java/com/uid2/shared/store/reader/RotatingS3KeyProvider.java +++ b/src/main/java/com/uid2/shared/store/reader/RotatingCloudEncryptionKeyProvider.java @@ -3,9 +3,9 @@ import com.uid2.shared.cloud.DownloadCloudStorage; import com.uid2.shared.store.CloudPath; import com.uid2.shared.store.ScopedStoreReader; -import com.uid2.shared.store.parser.S3KeyParser; +import com.uid2.shared.store.parser.CloudEncryptionKeyParser; import com.uid2.shared.store.scope.StoreScope; -import com.uid2.shared.model.S3Key; +import com.uid2.shared.model.CloudEncryptionKey; import io.vertx.core.json.JsonObject; import java.util.Set; @@ -23,14 +23,14 @@ import java.time.Instant; -public class RotatingS3KeyProvider implements StoreReader> { - ScopedStoreReader> reader; +public class RotatingCloudEncryptionKeyProvider implements StoreReader> { + ScopedStoreReader> reader; - private static final Logger LOGGER = LoggerFactory.getLogger(RotatingS3KeyProvider.class); - public Map> siteToKeysMap = new HashMap<>(); + private static final Logger LOGGER = LoggerFactory.getLogger(RotatingCloudEncryptionKeyProvider.class); + public Map> siteToKeysMap = new HashMap<>(); - public RotatingS3KeyProvider(DownloadCloudStorage fileStreamProvider, StoreScope scope) { - this.reader = new ScopedStoreReader<>(fileStreamProvider, scope, new S3KeyParser(), "s3encryption_keys"); + public RotatingCloudEncryptionKeyProvider(DownloadCloudStorage fileStreamProvider, StoreScope scope) { + this.reader = new ScopedStoreReader<>(fileStreamProvider, scope, new CloudEncryptionKeyParser(), "cloud_encryption_keys"); } @Override @@ -50,19 +50,19 @@ public long getVersion(JsonObject metadata) { @Override public long loadContent(JsonObject metadata) throws Exception { - long result = reader.loadContent(metadata, "s3encryption_keys"); + long result = reader.loadContent(metadata, "cloud_encryption_keys"); updateSiteToKeysMapping(); return result; } @Override - public Map getAll() { - Map keys = reader.getSnapshot(); + public Map getAll() { + Map keys = reader.getSnapshot(); return keys != null ? keys : new HashMap<>(); } public void updateSiteToKeysMapping() { - Map allKeys = getAll(); + Map allKeys = getAll(); siteToKeysMap.clear(); allKeys.values().forEach(key -> this.siteToKeysMap @@ -85,28 +85,28 @@ public int getTotalSites() { return siteToKeysMap.size(); } - public List getKeys(int siteId) { + public List getKeys(int siteId) { //for s3 encryption keys retrieval return siteToKeysMap.getOrDefault(siteId, new ArrayList<>()); } - public Collection getKeysForSite(Integer siteId) { - Map allKeys = getAll(); + public Collection getKeysForSite(Integer siteId) { + Map allKeys = getAll(); return allKeys.values().stream() .filter(key -> key.getSiteId() == (siteId)) .collect(Collectors.toList()); } - public S3Key getEncryptionKeyForSite(Integer siteId) { + public CloudEncryptionKey getEncryptionKeyForSite(Integer siteId) { //get the youngest activated key - Collection keys = getKeysForSite(siteId); + Collection keys = getKeysForSite(siteId); long now = Instant.now().getEpochSecond(); if (keys.isEmpty()) { throw new IllegalStateException("No S3 keys available for encryption for site ID: " + siteId); } return keys.stream() .filter(key -> key.getActivates() <= now) - .max(Comparator.comparingLong(S3Key::getCreated)) + .max(Comparator.comparingLong(CloudEncryptionKey::getCreated)) .orElseThrow(() -> new IllegalStateException("No active keys found for site ID: " + siteId)); } } diff --git a/src/main/java/com/uid2/shared/store/reader/RotatingKeyAclProvider.java b/src/main/java/com/uid2/shared/store/reader/RotatingKeyAclProvider.java index 427adaf1..ea1ff333 100644 --- a/src/main/java/com/uid2/shared/store/reader/RotatingKeyAclProvider.java +++ b/src/main/java/com/uid2/shared/store/reader/RotatingKeyAclProvider.java @@ -3,7 +3,6 @@ import com.uid2.shared.auth.AclSnapshot; import com.uid2.shared.auth.EncryptionKeyAcl; import com.uid2.shared.cloud.DownloadCloudStorage; -import com.uid2.shared.cloud.ICloudStorage; import com.uid2.shared.store.CloudPath; import com.uid2.shared.store.EncryptedScopedStoreReader; import com.uid2.shared.store.IKeyAclProvider; @@ -23,8 +22,8 @@ public RotatingKeyAclProvider(DownloadCloudStorage fileStreamProvider, StoreScop this.reader = new ScopedStoreReader<>(fileStreamProvider, scope, new KeyAclParser(), "key acls"); } - public RotatingKeyAclProvider(DownloadCloudStorage fileStreamProvider, EncryptedScope scope, RotatingS3KeyProvider s3KeyProvider) { - this.reader = new EncryptedScopedStoreReader<>(fileStreamProvider, scope, new KeyAclParser(), "key acls", s3KeyProvider); + public RotatingKeyAclProvider(DownloadCloudStorage fileStreamProvider, EncryptedScope scope, RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider) { + this.reader = new EncryptedScopedStoreReader<>(fileStreamProvider, scope, new KeyAclParser(), "key acls", cloudEncryptionKeyProvider); } @Override diff --git a/src/main/java/com/uid2/shared/store/reader/RotatingKeyStore.java b/src/main/java/com/uid2/shared/store/reader/RotatingKeyStore.java index d3190cbc..17bba8f3 100644 --- a/src/main/java/com/uid2/shared/store/reader/RotatingKeyStore.java +++ b/src/main/java/com/uid2/shared/store/reader/RotatingKeyStore.java @@ -52,8 +52,8 @@ public RotatingKeyStore(DownloadCloudStorage fileStreamProvider, StoreScope scop this.reader = new ScopedStoreReader<>(fileStreamProvider, scope, new KeyParser(), "keys"); } - public RotatingKeyStore(DownloadCloudStorage fileStreamProvider, EncryptedScope scope, RotatingS3KeyProvider s3KeyProvider) { - this.reader = new EncryptedScopedStoreReader<>(fileStreamProvider, scope, new KeyParser(), "keys", s3KeyProvider); + public RotatingKeyStore(DownloadCloudStorage fileStreamProvider, EncryptedScope scope, RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider) { + this.reader = new EncryptedScopedStoreReader<>(fileStreamProvider, scope, new KeyParser(), "keys", cloudEncryptionKeyProvider); } @Override diff --git a/src/main/java/com/uid2/shared/store/reader/RotatingKeysetKeyStore.java b/src/main/java/com/uid2/shared/store/reader/RotatingKeysetKeyStore.java index 81c8ec9a..9d426733 100644 --- a/src/main/java/com/uid2/shared/store/reader/RotatingKeysetKeyStore.java +++ b/src/main/java/com/uid2/shared/store/reader/RotatingKeysetKeyStore.java @@ -7,7 +7,6 @@ import com.uid2.shared.store.KeysetKeyStoreSnapshot; import com.uid2.shared.store.ScopedStoreReader; import com.uid2.shared.store.parser.KeysetKeyParser; -import com.uid2.shared.store.scope.EncryptedScope; import com.uid2.shared.store.scope.StoreScope; import com.uid2.shared.store.EncryptedScopedStoreReader; import io.vertx.core.json.JsonObject; @@ -22,8 +21,8 @@ public RotatingKeysetKeyStore(DownloadCloudStorage fileStreamProvider, StoreScop this.reader = new ScopedStoreReader<>(fileStreamProvider, scope, new KeysetKeyParser(), "keyset_keys"); } - public RotatingKeysetKeyStore(DownloadCloudStorage fileStreamProvider, StoreScope scope, RotatingS3KeyProvider s3KeyProvider) { - this.reader = new EncryptedScopedStoreReader<>(fileStreamProvider, scope, new KeysetKeyParser(), "keyset_keys", s3KeyProvider); + public RotatingKeysetKeyStore(DownloadCloudStorage fileStreamProvider, StoreScope scope, RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider) { + this.reader = new EncryptedScopedStoreReader<>(fileStreamProvider, scope, new KeysetKeyParser(), "keyset_keys", cloudEncryptionKeyProvider); } @Override diff --git a/src/main/java/com/uid2/shared/store/reader/RotatingKeysetProvider.java b/src/main/java/com/uid2/shared/store/reader/RotatingKeysetProvider.java index 91d8f4f0..28c352af 100644 --- a/src/main/java/com/uid2/shared/store/reader/RotatingKeysetProvider.java +++ b/src/main/java/com/uid2/shared/store/reader/RotatingKeysetProvider.java @@ -7,7 +7,6 @@ import com.uid2.shared.store.EncryptedScopedStoreReader; import com.uid2.shared.store.ScopedStoreReader; import com.uid2.shared.store.parser.KeysetParser; -import com.uid2.shared.store.scope.EncryptedScope; import com.uid2.shared.store.scope.StoreScope; import io.vertx.core.json.JsonObject; @@ -21,8 +20,8 @@ public RotatingKeysetProvider(DownloadCloudStorage fileStreamProvider, StoreScop this.reader = new ScopedStoreReader<>(fileStreamProvider, scope, new KeysetParser(), "keysets"); } - public RotatingKeysetProvider(DownloadCloudStorage fileStreamProvider, StoreScope scope, RotatingS3KeyProvider s3KeyProvider) { - this.reader = new EncryptedScopedStoreReader<>(fileStreamProvider,scope,new KeysetParser(),"keysets",s3KeyProvider); + public RotatingKeysetProvider(DownloadCloudStorage fileStreamProvider, StoreScope scope, RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider) { + this.reader = new EncryptedScopedStoreReader<>(fileStreamProvider,scope,new KeysetParser(),"keysets",cloudEncryptionKeyProvider); } public KeysetSnapshot getSnapshot(Instant asOf) { diff --git a/src/main/java/com/uid2/shared/store/reader/RotatingSiteStore.java b/src/main/java/com/uid2/shared/store/reader/RotatingSiteStore.java index 8d0aeecd..7a7f6254 100644 --- a/src/main/java/com/uid2/shared/store/reader/RotatingSiteStore.java +++ b/src/main/java/com/uid2/shared/store/reader/RotatingSiteStore.java @@ -7,7 +7,6 @@ import com.uid2.shared.store.ISiteStore; import com.uid2.shared.store.ScopedStoreReader; import com.uid2.shared.store.parser.SiteParser; -import com.uid2.shared.store.scope.EncryptedScope; import com.uid2.shared.store.scope.StoreScope; import io.vertx.core.json.JsonObject; @@ -23,8 +22,8 @@ public RotatingSiteStore(DownloadCloudStorage fileStreamProvider, StoreScope sco this.reader = new ScopedStoreReader<>(fileStreamProvider, scope, new SiteParser(), "sites"); } - public RotatingSiteStore(DownloadCloudStorage fileStreamProvider, StoreScope scope, RotatingS3KeyProvider s3KeyProvider) { - this.reader = new EncryptedScopedStoreReader<>(fileStreamProvider, scope, new SiteParser(), "sites", s3KeyProvider); + public RotatingSiteStore(DownloadCloudStorage fileStreamProvider, StoreScope scope, RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider) { + this.reader = new EncryptedScopedStoreReader<>(fileStreamProvider, scope, new SiteParser(), "sites", cloudEncryptionKeyProvider); } @Override diff --git a/src/main/java/com/uid2/shared/vertx/RotatingStoreVerticle.java b/src/main/java/com/uid2/shared/vertx/RotatingStoreVerticle.java index 8b647610..c7c03511 100644 --- a/src/main/java/com/uid2/shared/vertx/RotatingStoreVerticle.java +++ b/src/main/java/com/uid2/shared/vertx/RotatingStoreVerticle.java @@ -3,7 +3,6 @@ import com.uid2.shared.health.HealthComponent; import com.uid2.shared.health.HealthManager; import com.uid2.shared.store.reader.IMetadataVersionedStore; -import com.uid2.shared.store.reader.RotatingS3KeyProvider; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.Metrics; diff --git a/src/test/java/com/uid2/shared/store/EncryptedScopedStoreReaderTest.java b/src/test/java/com/uid2/shared/store/EncryptedScopedStoreReaderTest.java index 49462f60..3ac00f0a 100644 --- a/src/test/java/com/uid2/shared/store/EncryptedScopedStoreReaderTest.java +++ b/src/test/java/com/uid2/shared/store/EncryptedScopedStoreReaderTest.java @@ -3,12 +3,11 @@ import com.google.common.collect.ImmutableList; import com.uid2.shared.cloud.InMemoryStorageMock; import com.uid2.shared.encryption.AesGcm; -import com.uid2.shared.model.S3Key; +import com.uid2.shared.model.CloudEncryptionKey; import com.uid2.shared.store.parser.Parser; import com.uid2.shared.store.parser.ParsingResult; -import com.uid2.shared.store.reader.RotatingS3KeyProvider; +import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider; import com.uid2.shared.store.scope.EncryptedScope; -import com.uid2.shared.store.scope.GlobalScope; import io.vertx.core.json.JsonObject; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -16,8 +15,6 @@ import java.io.*; import java.nio.charset.StandardCharsets; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import static com.uid2.shared.TestUtilites.toInputStream; @@ -37,22 +34,22 @@ class EncryptedScopedStoreReaderTest { )); private InMemoryStorageMock storage; private final TestDataParser parser = new TestDataParser(); - private RotatingS3KeyProvider keyProvider; + private RotatingCloudEncryptionKeyProvider keyProvider; private final int testSiteId = 123; - private S3Key encryptionKey; + private CloudEncryptionKey encryptionKey; @BeforeEach void setUp() { storage = new InMemoryStorageMock(); - keyProvider = mock(RotatingS3KeyProvider.class); + keyProvider = mock(RotatingCloudEncryptionKeyProvider.class); // Generate a valid 32-byte AES key byte[] keyBytes = new byte[32]; new Random().nextBytes(keyBytes); String base64Key = Base64.getEncoder().encodeToString(keyBytes); - encryptionKey = new S3Key(1, testSiteId, 0, 0, base64Key); + encryptionKey = new CloudEncryptionKey(1, testSiteId, 0, 0, base64Key); - Map mockKeyMap = new HashMap<>(); + Map mockKeyMap = new HashMap<>(); mockKeyMap.put(encryptionKey.getId(), encryptionKey); when(keyProvider.getAll()).thenReturn(mockKeyMap); } @@ -156,9 +153,9 @@ void testLoadWithMultipleEncryptionKeys() throws Exception { byte[] newKeyBytes = new byte[32]; new Random().nextBytes(newKeyBytes); String base64NewKey = Base64.getEncoder().encodeToString(newKeyBytes); - S3Key newKey = new S3Key(2, testSiteId, 0, 0, base64NewKey); + CloudEncryptionKey newKey = new CloudEncryptionKey(2, testSiteId, 0, 0, base64NewKey); - Map mockKeyMap = new HashMap<>(); + Map mockKeyMap = new HashMap<>(); mockKeyMap.put(encryptionKey.getId(), encryptionKey); mockKeyMap.put(newKey.getId(), newKey); when(keyProvider.getAll()).thenReturn(mockKeyMap); diff --git a/src/test/java/com/uid2/shared/store/parser/S3KeyParserTest.java b/src/test/java/com/uid2/shared/store/parser/CloudEncryptionKeyParserTest.java similarity index 83% rename from src/test/java/com/uid2/shared/store/parser/S3KeyParserTest.java rename to src/test/java/com/uid2/shared/store/parser/CloudEncryptionKeyParserTest.java index acbb6f97..42315a12 100644 --- a/src/test/java/com/uid2/shared/store/parser/S3KeyParserTest.java +++ b/src/test/java/com/uid2/shared/store/parser/CloudEncryptionKeyParserTest.java @@ -1,8 +1,7 @@ package com.uid2.shared.store.parser; -import com.uid2.shared.model.S3Key; +import com.uid2.shared.model.CloudEncryptionKey; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; @@ -15,13 +14,13 @@ import static org.junit.jupiter.api.Assertions.*; -class S3KeyParserTest { +class CloudEncryptionKeyParserTest { - private S3KeyParser parser; + private CloudEncryptionKeyParser parser; @BeforeEach void setUp() { - parser = new S3KeyParser(); + parser = new CloudEncryptionKeyParser(); } @Test @@ -35,7 +34,7 @@ void testDeserialize() throws IOException { "}]"; InputStream inputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); - ParsingResult> result = parser.deserialize(inputStream); + ParsingResult> result = parser.deserialize(inputStream); assertNotNull(result); assertEquals(3, result.getData().size()); @@ -43,21 +42,21 @@ void testDeserialize() throws IOException { assertTrue(result.getData().containsKey(2)); assertTrue(result.getData().containsKey(3)); - S3Key key1 = result.getData().get(1); + CloudEncryptionKey key1 = result.getData().get(1); assertEquals(1, key1.getId()); assertEquals(123, key1.getSiteId()); assertEquals(1687635529L, key1.getActivates()); assertEquals(1687808329L, key1.getCreated()); assertEquals("S3keySecretByteHere1", key1.getSecret()); - S3Key key2 = result.getData().get(2); + CloudEncryptionKey key2 = result.getData().get(2); assertEquals(2, key2.getId()); assertEquals(123, key2.getSiteId()); assertEquals(1687808429L, key2.getActivates()); assertEquals(1687808329L, key2.getCreated()); assertEquals("S3keySecretByteHere2", key2.getSecret()); - S3Key key3 = result.getData().get(3); + CloudEncryptionKey key3 = result.getData().get(3); assertEquals(3, key3.getId()); assertEquals(456, key3.getSiteId()); assertEquals(1687635529L, key3.getActivates()); @@ -71,7 +70,7 @@ void testDeserializeEmpty() throws IOException { String json = "[]"; InputStream inputStream = new ByteArrayInputStream(json.getBytes()); - ParsingResult> result = parser.deserialize(inputStream); + ParsingResult> result = parser.deserialize(inputStream); assertNotNull(result); assertTrue(result.getData().isEmpty()); @@ -86,10 +85,10 @@ void testDeserializeInvalidJson() { } @Test - void testS3KeySerialization() throws Exception { - S3Key s3Key = new S3Key(1, 999, 1718689091L, 1718689091L, "64bNHMpU/mjaywjOpVacFOvEIFZmbYYUsNVNVu1jJZs="); + void testCloudEncryptionKeySerialization() throws Exception { + CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, 999, 1718689091L, 1718689091L, "64bNHMpU/mjaywjOpVacFOvEIFZmbYYUsNVNVu1jJZs="); ObjectMapper mapper = new ObjectMapper(); - String jsonString = mapper.writeValueAsString(s3Key); + String jsonString = mapper.writeValueAsString(cloudEncryptionKey); String expectedJson = "{\"id\":1,\"siteId\":999,\"activates\":1718689091,\"created\":1718689091,\"secret\":\"64bNHMpU/mjaywjOpVacFOvEIFZmbYYUsNVNVu1jJZs=\"}"; assertEquals(expectedJson, jsonString); @@ -122,7 +121,7 @@ void testDeserializeEndpointResults() throws IOException { " ]"; InputStream inputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); - ParsingResult> result = parser.deserialize(inputStream); + ParsingResult> result = parser.deserialize(inputStream); assertNotNull(result); assertEquals(3, result.getCount()); @@ -131,21 +130,21 @@ void testDeserializeEndpointResults() throws IOException { assertTrue(result.getData().containsKey(2)); assertTrue(result.getData().containsKey(3)); - S3Key key1 = result.getData().get(1); + CloudEncryptionKey key1 = result.getData().get(1); assertEquals(1, key1.getId()); assertEquals(999, key1.getSiteId()); assertEquals(1720641670L, key1.getActivates()); assertEquals(1720641670L, key1.getCreated()); assertEquals("mydrCudb2PZOm01Qn0SpthltmexHUAA11Hy1m+uxjVw=", key1.getSecret()); - S3Key key2 = result.getData().get(2); + CloudEncryptionKey key2 = result.getData().get(2); assertEquals(2, key2.getId()); assertEquals(999, key2.getSiteId()); assertEquals(1720728070L, key2.getActivates()); assertEquals(1720641670L, key2.getCreated()); assertEquals("FtdslrFSsvVXOuhOWGwEI+0QTkCvM8SGZAP3k2u3PgY=", key2.getSecret()); - S3Key key3 = result.getData().get(3); + CloudEncryptionKey key3 = result.getData().get(3); assertEquals(3, key3.getId()); assertEquals(999, key3.getSiteId()); assertEquals(1720814470L, key3.getActivates()); diff --git a/src/test/java/com/uid2/shared/store/reader/RotatingCloudEncryptionKeyProviderTest.java b/src/test/java/com/uid2/shared/store/reader/RotatingCloudEncryptionKeyProviderTest.java new file mode 100644 index 00000000..f12c52ae --- /dev/null +++ b/src/test/java/com/uid2/shared/store/reader/RotatingCloudEncryptionKeyProviderTest.java @@ -0,0 +1,473 @@ +package com.uid2.shared.store.reader; + +import com.uid2.shared.cloud.DownloadCloudStorage; +import com.uid2.shared.model.CloudEncryptionKey; +import com.uid2.shared.store.CloudPath; +import com.uid2.shared.store.ScopedStoreReader; +import com.uid2.shared.store.scope.StoreScope; +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.time.Instant; +import java.util.Set; +import java.util.Map; +import java.util.List; +import java.util.HashMap; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class RotatingCloudEncryptionKeyProviderTest { + + @Mock + private DownloadCloudStorage fileStreamProvider; + + @Mock + private StoreScope scope; + + @Mock + private ScopedStoreReader> reader; + + private RotatingCloudEncryptionKeyProvider rotatingCloudEncryptionKeyProvider; + + private static final long CURRENT_TIME = Instant.now().getEpochSecond(); + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + rotatingCloudEncryptionKeyProvider = new RotatingCloudEncryptionKeyProvider(fileStreamProvider, scope); + // Inject the mock reader into the RotatingCloudEncryptionKeyProvider + rotatingCloudEncryptionKeyProvider.reader = reader; + } + + @Test + void testGetMetadata() throws Exception { + JsonObject expectedMetadata = new JsonObject().put("version", 1L); + when(reader.getMetadata()).thenReturn(expectedMetadata); + + JsonObject metadata = rotatingCloudEncryptionKeyProvider.getMetadata(); + assertEquals(expectedMetadata, metadata); + } + + @Test + void testGetMetadataPath() { + CloudPath expectedPath = new CloudPath("some/path"); + when(reader.getMetadataPath()).thenReturn(expectedPath); + + CloudPath path = rotatingCloudEncryptionKeyProvider.getMetadataPath(); + assertEquals(expectedPath, path); + } + + @Test + void testGetVersion() { + JsonObject metadata = new JsonObject().put("version", 1L); + long version = rotatingCloudEncryptionKeyProvider.getVersion(metadata); + assertEquals(1L, version); + } + + @Test + void testLoadContentWithMetadata() throws Exception { + JsonObject metadata = new JsonObject(); + when(reader.loadContent(metadata, "cloud_encryption_keys")).thenReturn(1L); + + long version = rotatingCloudEncryptionKeyProvider.loadContent(metadata); + assertEquals(1L, version); + } + + @Test + void testLoadContent() throws Exception { + JsonObject metadata = new JsonObject(); + when(reader.getMetadata()).thenReturn(metadata); + when(reader.loadContent(metadata, "cloud_encryption_keys")).thenReturn(1L); + + rotatingCloudEncryptionKeyProvider.loadContent(); + verify(reader, times(1)).getMetadata(); + verify(reader, times(1)).loadContent(metadata, "cloud_encryption_keys"); + } + + @Test + void testLoadContentEmptyArray() throws Exception { + JsonObject metadata = new JsonObject().put("keys", new JsonArray()); + when(reader.getMetadata()).thenReturn(metadata); + when(reader.loadContent(metadata, "cloud_encryption_keys")).thenReturn(0L); + + long version = rotatingCloudEncryptionKeyProvider.loadContent(metadata); + assertEquals(0L, version); + + Map keys = rotatingCloudEncryptionKeyProvider.getAll(); + assertTrue(keys.isEmpty()); + } + + @Test + void testGetAll() { + Map expectedKeys = new HashMap<>(); + CloudEncryptionKey key1 = new CloudEncryptionKey(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); + CloudEncryptionKey key2 = new CloudEncryptionKey(2, 123, 1687808429, 1687808329, "S3keySecretByteHere2"); + expectedKeys.put(1, key1); + expectedKeys.put(2, key2); + when(reader.getSnapshot()).thenReturn(expectedKeys); + + Map keys = rotatingCloudEncryptionKeyProvider.getAll(); + assertEquals(expectedKeys, keys); + } + + @Test + void testGetAllEmpty() { + when(reader.getSnapshot()).thenReturn(new HashMap<>()); + + Map keys = rotatingCloudEncryptionKeyProvider.getAll(); + assertTrue(keys.isEmpty()); + } + + @Test + void testGetAllNullSnapshot() { + when(reader.getSnapshot()).thenReturn(null); + + Map keys = rotatingCloudEncryptionKeyProvider.getAll(); + assertNotNull(keys); + assertTrue(keys.isEmpty()); + } + + @Test + void testUpdateExistingKey() throws Exception { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey existingKey = new CloudEncryptionKey(1, 123, 1687635529, 1687808329, "oldSecret"); + existingKeys.put(1, existingKey); + when(reader.getSnapshot()).thenReturn(existingKeys); + + CloudEncryptionKey updatedKey = new CloudEncryptionKey(1, 123, 1687635529, 1687808329, "newSecret"); + existingKeys.put(1, updatedKey); + + rotatingCloudEncryptionKeyProvider.getAll().put(1, updatedKey); + + verify(reader, times(1)).getSnapshot(); + assertEquals("newSecret", rotatingCloudEncryptionKeyProvider.getAll().get(1).getSecret()); + } + + @Test + void testAddNewKey() throws Exception { + Map existingKeys = new HashMap<>(); + when(reader.getSnapshot()).thenReturn(existingKeys); + + CloudEncryptionKey newKey = new CloudEncryptionKey(2, 456, 1687808429, 1687808329, "newSecret"); + existingKeys.put(2, newKey); + + rotatingCloudEncryptionKeyProvider.getAll().put(2, newKey); + + verify(reader, times(1)).getSnapshot(); + assertEquals("newSecret", rotatingCloudEncryptionKeyProvider.getAll().get(2).getSecret()); + } + + @Test + void testHandleNonExistentKey() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey existingKey = new CloudEncryptionKey(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); + existingKeys.put(1, existingKey); + when(reader.getSnapshot()).thenReturn(existingKeys); + + CloudEncryptionKey nonExistentKey = rotatingCloudEncryptionKeyProvider.getAll().get(99); + assertNull(nonExistentKey); + } + + @Test + void testGetKeysForSite() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey key1 = new CloudEncryptionKey(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); + CloudEncryptionKey key2 = new CloudEncryptionKey(2, 123, 1687808429, 1687808329, "S3keySecretByteHere2"); + CloudEncryptionKey key3 = new CloudEncryptionKey(3, 456, 1687635529, 1687808329, "S3keySecretByteHere3"); + existingKeys.put(1, key1); + existingKeys.put(2, key2); + existingKeys.put(3, key3); + when(reader.getSnapshot()).thenReturn(existingKeys); + + Collection retrievedKeys = rotatingCloudEncryptionKeyProvider.getKeysForSite(123); + assertNotNull(retrievedKeys); + assertEquals(2, retrievedKeys.size()); + assertTrue(retrievedKeys.contains(key1)); + assertTrue(retrievedKeys.contains(key2)); + + Collection noKeysRetrieved = rotatingCloudEncryptionKeyProvider.getKeysForSite(789); + assertNotNull(noKeysRetrieved); + assertTrue(noKeysRetrieved.isEmpty()); + } + + @Test + void testGetAllWithSingleKey() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey singleKey = new CloudEncryptionKey(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); + existingKeys.put(1, singleKey); + when(reader.getSnapshot()).thenReturn(existingKeys); + + Map keys = rotatingCloudEncryptionKeyProvider.getAll(); + assertEquals(1, keys.size()); + assertEquals(singleKey, keys.get(1)); + } + + @Test + void testGetEncryptionKeyForSiteWithSingleKey() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey singleKey = new CloudEncryptionKey(1, 123, CURRENT_TIME - 1000, CURRENT_TIME + 1000, "S3keySecretByteHere1"); + existingKeys.put(1, singleKey); + when(reader.getSnapshot()).thenReturn(existingKeys); + + CloudEncryptionKey retrievedKey = rotatingCloudEncryptionKeyProvider.getEncryptionKeyForSite(123); + assertNotNull(retrievedKey); + assertEquals(singleKey, retrievedKey); + } + + @Test + void testGetKeysForSiteWithSingleKey() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey singleKey = new CloudEncryptionKey(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); + existingKeys.put(1, singleKey); + when(reader.getSnapshot()).thenReturn(existingKeys); + + Collection retrievedKeys = rotatingCloudEncryptionKeyProvider.getKeysForSite(123); + assertNotNull(retrievedKeys); + assertEquals(1, retrievedKeys.size()); + assertTrue(retrievedKeys.contains(singleKey)); + } + + @Test + void testGetKeysForSiteWithMultipleKeys() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey key1 = new CloudEncryptionKey(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); + CloudEncryptionKey key2 = new CloudEncryptionKey(2, 123, 1687808429, 1687808329, "S3keySecretByteHere2"); + existingKeys.put(1, key1); + existingKeys.put(2, key2); + when(reader.getSnapshot()).thenReturn(existingKeys); + + Collection retrievedKeys = rotatingCloudEncryptionKeyProvider.getKeysForSite(123); + assertNotNull(retrievedKeys); + assertEquals(2, retrievedKeys.size()); + assertTrue(retrievedKeys.contains(key1)); + assertTrue(retrievedKeys.contains(key2)); + } + + @Test + void testGetEncryptionKeyForNonExistentSite() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey key1 = new CloudEncryptionKey(1, 123, CURRENT_TIME - 1000, CURRENT_TIME + 1000, "S3keySecretByteHere1"); + existingKeys.put(1, key1); + when(reader.getSnapshot()).thenReturn(existingKeys); + + IllegalStateException exception = assertThrows(IllegalStateException.class, + () -> rotatingCloudEncryptionKeyProvider.getEncryptionKeyForSite(456)); + + assertEquals("No S3 keys available for encryption for site ID: 456", exception.getMessage()); + } + + @Test + void testGetAllWhenReaderThrowsException() { + when(reader.getSnapshot()).thenThrow(new RuntimeException("Reader exception")); + + assertThrows(RuntimeException.class, () -> rotatingCloudEncryptionKeyProvider.getAll()); + } + + @Test + void testLoadContentWhenReaderThrowsException() throws Exception { + JsonObject metadata = new JsonObject(); + when(reader.getMetadata()).thenReturn(metadata); + when(reader.loadContent(metadata, "cloud_encryption_keys")).thenThrow(new RuntimeException("Load content exception")); + + assertThrows(RuntimeException.class, () -> rotatingCloudEncryptionKeyProvider.loadContent()); + } + + @Test + void testGetEncryptionKeyForSiteWithEmptyMap() { + Map existingKeys = new HashMap<>(); + when(reader.getSnapshot()).thenReturn(existingKeys); + assertThrows(IllegalStateException.class, () -> rotatingCloudEncryptionKeyProvider.getEncryptionKeyForSite(123)); + } + + @Test + void testGetKeysForSiteWithEmptyMap() { + Map existingKeys = new HashMap<>(); + when(reader.getSnapshot()).thenReturn(existingKeys); + Collection retrievedKeys = rotatingCloudEncryptionKeyProvider.getKeysForSite(123); + assertNotNull(retrievedKeys); + assertEquals(0, retrievedKeys.size()); + } + + @Test + void testGetEncryptionKeyForSiteWithMultipleKeysAndNonExistentSite() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey key1 = new CloudEncryptionKey(1, 123, CURRENT_TIME - 2000, CURRENT_TIME + 1000, "S3keySecretByteHere1"); + CloudEncryptionKey key2 = new CloudEncryptionKey(2, 123, CURRENT_TIME - 1000, CURRENT_TIME + 2000, "S3keySecretByteHere2"); + existingKeys.put(1, key1); + existingKeys.put(2, key2); + when(reader.getSnapshot()).thenReturn(existingKeys); + + IllegalStateException exception = assertThrows(IllegalStateException.class, + () -> rotatingCloudEncryptionKeyProvider.getEncryptionKeyForSite(456)); + + assertEquals("No S3 keys available for encryption for site ID: 456", exception.getMessage()); + } + + @Test + void testGetKeysForSiteWithMultipleKeysAndNonExistentSite() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey key1 = new CloudEncryptionKey(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); + CloudEncryptionKey key2 = new CloudEncryptionKey(2, 123, 1687808429, 1687808329, "S3keySecretByteHere2"); + existingKeys.put(1, key1); + existingKeys.put(2, key2); + when(reader.getSnapshot()).thenReturn(existingKeys); + Collection retrievedKeys = rotatingCloudEncryptionKeyProvider.getKeysForSite(456); + assertNotNull(retrievedKeys); + assertEquals(0, retrievedKeys.size()); + } + + @Test + void testUpdateSiteToKeysMapping() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey key1 = new CloudEncryptionKey(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); + CloudEncryptionKey key2 = new CloudEncryptionKey(2, 123, 1687808429, 1687808329, "S3keySecretByteHere2"); + CloudEncryptionKey key3 = new CloudEncryptionKey(3, 456, 1687635529, 1687808329, "S3keySecretByteHere3"); + existingKeys.put(1, key1); + existingKeys.put(2, key2); + existingKeys.put(3, key3); + when(reader.getSnapshot()).thenReturn(existingKeys); + + rotatingCloudEncryptionKeyProvider.updateSiteToKeysMapping(); + + // Verify the behavior that depends on siteToKeysMap + Collection site123Keys = rotatingCloudEncryptionKeyProvider.getKeysForSite(123); + Collection site456Keys = rotatingCloudEncryptionKeyProvider.getKeysForSite(456); + + assertEquals(2, site123Keys.size()); + assertTrue(site123Keys.contains(key1)); + assertTrue(site123Keys.contains(key2)); + + assertEquals(1, site456Keys.size()); + assertTrue(site456Keys.contains(key3)); + + assertEquals(2, rotatingCloudEncryptionKeyProvider.getTotalSites()); + Set allSiteIds = rotatingCloudEncryptionKeyProvider.getAllSiteIds(); + assertEquals(2, allSiteIds.size()); + assertTrue(allSiteIds.contains(123)); + assertTrue(allSiteIds.contains(456)); + } + + @Test + void testLoadContentOverride() throws Exception { + JsonObject metadata = new JsonObject(); + when(reader.getMetadata()).thenReturn(metadata); + when(reader.loadContent(metadata, "cloud_encryption_keys")).thenReturn(1L); + + rotatingCloudEncryptionKeyProvider.loadContent(); + + verify(reader, times(1)).getMetadata(); + verify(reader, times(1)).loadContent(metadata, "cloud_encryption_keys"); + } + + @Test + void testGetAllSiteIds() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey key1 = new CloudEncryptionKey(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); + CloudEncryptionKey key2 = new CloudEncryptionKey(2, 456, 1687808429, 1687808329, "S3keySecretByteHere2"); + existingKeys.put(1, key1); + existingKeys.put(2, key2); + when(reader.getSnapshot()).thenReturn(existingKeys); + + rotatingCloudEncryptionKeyProvider.updateSiteToKeysMapping(); + + Set allSiteIds = rotatingCloudEncryptionKeyProvider.getAllSiteIds(); + assertEquals(2, allSiteIds.size()); + assertTrue(allSiteIds.contains(123)); + assertTrue(allSiteIds.contains(456)); + } + + @Test + void testGetTotalSites() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey key1 = new CloudEncryptionKey(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); + CloudEncryptionKey key2 = new CloudEncryptionKey(2, 123, 1687808429, 1687808329, "S3keySecretByteHere2"); + CloudEncryptionKey key3 = new CloudEncryptionKey(3, 456, 1687635529, 1687808329, "S3keySecretByteHere3"); + existingKeys.put(1, key1); + existingKeys.put(2, key2); + existingKeys.put(3, key3); + when(reader.getSnapshot()).thenReturn(existingKeys); + + rotatingCloudEncryptionKeyProvider.updateSiteToKeysMapping(); + + int totalSites = rotatingCloudEncryptionKeyProvider.getTotalSites(); + assertEquals(2, totalSites); + } + + @Test + void testGetKeysForSiteFromMap() { + CloudEncryptionKey key1 = new CloudEncryptionKey(1, 100, 1687635529, 1687808329, "secret1"); + CloudEncryptionKey key2 = new CloudEncryptionKey(2, 100, 1687808429, 1687981229, "secret2"); + CloudEncryptionKey key3 = new CloudEncryptionKey(3, 200, 1687981329, 1688154129, "secret3"); + + Map> testMap = new HashMap<>(); + testMap.put(100, Arrays.asList(key1, key2)); + testMap.put(200, Collections.singletonList(key3)); + + rotatingCloudEncryptionKeyProvider.siteToKeysMap = testMap; + + List result1 = rotatingCloudEncryptionKeyProvider.getKeys(100); + assertEquals(2, result1.size()); + assertTrue(result1.contains(key1)); + assertTrue(result1.contains(key2)); + + List result2 = rotatingCloudEncryptionKeyProvider.getKeys(200); + assertEquals(1, result2.size()); + assertTrue(result2.contains(key3)); + + List result3 = rotatingCloudEncryptionKeyProvider.getKeys(300); + assertTrue(result3.isEmpty()); + } + + @Test + void testGetKeysForSiteFromMapWithEmptyMap() { + rotatingCloudEncryptionKeyProvider.siteToKeysMap = new HashMap<>(); + + List result = rotatingCloudEncryptionKeyProvider.getKeys(100); + assertTrue(result.isEmpty()); + } + + @Test + void testGetKeysForSiteFromMapWithNullMap() { + rotatingCloudEncryptionKeyProvider.siteToKeysMap = null; + + assertThrows(NullPointerException.class, () -> rotatingCloudEncryptionKeyProvider.getKeys(100)); + } + + @Test + void testGetEncryptionKeyForSite() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey key1 = new CloudEncryptionKey(1, 123, CURRENT_TIME - 3000, 1687808329, "S3keySecretByteHere1"); + CloudEncryptionKey key2 = new CloudEncryptionKey(2, 123, CURRENT_TIME - 2000, 1687981229, "S3keySecretByteHere2"); + CloudEncryptionKey key3 = new CloudEncryptionKey(3, 123, CURRENT_TIME - 1000, 1688154129, "S3keySecretByteHere3"); + CloudEncryptionKey key4 = new CloudEncryptionKey(4, 123, CURRENT_TIME + 1000, 1688327029, "S3keySecretByteHere4"); // Future key + existingKeys.put(1, key1); + existingKeys.put(2, key2); + existingKeys.put(3, key3); + existingKeys.put(4, key4); + when(reader.getSnapshot()).thenReturn(existingKeys); + + CloudEncryptionKey retrievedKey = rotatingCloudEncryptionKeyProvider.getEncryptionKeyForSite(123); + assertNotNull(retrievedKey); + assertEquals(key3, retrievedKey); // Should return the most recent active key + } + + @Test + void testGetEncryptionKeyForSiteWithNoActiveKeys() { + Map existingKeys = new HashMap<>(); + CloudEncryptionKey key1 = new CloudEncryptionKey(1, 123, CURRENT_TIME + 1000, 1687808329, "S3keySecretByteHere1"); + CloudEncryptionKey key2 = new CloudEncryptionKey(2, 123, CURRENT_TIME + 2000, 1687981229, "S3keySecretByteHere2"); + existingKeys.put(1, key1); + existingKeys.put(2, key2); + when(reader.getSnapshot()).thenReturn(existingKeys); + + assertThrows(IllegalStateException.class, () -> rotatingCloudEncryptionKeyProvider.getEncryptionKeyForSite(123)); + } +} diff --git a/src/test/java/com/uid2/shared/store/reader/RotatingS3KeyProviderTest.java b/src/test/java/com/uid2/shared/store/reader/RotatingS3KeyProviderTest.java deleted file mode 100644 index bdba2c7b..00000000 --- a/src/test/java/com/uid2/shared/store/reader/RotatingS3KeyProviderTest.java +++ /dev/null @@ -1,473 +0,0 @@ -package com.uid2.shared.store.reader; - -import com.uid2.shared.cloud.DownloadCloudStorage; -import com.uid2.shared.model.S3Key; -import com.uid2.shared.store.CloudPath; -import com.uid2.shared.store.ScopedStoreReader; -import com.uid2.shared.store.scope.StoreScope; -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.time.Instant; -import java.util.Set; -import java.util.Map; -import java.util.List; -import java.util.HashMap; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -public class RotatingS3KeyProviderTest { - - @Mock - private DownloadCloudStorage fileStreamProvider; - - @Mock - private StoreScope scope; - - @Mock - private ScopedStoreReader> reader; - - private RotatingS3KeyProvider rotatingS3KeyProvider; - - private static final long CURRENT_TIME = Instant.now().getEpochSecond(); - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - rotatingS3KeyProvider = new RotatingS3KeyProvider(fileStreamProvider, scope); - // Inject the mock reader into the RotatingS3KeyProvider - rotatingS3KeyProvider.reader = reader; - } - - @Test - void testGetMetadata() throws Exception { - JsonObject expectedMetadata = new JsonObject().put("version", 1L); - when(reader.getMetadata()).thenReturn(expectedMetadata); - - JsonObject metadata = rotatingS3KeyProvider.getMetadata(); - assertEquals(expectedMetadata, metadata); - } - - @Test - void testGetMetadataPath() { - CloudPath expectedPath = new CloudPath("some/path"); - when(reader.getMetadataPath()).thenReturn(expectedPath); - - CloudPath path = rotatingS3KeyProvider.getMetadataPath(); - assertEquals(expectedPath, path); - } - - @Test - void testGetVersion() { - JsonObject metadata = new JsonObject().put("version", 1L); - long version = rotatingS3KeyProvider.getVersion(metadata); - assertEquals(1L, version); - } - - @Test - void testLoadContentWithMetadata() throws Exception { - JsonObject metadata = new JsonObject(); - when(reader.loadContent(metadata, "s3encryption_keys")).thenReturn(1L); - - long version = rotatingS3KeyProvider.loadContent(metadata); - assertEquals(1L, version); - } - - @Test - void testLoadContent() throws Exception { - JsonObject metadata = new JsonObject(); - when(reader.getMetadata()).thenReturn(metadata); - when(reader.loadContent(metadata, "s3encryption_keys")).thenReturn(1L); - - rotatingS3KeyProvider.loadContent(); - verify(reader, times(1)).getMetadata(); - verify(reader, times(1)).loadContent(metadata, "s3encryption_keys"); - } - - @Test - void testLoadContentEmptyArray() throws Exception { - JsonObject metadata = new JsonObject().put("keys", new JsonArray()); - when(reader.getMetadata()).thenReturn(metadata); - when(reader.loadContent(metadata, "s3encryption_keys")).thenReturn(0L); - - long version = rotatingS3KeyProvider.loadContent(metadata); - assertEquals(0L, version); - - Map keys = rotatingS3KeyProvider.getAll(); - assertTrue(keys.isEmpty()); - } - - @Test - void testGetAll() { - Map expectedKeys = new HashMap<>(); - S3Key key1 = new S3Key(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); - S3Key key2 = new S3Key(2, 123, 1687808429, 1687808329, "S3keySecretByteHere2"); - expectedKeys.put(1, key1); - expectedKeys.put(2, key2); - when(reader.getSnapshot()).thenReturn(expectedKeys); - - Map keys = rotatingS3KeyProvider.getAll(); - assertEquals(expectedKeys, keys); - } - - @Test - void testGetAllEmpty() { - when(reader.getSnapshot()).thenReturn(new HashMap<>()); - - Map keys = rotatingS3KeyProvider.getAll(); - assertTrue(keys.isEmpty()); - } - - @Test - void testGetAllNullSnapshot() { - when(reader.getSnapshot()).thenReturn(null); - - Map keys = rotatingS3KeyProvider.getAll(); - assertNotNull(keys); - assertTrue(keys.isEmpty()); - } - - @Test - void testUpdateExistingKey() throws Exception { - Map existingKeys = new HashMap<>(); - S3Key existingKey = new S3Key(1, 123, 1687635529, 1687808329, "oldSecret"); - existingKeys.put(1, existingKey); - when(reader.getSnapshot()).thenReturn(existingKeys); - - S3Key updatedKey = new S3Key(1, 123, 1687635529, 1687808329, "newSecret"); - existingKeys.put(1, updatedKey); - - rotatingS3KeyProvider.getAll().put(1, updatedKey); - - verify(reader, times(1)).getSnapshot(); - assertEquals("newSecret", rotatingS3KeyProvider.getAll().get(1).getSecret()); - } - - @Test - void testAddNewKey() throws Exception { - Map existingKeys = new HashMap<>(); - when(reader.getSnapshot()).thenReturn(existingKeys); - - S3Key newKey = new S3Key(2, 456, 1687808429, 1687808329, "newSecret"); - existingKeys.put(2, newKey); - - rotatingS3KeyProvider.getAll().put(2, newKey); - - verify(reader, times(1)).getSnapshot(); - assertEquals("newSecret", rotatingS3KeyProvider.getAll().get(2).getSecret()); - } - - @Test - void testHandleNonExistentKey() { - Map existingKeys = new HashMap<>(); - S3Key existingKey = new S3Key(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); - existingKeys.put(1, existingKey); - when(reader.getSnapshot()).thenReturn(existingKeys); - - S3Key nonExistentKey = rotatingS3KeyProvider.getAll().get(99); - assertNull(nonExistentKey); - } - - @Test - void testGetKeysForSite() { - Map existingKeys = new HashMap<>(); - S3Key key1 = new S3Key(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); - S3Key key2 = new S3Key(2, 123, 1687808429, 1687808329, "S3keySecretByteHere2"); - S3Key key3 = new S3Key(3, 456, 1687635529, 1687808329, "S3keySecretByteHere3"); - existingKeys.put(1, key1); - existingKeys.put(2, key2); - existingKeys.put(3, key3); - when(reader.getSnapshot()).thenReturn(existingKeys); - - Collection retrievedKeys = rotatingS3KeyProvider.getKeysForSite(123); - assertNotNull(retrievedKeys); - assertEquals(2, retrievedKeys.size()); - assertTrue(retrievedKeys.contains(key1)); - assertTrue(retrievedKeys.contains(key2)); - - Collection noKeysRetrieved = rotatingS3KeyProvider.getKeysForSite(789); - assertNotNull(noKeysRetrieved); - assertTrue(noKeysRetrieved.isEmpty()); - } - - @Test - void testGetAllWithSingleKey() { - Map existingKeys = new HashMap<>(); - S3Key singleKey = new S3Key(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); - existingKeys.put(1, singleKey); - when(reader.getSnapshot()).thenReturn(existingKeys); - - Map keys = rotatingS3KeyProvider.getAll(); - assertEquals(1, keys.size()); - assertEquals(singleKey, keys.get(1)); - } - - @Test - void testGetEncryptionKeyForSiteWithSingleKey() { - Map existingKeys = new HashMap<>(); - S3Key singleKey = new S3Key(1, 123, CURRENT_TIME - 1000, CURRENT_TIME + 1000, "S3keySecretByteHere1"); - existingKeys.put(1, singleKey); - when(reader.getSnapshot()).thenReturn(existingKeys); - - S3Key retrievedKey = rotatingS3KeyProvider.getEncryptionKeyForSite(123); - assertNotNull(retrievedKey); - assertEquals(singleKey, retrievedKey); - } - - @Test - void testGetKeysForSiteWithSingleKey() { - Map existingKeys = new HashMap<>(); - S3Key singleKey = new S3Key(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); - existingKeys.put(1, singleKey); - when(reader.getSnapshot()).thenReturn(existingKeys); - - Collection retrievedKeys = rotatingS3KeyProvider.getKeysForSite(123); - assertNotNull(retrievedKeys); - assertEquals(1, retrievedKeys.size()); - assertTrue(retrievedKeys.contains(singleKey)); - } - - @Test - void testGetKeysForSiteWithMultipleKeys() { - Map existingKeys = new HashMap<>(); - S3Key key1 = new S3Key(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); - S3Key key2 = new S3Key(2, 123, 1687808429, 1687808329, "S3keySecretByteHere2"); - existingKeys.put(1, key1); - existingKeys.put(2, key2); - when(reader.getSnapshot()).thenReturn(existingKeys); - - Collection retrievedKeys = rotatingS3KeyProvider.getKeysForSite(123); - assertNotNull(retrievedKeys); - assertEquals(2, retrievedKeys.size()); - assertTrue(retrievedKeys.contains(key1)); - assertTrue(retrievedKeys.contains(key2)); - } - - @Test - void testGetEncryptionKeyForNonExistentSite() { - Map existingKeys = new HashMap<>(); - S3Key key1 = new S3Key(1, 123, CURRENT_TIME - 1000, CURRENT_TIME + 1000, "S3keySecretByteHere1"); - existingKeys.put(1, key1); - when(reader.getSnapshot()).thenReturn(existingKeys); - - IllegalStateException exception = assertThrows(IllegalStateException.class, - () -> rotatingS3KeyProvider.getEncryptionKeyForSite(456)); - - assertEquals("No S3 keys available for encryption for site ID: 456", exception.getMessage()); - } - - @Test - void testGetAllWhenReaderThrowsException() { - when(reader.getSnapshot()).thenThrow(new RuntimeException("Reader exception")); - - assertThrows(RuntimeException.class, () -> rotatingS3KeyProvider.getAll()); - } - - @Test - void testLoadContentWhenReaderThrowsException() throws Exception { - JsonObject metadata = new JsonObject(); - when(reader.getMetadata()).thenReturn(metadata); - when(reader.loadContent(metadata, "s3encryption_keys")).thenThrow(new RuntimeException("Load content exception")); - - assertThrows(RuntimeException.class, () -> rotatingS3KeyProvider.loadContent()); - } - - @Test - void testGetEncryptionKeyForSiteWithEmptyMap() { - Map existingKeys = new HashMap<>(); - when(reader.getSnapshot()).thenReturn(existingKeys); - assertThrows(IllegalStateException.class, () -> rotatingS3KeyProvider.getEncryptionKeyForSite(123)); - } - - @Test - void testGetKeysForSiteWithEmptyMap() { - Map existingKeys = new HashMap<>(); - when(reader.getSnapshot()).thenReturn(existingKeys); - Collection retrievedKeys = rotatingS3KeyProvider.getKeysForSite(123); - assertNotNull(retrievedKeys); - assertEquals(0, retrievedKeys.size()); - } - - @Test - void testGetEncryptionKeyForSiteWithMultipleKeysAndNonExistentSite() { - Map existingKeys = new HashMap<>(); - S3Key key1 = new S3Key(1, 123, CURRENT_TIME - 2000, CURRENT_TIME + 1000, "S3keySecretByteHere1"); - S3Key key2 = new S3Key(2, 123, CURRENT_TIME - 1000, CURRENT_TIME + 2000, "S3keySecretByteHere2"); - existingKeys.put(1, key1); - existingKeys.put(2, key2); - when(reader.getSnapshot()).thenReturn(existingKeys); - - IllegalStateException exception = assertThrows(IllegalStateException.class, - () -> rotatingS3KeyProvider.getEncryptionKeyForSite(456)); - - assertEquals("No S3 keys available for encryption for site ID: 456", exception.getMessage()); - } - - @Test - void testGetKeysForSiteWithMultipleKeysAndNonExistentSite() { - Map existingKeys = new HashMap<>(); - S3Key key1 = new S3Key(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); - S3Key key2 = new S3Key(2, 123, 1687808429, 1687808329, "S3keySecretByteHere2"); - existingKeys.put(1, key1); - existingKeys.put(2, key2); - when(reader.getSnapshot()).thenReturn(existingKeys); - Collection retrievedKeys = rotatingS3KeyProvider.getKeysForSite(456); - assertNotNull(retrievedKeys); - assertEquals(0, retrievedKeys.size()); - } - - @Test - void testUpdateSiteToKeysMapping() { - Map existingKeys = new HashMap<>(); - S3Key key1 = new S3Key(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); - S3Key key2 = new S3Key(2, 123, 1687808429, 1687808329, "S3keySecretByteHere2"); - S3Key key3 = new S3Key(3, 456, 1687635529, 1687808329, "S3keySecretByteHere3"); - existingKeys.put(1, key1); - existingKeys.put(2, key2); - existingKeys.put(3, key3); - when(reader.getSnapshot()).thenReturn(existingKeys); - - rotatingS3KeyProvider.updateSiteToKeysMapping(); - - // Verify the behavior that depends on siteToKeysMap - Collection site123Keys = rotatingS3KeyProvider.getKeysForSite(123); - Collection site456Keys = rotatingS3KeyProvider.getKeysForSite(456); - - assertEquals(2, site123Keys.size()); - assertTrue(site123Keys.contains(key1)); - assertTrue(site123Keys.contains(key2)); - - assertEquals(1, site456Keys.size()); - assertTrue(site456Keys.contains(key3)); - - assertEquals(2, rotatingS3KeyProvider.getTotalSites()); - Set allSiteIds = rotatingS3KeyProvider.getAllSiteIds(); - assertEquals(2, allSiteIds.size()); - assertTrue(allSiteIds.contains(123)); - assertTrue(allSiteIds.contains(456)); - } - - @Test - void testLoadContentOverride() throws Exception { - JsonObject metadata = new JsonObject(); - when(reader.getMetadata()).thenReturn(metadata); - when(reader.loadContent(metadata, "s3encryption_keys")).thenReturn(1L); - - rotatingS3KeyProvider.loadContent(); - - verify(reader, times(1)).getMetadata(); - verify(reader, times(1)).loadContent(metadata, "s3encryption_keys"); - } - - @Test - void testGetAllSiteIds() { - Map existingKeys = new HashMap<>(); - S3Key key1 = new S3Key(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); - S3Key key2 = new S3Key(2, 456, 1687808429, 1687808329, "S3keySecretByteHere2"); - existingKeys.put(1, key1); - existingKeys.put(2, key2); - when(reader.getSnapshot()).thenReturn(existingKeys); - - rotatingS3KeyProvider.updateSiteToKeysMapping(); - - Set allSiteIds = rotatingS3KeyProvider.getAllSiteIds(); - assertEquals(2, allSiteIds.size()); - assertTrue(allSiteIds.contains(123)); - assertTrue(allSiteIds.contains(456)); - } - - @Test - void testGetTotalSites() { - Map existingKeys = new HashMap<>(); - S3Key key1 = new S3Key(1, 123, 1687635529, 1687808329, "S3keySecretByteHere1"); - S3Key key2 = new S3Key(2, 123, 1687808429, 1687808329, "S3keySecretByteHere2"); - S3Key key3 = new S3Key(3, 456, 1687635529, 1687808329, "S3keySecretByteHere3"); - existingKeys.put(1, key1); - existingKeys.put(2, key2); - existingKeys.put(3, key3); - when(reader.getSnapshot()).thenReturn(existingKeys); - - rotatingS3KeyProvider.updateSiteToKeysMapping(); - - int totalSites = rotatingS3KeyProvider.getTotalSites(); - assertEquals(2, totalSites); - } - - @Test - void testGetKeysForSiteFromMap() { - S3Key key1 = new S3Key(1, 100, 1687635529, 1687808329, "secret1"); - S3Key key2 = new S3Key(2, 100, 1687808429, 1687981229, "secret2"); - S3Key key3 = new S3Key(3, 200, 1687981329, 1688154129, "secret3"); - - Map> testMap = new HashMap<>(); - testMap.put(100, Arrays.asList(key1, key2)); - testMap.put(200, Collections.singletonList(key3)); - - rotatingS3KeyProvider.siteToKeysMap = testMap; - - List result1 = rotatingS3KeyProvider.getKeys(100); - assertEquals(2, result1.size()); - assertTrue(result1.contains(key1)); - assertTrue(result1.contains(key2)); - - List result2 = rotatingS3KeyProvider.getKeys(200); - assertEquals(1, result2.size()); - assertTrue(result2.contains(key3)); - - List result3 = rotatingS3KeyProvider.getKeys(300); - assertTrue(result3.isEmpty()); - } - - @Test - void testGetKeysForSiteFromMapWithEmptyMap() { - rotatingS3KeyProvider.siteToKeysMap = new HashMap<>(); - - List result = rotatingS3KeyProvider.getKeys(100); - assertTrue(result.isEmpty()); - } - - @Test - void testGetKeysForSiteFromMapWithNullMap() { - rotatingS3KeyProvider.siteToKeysMap = null; - - assertThrows(NullPointerException.class, () -> rotatingS3KeyProvider.getKeys(100)); - } - - @Test - void testGetEncryptionKeyForSite() { - Map existingKeys = new HashMap<>(); - S3Key key1 = new S3Key(1, 123, CURRENT_TIME - 3000, 1687808329, "S3keySecretByteHere1"); - S3Key key2 = new S3Key(2, 123, CURRENT_TIME - 2000, 1687981229, "S3keySecretByteHere2"); - S3Key key3 = new S3Key(3, 123, CURRENT_TIME - 1000, 1688154129, "S3keySecretByteHere3"); - S3Key key4 = new S3Key(4, 123, CURRENT_TIME + 1000, 1688327029, "S3keySecretByteHere4"); // Future key - existingKeys.put(1, key1); - existingKeys.put(2, key2); - existingKeys.put(3, key3); - existingKeys.put(4, key4); - when(reader.getSnapshot()).thenReturn(existingKeys); - - S3Key retrievedKey = rotatingS3KeyProvider.getEncryptionKeyForSite(123); - assertNotNull(retrievedKey); - assertEquals(key3, retrievedKey); // Should return the most recent active key - } - - @Test - void testGetEncryptionKeyForSiteWithNoActiveKeys() { - Map existingKeys = new HashMap<>(); - S3Key key1 = new S3Key(1, 123, CURRENT_TIME + 1000, 1687808329, "S3keySecretByteHere1"); - S3Key key2 = new S3Key(2, 123, CURRENT_TIME + 2000, 1687981229, "S3keySecretByteHere2"); - existingKeys.put(1, key1); - existingKeys.put(2, key2); - when(reader.getSnapshot()).thenReturn(existingKeys); - - assertThrows(IllegalStateException.class, () -> rotatingS3KeyProvider.getEncryptionKeyForSite(123)); - } -}