From d128bc782ad48372811e5a2874095e5b6b2af837 Mon Sep 17 00:00:00 2001 From: Jon Chambers Date: Fri, 18 Jun 2021 13:06:10 -0400 Subject: [PATCH] Retire Postgres-backed pending account/device tables. --- .../textsecuregcm/WhisperServerService.java | 17 +- .../dynamic/DynamicConfiguration.java | 16 -- ...cationCodeStoreMigrationConfiguration.java | 51 ------ .../controllers/AccountController.java | 6 +- .../controllers/DeviceController.java | 6 +- .../storage/PendingAccounts.java | 84 ---------- .../storage/PendingAccountsManager.java | 55 ------- .../textsecuregcm/storage/PendingDevices.java | 68 -------- .../storage/PendingDevicesManager.java | 54 ------ .../StoredVerificationCodeManager.java | 29 ++++ .../storage/VerificationCodeStore.java | 93 ++++++++++- .../VerificationCodeStoreDynamoDb.java | 106 ------------ .../StoredVerificationCodeRowMapper.java | 24 --- .../textsecuregcm/workers/VacuumCommand.java | 5 - .../storage/PendingAccountsManagerTest.java | 154 ------------------ .../storage/PendingAccountsTest.java | 48 ------ .../storage/PendingDevicesManagerTest.java | 153 ----------------- .../storage/PendingDevicesTest.java | 48 ------ .../StoredVerificationCodeManagerTest.java | 62 +++++++ .../VerificationCodeStoreDynamoDbTest.java | 48 ------ .../storage/VerificationCodeStoreTest.java | 81 +++++---- .../controllers/AccountControllerTest.java | 4 +- .../controllers/DeviceControllerTest.java | 6 +- 23 files changed, 243 insertions(+), 975 deletions(-) delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicVerificationCodeStoreMigrationConfiguration.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccounts.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccountsManager.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevices.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevicesManager.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/storage/StoredVerificationCodeManager.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreDynamoDb.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/storage/mappers/StoredVerificationCodeRowMapper.java delete mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingAccountsManagerTest.java delete mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingAccountsTest.java delete mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingDevicesManagerTest.java delete mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingDevicesTest.java create mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/storage/StoredVerificationCodeManagerTest.java delete mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreDynamoDbTest.java diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 4e9097a04..3391d3a3b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -166,10 +166,6 @@ import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.storage.MigrationDeletedAccounts; import org.whispersystems.textsecuregcm.storage.MigrationRetryAccounts; import org.whispersystems.textsecuregcm.storage.MigrationRetryAccountsTableCrawler; -import org.whispersystems.textsecuregcm.storage.PendingAccounts; -import org.whispersystems.textsecuregcm.storage.PendingAccountsManager; -import org.whispersystems.textsecuregcm.storage.PendingDevices; -import org.whispersystems.textsecuregcm.storage.PendingDevicesManager; import org.whispersystems.textsecuregcm.storage.Profiles; import org.whispersystems.textsecuregcm.storage.ProfilesManager; import org.whispersystems.textsecuregcm.storage.PubSubManager; @@ -181,9 +177,10 @@ import org.whispersystems.textsecuregcm.storage.RemoteConfigsManager; import org.whispersystems.textsecuregcm.storage.ReportMessageDynamoDb; import org.whispersystems.textsecuregcm.storage.ReportMessageManager; import org.whispersystems.textsecuregcm.storage.ReservedUsernames; +import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager; import org.whispersystems.textsecuregcm.storage.Usernames; import org.whispersystems.textsecuregcm.storage.UsernamesManager; -import org.whispersystems.textsecuregcm.storage.VerificationCodeStoreDynamoDb; +import org.whispersystems.textsecuregcm.storage.VerificationCodeStore; import org.whispersystems.textsecuregcm.util.AsnManager; import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig; @@ -356,8 +353,6 @@ public class WhisperServerService extends Application getExperimentEnrollmentConfiguration( final String experimentName) { return Optional.ofNullable(experiments.get(experimentName)); @@ -109,12 +101,4 @@ public class DynamicConfiguration { public DynamicRateLimitChallengeConfiguration getRateLimitChallengeConfiguration() { return rateLimitChallenge; } - - public DynamicVerificationCodeStoreMigrationConfiguration getPendingAccountsMigrationConfiguration() { - return pendingAccountsMigration; - } - - public DynamicVerificationCodeStoreMigrationConfiguration getPendingDevicesMigrationConfiguration() { - return pendingDevicesMigration; - } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicVerificationCodeStoreMigrationConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicVerificationCodeStoreMigrationConfiguration.java deleted file mode 100644 index b1c975d70..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicVerificationCodeStoreMigrationConfiguration.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2013-2021 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.configuration.dynamic; - -import com.fasterxml.jackson.annotation.JsonProperty; -import javax.validation.constraints.NotNull; - -public class DynamicVerificationCodeStoreMigrationConfiguration { - - public enum WriteDestination { - POSTGRES, - DYNAMODB - } - - @JsonProperty - @NotNull - private WriteDestination writeDestination = WriteDestination.POSTGRES; - - @JsonProperty - private boolean readPostgres = true; - - @JsonProperty - private boolean readDynamoDb = false; - - public WriteDestination getWriteDestination() { - return writeDestination; - } - - public void setWriteDestination(final WriteDestination writeDestination) { - this.writeDestination = writeDestination; - } - - public boolean isReadPostgres() { - return readPostgres; - } - - public void setReadPostgres(final boolean readPostgres) { - this.readPostgres = readPostgres; - } - - public boolean isReadDynamoDb() { - return readDynamoDb; - } - - public void setReadDynamoDb(final boolean readDynamoDb) { - this.readDynamoDb = readDynamoDb; - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index f9600038b..b3497c51e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -76,7 +76,7 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.MessagesManager; -import org.whispersystems.textsecuregcm.storage.PendingAccountsManager; +import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager; import org.whispersystems.textsecuregcm.storage.UsernamesManager; import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.ForwardedIpUtil; @@ -112,7 +112,7 @@ public class AccountController { private static final String VERIFY_EXPERIMENT_TAG_NAME = "twilioVerify"; - private final PendingAccountsManager pendingAccounts; + private final StoredVerificationCodeManager pendingAccounts; private final AccountsManager accounts; private final UsernamesManager usernames; private final AbusiveHostRules abusiveHostRules; @@ -130,7 +130,7 @@ public class AccountController { private final TwilioVerifyExperimentEnrollmentManager verifyExperimentEnrollmentManager; - public AccountController(PendingAccountsManager pendingAccounts, + public AccountController(StoredVerificationCodeManager pendingAccounts, AccountsManager accounts, UsernamesManager usernames, AbusiveHostRules abusiveHostRules, diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java index c099833f5..e5b3eb2c0 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java @@ -24,7 +24,7 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities; import org.whispersystems.textsecuregcm.storage.MessagesManager; -import org.whispersystems.textsecuregcm.storage.PendingDevicesManager; +import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager; import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.VerificationCode; import org.whispersystems.textsecuregcm.util.ua.UnrecognizedUserAgentException; @@ -55,14 +55,14 @@ public class DeviceController { private static final int MAX_DEVICES = 6; - private final PendingDevicesManager pendingDevices; + private final StoredVerificationCodeManager pendingDevices; private final AccountsManager accounts; private final MessagesManager messages; private final RateLimiters rateLimiters; private final Map maxDeviceConfiguration; private final DirectoryQueue directoryQueue; - public DeviceController(PendingDevicesManager pendingDevices, + public DeviceController(StoredVerificationCodeManager pendingDevices, AccountsManager accounts, MessagesManager messages, DirectoryQueue directoryQueue, diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccounts.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccounts.java deleted file mode 100644 index b616cc82d..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccounts.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.textsecuregcm.storage; - -import static com.codahale.metrics.MetricRegistry.name; - -import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.SharedMetricRegistries; -import com.codahale.metrics.Timer; -import java.util.Optional; -import com.google.common.annotations.VisibleForTesting; -import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; -import org.whispersystems.textsecuregcm.storage.mappers.StoredVerificationCodeRowMapper; -import org.whispersystems.textsecuregcm.util.Constants; - -public class PendingAccounts implements VerificationCodeStore { - - private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); - private final Timer insertTimer = metricRegistry.timer(name(PendingAccounts.class, "insert" )); - private final Timer getCodeForNumberTimer = metricRegistry.timer(name(PendingAccounts.class, "getCodeForNumber")); - private final Timer removeTimer = metricRegistry.timer(name(PendingAccounts.class, "remove" )); - private final Timer vacuumTimer = metricRegistry.timer(name(PendingAccounts.class, "vacuum" )); - - private final FaultTolerantDatabase database; - - public PendingAccounts(FaultTolerantDatabase database) { - this.database = database; - this.database.getDatabase().registerRowMapper(new StoredVerificationCodeRowMapper()); - } - - @Override - public void insert(final String number, final StoredVerificationCode storedVerificationCode) { - database.use(jdbi -> jdbi.useHandle(handle -> { - try (Timer.Context ignored = insertTimer.time()) { - handle.createUpdate("INSERT INTO pending_accounts (number, verification_code, timestamp, push_code, twilio_verification_sid) " + - "VALUES (:number, :verification_code, :timestamp, :push_code, :twilio_verification_sid) " + - "ON CONFLICT(number) DO UPDATE " + - "SET verification_code = EXCLUDED.verification_code, timestamp = EXCLUDED.timestamp, push_code = EXCLUDED.push_code, twilio_verification_sid = EXCLUDED.twilio_verification_sid") - .bind("verification_code", storedVerificationCode.getCode()) - .bind("timestamp", storedVerificationCode.getTimestamp()) - .bind("number", number) - .bind("push_code", storedVerificationCode.getPushCode()) - .bind("twilio_verification_sid", storedVerificationCode.getTwilioVerificationSid().orElse(null)) - .execute(); - } - })); - } - - @Override - public Optional findForNumber(String number) { - return database.with(jdbi ->jdbi.withHandle(handle -> { - try (Timer.Context ignored = getCodeForNumberTimer.time()) { - return handle.createQuery("SELECT verification_code, timestamp, push_code, twilio_verification_sid FROM pending_accounts WHERE number = :number") - .bind("number", number) - .mapTo(StoredVerificationCode.class) - .findFirst(); - } - })); - } - - @Override - public void remove(String number) { - database.use(jdbi-> jdbi.useHandle(handle -> { - try (Timer.Context ignored = removeTimer.time()) { - handle.createUpdate("DELETE FROM pending_accounts WHERE number = :number") - .bind("number", number) - .execute(); - } - })); - } - - public void vacuum() { - database.use(jdbi -> jdbi.useHandle(handle -> { - try (Timer.Context ignored = vacuumTimer.time()) { - handle.execute("VACUUM pending_accounts"); - } - })); - } - - - -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccountsManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccountsManager.java deleted file mode 100644 index 6f2f32ee4..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccountsManager.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.textsecuregcm.storage; - -import java.util.Optional; -import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; - -public class PendingAccountsManager { - - private final PendingAccounts pendingAccounts; - private final VerificationCodeStoreDynamoDb pendingAccountsDynamoDb; - private final DynamicConfigurationManager dynamicConfigurationManager; - - public PendingAccountsManager( - final PendingAccounts pendingAccounts, - final VerificationCodeStoreDynamoDb pendingAccountsDynamoDb, - final DynamicConfigurationManager dynamicConfigurationManager) { - - this.pendingAccounts = pendingAccounts; - this.pendingAccountsDynamoDb = pendingAccountsDynamoDb; - this.dynamicConfigurationManager = dynamicConfigurationManager; - } - - public void store(String number, StoredVerificationCode code) { - switch (dynamicConfigurationManager.getConfiguration().getPendingAccountsMigrationConfiguration().getWriteDestination()) { - - case POSTGRES: - pendingAccounts.insert(number, code); - break; - - case DYNAMODB: - pendingAccountsDynamoDb.insert(number, code); - break; - } - } - - public void remove(String number) { - pendingAccounts.remove(number); - pendingAccountsDynamoDb.remove(number); - } - - public Optional getCodeForNumber(String number) { - final Optional maybeCodeFromPostgres = - dynamicConfigurationManager.getConfiguration().getPendingAccountsMigrationConfiguration().isReadPostgres() - ? pendingAccounts.findForNumber(number) - : Optional.empty(); - - return maybeCodeFromPostgres.or( - () -> dynamicConfigurationManager.getConfiguration().getPendingAccountsMigrationConfiguration().isReadDynamoDb() - ? pendingAccountsDynamoDb.findForNumber(number) - : Optional.empty()); - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevices.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevices.java deleted file mode 100644 index 1ffd8b4e1..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevices.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.textsecuregcm.storage; - -import static com.codahale.metrics.MetricRegistry.name; - -import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.SharedMetricRegistries; -import com.codahale.metrics.Timer; -import java.util.Optional; -import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; -import org.whispersystems.textsecuregcm.storage.mappers.StoredVerificationCodeRowMapper; -import org.whispersystems.textsecuregcm.util.Constants; - -public class PendingDevices implements VerificationCodeStore { - - private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); - private final Timer insertTimer = metricRegistry.timer(name(PendingDevices.class, "insert" )); - private final Timer getCodeForNumberTimer = metricRegistry.timer(name(PendingDevices.class, "getcodeForNumber")); - private final Timer removeTimer = metricRegistry.timer(name(PendingDevices.class, "remove" )); - - private final FaultTolerantDatabase database; - - public PendingDevices(FaultTolerantDatabase database) { - this.database = database; - this.database.getDatabase().registerRowMapper(new StoredVerificationCodeRowMapper()); - } - - @Override - public void insert(final String number, final StoredVerificationCode storedVerificationCode) { - database.use(jdbi ->jdbi.useHandle(handle -> { - try (Timer.Context timer = insertTimer.time()) { - handle.createUpdate("WITH upsert AS (UPDATE pending_devices SET verification_code = :verification_code, timestamp = :timestamp WHERE number = :number RETURNING *) " + - "INSERT INTO pending_devices (number, verification_code, timestamp) SELECT :number, :verification_code, :timestamp WHERE NOT EXISTS (SELECT * FROM upsert)") - .bind("number", number) - .bind("verification_code", storedVerificationCode.getCode()) - .bind("timestamp", storedVerificationCode.getTimestamp()) - .execute(); - } - })); - } - - @Override - public Optional findForNumber(String number) { - return database.with(jdbi -> jdbi.withHandle(handle -> { - try (Timer.Context timer = getCodeForNumberTimer.time()) { - return handle.createQuery("SELECT verification_code, timestamp, NULL as push_code, NULL as twilio_verification_sid FROM pending_devices WHERE number = :number") - .bind("number", number) - .mapTo(StoredVerificationCode.class) - .findFirst(); - } - })); - } - - @Override - public void remove(String number) { - database.use(jdbi -> jdbi.useHandle(handle -> { - try (Timer.Context timer = removeTimer.time()) { - handle.createUpdate("DELETE FROM pending_devices WHERE number = :number") - .bind("number", number) - .execute(); - } - })); - } - -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevicesManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevicesManager.java deleted file mode 100644 index 86f8f0716..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevicesManager.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.textsecuregcm.storage; - -import java.util.Optional; -import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; - -public class PendingDevicesManager { - - private final PendingDevices pendingDevices; - private final VerificationCodeStoreDynamoDb pendingDevicesDynamoDb; - private final DynamicConfigurationManager dynamicConfigurationManager; - - public PendingDevicesManager( - final PendingDevices pendingDevices, - final VerificationCodeStoreDynamoDb pendingDevicesDynamoDb, - final DynamicConfigurationManager dynamicConfigurationManager) { - - this.pendingDevices = pendingDevices; - this.pendingDevicesDynamoDb = pendingDevicesDynamoDb; - this.dynamicConfigurationManager = dynamicConfigurationManager; - } - - public void store(String number, StoredVerificationCode code) { - switch (dynamicConfigurationManager.getConfiguration().getPendingDevicesMigrationConfiguration().getWriteDestination()) { - case POSTGRES: - pendingDevices.insert(number, code); - break; - - case DYNAMODB: - pendingDevicesDynamoDb.insert(number, code); - break; - } - } - - public void remove(String number) { - pendingDevices.remove(number); - pendingDevicesDynamoDb.remove(number); - } - - public Optional getCodeForNumber(String number) { - final Optional maybeCodeFromPostgres = - dynamicConfigurationManager.getConfiguration().getPendingDevicesMigrationConfiguration().isReadPostgres() - ? pendingDevices.findForNumber(number) - : Optional.empty(); - - return maybeCodeFromPostgres.or( - () -> dynamicConfigurationManager.getConfiguration().getPendingDevicesMigrationConfiguration().isReadDynamoDb() - ? pendingDevicesDynamoDb.findForNumber(number) - : Optional.empty()); - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/StoredVerificationCodeManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/StoredVerificationCodeManager.java new file mode 100644 index 000000000..c4c73128c --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/StoredVerificationCodeManager.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2020 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ +package org.whispersystems.textsecuregcm.storage; + +import java.util.Optional; +import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; + +public class StoredVerificationCodeManager { + + private final VerificationCodeStore verificationCodeStore; + + public StoredVerificationCodeManager(final VerificationCodeStore verificationCodeStore) { + this.verificationCodeStore = verificationCodeStore; + } + + public void store(String number, StoredVerificationCode code) { + verificationCodeStore.insert(number, code); + } + + public void remove(String number) { + verificationCodeStore.remove(number); + } + + public Optional getCodeForNumber(String number) { + return verificationCodeStore.findForNumber(number); + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStore.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStore.java index 38b3fd532..7be0675ad 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStore.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStore.java @@ -5,14 +5,99 @@ package org.whispersystems.textsecuregcm.storage; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.annotations.VisibleForTesting; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Timer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; +import org.whispersystems.textsecuregcm.util.AttributeValues; +import org.whispersystems.textsecuregcm.util.SystemMapper; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import java.time.Instant; +import java.util.Map; import java.util.Optional; -public interface VerificationCodeStore { +import static com.codahale.metrics.MetricRegistry.name; - void insert(String number, StoredVerificationCode verificationCode); +public class VerificationCodeStore { - Optional findForNumber(String number); + private final DynamoDbClient dynamoDbClient; + private final String tableName; - void remove(String number); + private final Timer insertTimer; + private final Timer getTimer; + private final Timer removeTimer; + + @VisibleForTesting + static final String KEY_E164 = "P"; + + private static final String ATTR_STORED_CODE = "C"; + private static final String ATTR_TTL = "E"; + + private static final Logger log = LoggerFactory.getLogger(VerificationCodeStore.class); + + public VerificationCodeStore(final DynamoDbClient dynamoDbClient, final String tableName) { + this.dynamoDbClient = dynamoDbClient; + this.tableName = tableName; + + this.insertTimer = Metrics.timer(name(getClass(), "insert"), "table", tableName); + this.getTimer = Metrics.timer(name(getClass(), "get"), "table", tableName); + this.removeTimer = Metrics.timer(name(getClass(), "remove"), "table", tableName); + } + + public void insert(final String number, final StoredVerificationCode verificationCode) { + insertTimer.record(() -> { + try { + dynamoDbClient.putItem(PutItemRequest.builder() + .tableName(tableName) + .item(Map.of( + KEY_E164, AttributeValues.fromString(number), + ATTR_STORED_CODE, AttributeValues.fromString(SystemMapper.getMapper().writeValueAsString(verificationCode)), + ATTR_TTL, AttributeValues.fromLong(getExpirationTimestamp(verificationCode)))) + .build()); + } catch (final JsonProcessingException e) { + // This should never happen when writing directly to a string except in cases of serious misconfiguration, which + // would be caught by tests. + throw new AssertionError(e); + } + }); + } + + private long getExpirationTimestamp(final StoredVerificationCode storedVerificationCode) { + return Instant.ofEpochMilli(storedVerificationCode.getTimestamp()).plus(StoredVerificationCode.EXPIRATION).getEpochSecond(); + } + + public Optional findForNumber(final String number) { + return getTimer.record(() -> { + final GetItemResponse response = dynamoDbClient.getItem(GetItemRequest.builder() + .tableName(tableName) + .consistentRead(true) + .key(Map.of(KEY_E164, AttributeValues.fromString(number))) + .build()); + + try { + return response.hasItem() + ? Optional.of(SystemMapper.getMapper().readValue(response.item().get(ATTR_STORED_CODE).s(), StoredVerificationCode.class)) + : Optional.empty(); + } catch (final JsonProcessingException e) { + log.error("Failed to parse stored verification code", e); + return Optional.empty(); + } + }); + } + + public void remove(final String number) { + removeTimer.record(() -> { + dynamoDbClient.deleteItem(DeleteItemRequest.builder() + .tableName(tableName) + .key(Map.of(KEY_E164, AttributeValues.fromString(number))) + .build()); + }); + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreDynamoDb.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreDynamoDb.java deleted file mode 100644 index 338aeba08..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreDynamoDb.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2013-2021 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.storage; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.google.common.annotations.VisibleForTesting; -import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.Timer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; -import org.whispersystems.textsecuregcm.util.AttributeValues; -import org.whispersystems.textsecuregcm.util.SystemMapper; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest; -import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; -import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; -import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; -import java.time.Instant; -import java.util.Map; -import java.util.Optional; - -import static com.codahale.metrics.MetricRegistry.name; - -public class VerificationCodeStoreDynamoDb implements VerificationCodeStore { - - private final DynamoDbClient dynamoDbClient; - private final String tableName; - - private final Timer insertTimer; - private final Timer getTimer; - private final Timer removeTimer; - - @VisibleForTesting - static final String KEY_E164 = "P"; - - private static final String ATTR_STORED_CODE = "C"; - private static final String ATTR_TTL = "E"; - - private static final Logger log = LoggerFactory.getLogger(VerificationCodeStoreDynamoDb.class); - - public VerificationCodeStoreDynamoDb(final DynamoDbClient dynamoDbClient, final String tableName) { - this.dynamoDbClient = dynamoDbClient; - this.tableName = tableName; - - this.insertTimer = Metrics.timer(name(getClass(), "insert"), "table", tableName); - this.getTimer = Metrics.timer(name(getClass(), "get"), "table", tableName); - this.removeTimer = Metrics.timer(name(getClass(), "remove"), "table", tableName); - } - - @Override - public void insert(final String number, final StoredVerificationCode verificationCode) { - insertTimer.record(() -> { - try { - dynamoDbClient.putItem(PutItemRequest.builder() - .tableName(tableName) - .item(Map.of( - KEY_E164, AttributeValues.fromString(number), - ATTR_STORED_CODE, AttributeValues.fromString(SystemMapper.getMapper().writeValueAsString(verificationCode)), - ATTR_TTL, AttributeValues.fromLong(getExpirationTimestamp(verificationCode)))) - .build()); - } catch (final JsonProcessingException e) { - // This should never happen when writing directly to a string except in cases of serious misconfiguration, which - // would be caught by tests. - throw new AssertionError(e); - } - }); - } - - private long getExpirationTimestamp(final StoredVerificationCode storedVerificationCode) { - return Instant.ofEpochMilli(storedVerificationCode.getTimestamp()).plus(StoredVerificationCode.EXPIRATION).getEpochSecond(); - } - - @Override - public Optional findForNumber(final String number) { - return getTimer.record(() -> { - final GetItemResponse response = dynamoDbClient.getItem(GetItemRequest.builder() - .tableName(tableName) - .consistentRead(true) - .key(Map.of(KEY_E164, AttributeValues.fromString(number))) - .build()); - - try { - return response.hasItem() - ? Optional.of(SystemMapper.getMapper().readValue(response.item().get(ATTR_STORED_CODE).s(), StoredVerificationCode.class)) - : Optional.empty(); - } catch (final JsonProcessingException e) { - log.error("Failed to parse stored verification code", e); - return Optional.empty(); - } - }); - } - - @Override - public void remove(final String number) { - removeTimer.record(() -> { - dynamoDbClient.deleteItem(DeleteItemRequest.builder() - .tableName(tableName) - .key(Map.of(KEY_E164, AttributeValues.fromString(number))) - .build()); - }); - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/mappers/StoredVerificationCodeRowMapper.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/mappers/StoredVerificationCodeRowMapper.java deleted file mode 100644 index a427a36ec..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/mappers/StoredVerificationCodeRowMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.storage.mappers; - -import org.jdbi.v3.core.mapper.RowMapper; -import org.jdbi.v3.core.statement.StatementContext; -import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; - -import java.sql.ResultSet; -import java.sql.SQLException; - -public class StoredVerificationCodeRowMapper implements RowMapper { - - @Override - public StoredVerificationCode map(ResultSet resultSet, StatementContext ctx) throws SQLException { - return new StoredVerificationCode(resultSet.getString("verification_code"), - resultSet.getLong("timestamp"), - resultSet.getString("push_code"), - resultSet.getString("twilio_verification_sid")); - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/VacuumCommand.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/VacuumCommand.java index 5b5b7071d..855d7be3b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/workers/VacuumCommand.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/VacuumCommand.java @@ -13,7 +13,6 @@ import org.whispersystems.textsecuregcm.WhisperServerConfiguration; import org.whispersystems.textsecuregcm.configuration.DatabaseConfiguration; import org.whispersystems.textsecuregcm.storage.Accounts; import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase; -import org.whispersystems.textsecuregcm.storage.PendingAccounts; import io.dropwizard.cli.ConfiguredCommand; import io.dropwizard.setup.Bootstrap; @@ -38,14 +37,10 @@ public class VacuumCommand extends ConfiguredCommand FaultTolerantDatabase accountDatabase = new FaultTolerantDatabase("account_database_vacuum", accountJdbi, accountDbConfig.getCircuitBreakerConfiguration()); Accounts accounts = new Accounts(accountDatabase); - PendingAccounts pendingAccounts = new PendingAccounts(accountDatabase); logger.info("Vacuuming accounts..."); accounts.vacuum(); - logger.info("Vacuuming pending_accounts..."); - pendingAccounts.vacuum(); - Thread.sleep(3000); System.exit(0); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingAccountsManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingAccountsManagerTest.java deleted file mode 100644 index 6eea5c378..000000000 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingAccountsManagerTest.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2013-2021 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.storage; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicVerificationCodeStoreMigrationConfiguration; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicVerificationCodeStoreMigrationConfiguration.WriteDestination; - -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -class PendingAccountsManagerTest { - - private PendingAccounts postgresPendingAccounts; - private VerificationCodeStoreDynamoDb dynamoDbPendingAccounts; - private DynamicVerificationCodeStoreMigrationConfiguration migrationConfiguration; - - private PendingAccountsManager pendingAccountsManager; - - @BeforeEach - void setUp() { - final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class); - final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class); - migrationConfiguration = mock(DynamicVerificationCodeStoreMigrationConfiguration.class); - - when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration); - when(dynamicConfiguration.getPendingAccountsMigrationConfiguration()).thenReturn(migrationConfiguration); - - postgresPendingAccounts = mock(PendingAccounts.class); - dynamoDbPendingAccounts = mock(VerificationCodeStoreDynamoDb.class); - - pendingAccountsManager = new PendingAccountsManager(postgresPendingAccounts, dynamoDbPendingAccounts, dynamicConfigurationManager); - } - - @Test - void storePostgres() { - final String number = "+18005551234"; - final StoredVerificationCode code = mock(StoredVerificationCode.class); - - when(migrationConfiguration.getWriteDestination()).thenReturn(WriteDestination.POSTGRES); - - pendingAccountsManager.store(number, code); - - verify(postgresPendingAccounts).insert(number, code); - verify(dynamoDbPendingAccounts, never()).insert(any(), any()); - } - - @Test - void storeDynamoDb() { - final String number = "+18005551234"; - final StoredVerificationCode code = mock(StoredVerificationCode.class); - - when(migrationConfiguration.getWriteDestination()).thenReturn(WriteDestination.DYNAMODB); - - pendingAccountsManager.store(number, code); - - verify(dynamoDbPendingAccounts).insert(number, code); - verify(postgresPendingAccounts, never()).insert(any(), any()); - } - - @Test - void remove() { - final String number = "+18005551234"; - - pendingAccountsManager.remove(number); - - verify(postgresPendingAccounts).remove(number); - verify(dynamoDbPendingAccounts).remove(number); - } - - @Test - void getCodeForNumber() { - final String number = "+18005551234"; - - final StoredVerificationCode postgresCode = mock(StoredVerificationCode.class); - final StoredVerificationCode dynamoDbCode = mock(StoredVerificationCode.class); - - { - when(migrationConfiguration.isReadPostgres()).thenReturn(false); - when(migrationConfiguration.isReadDynamoDb()).thenReturn(false); - when(postgresPendingAccounts.findForNumber(number)).thenReturn(Optional.empty()); - when(dynamoDbPendingAccounts.findForNumber(number)).thenReturn(Optional.empty()); - - assertEquals(Optional.empty(), pendingAccountsManager.getCodeForNumber(number)); - - when(migrationConfiguration.isReadPostgres()).thenReturn(true); - - assertEquals(Optional.empty(), pendingAccountsManager.getCodeForNumber(number)); - - when(dynamoDbPendingAccounts.findForNumber(number)).thenReturn(Optional.of(dynamoDbCode)); - - assertEquals(Optional.empty(), pendingAccountsManager.getCodeForNumber(number)); - - when(postgresPendingAccounts.findForNumber(number)).thenReturn(Optional.of(postgresCode)); - - assertEquals(Optional.of(postgresCode), pendingAccountsManager.getCodeForNumber(number)); - } - - { - when(migrationConfiguration.isReadPostgres()).thenReturn(false); - when(migrationConfiguration.isReadDynamoDb()).thenReturn(false); - when(postgresPendingAccounts.findForNumber(number)).thenReturn(Optional.empty()); - when(dynamoDbPendingAccounts.findForNumber(number)).thenReturn(Optional.empty()); - - assertEquals(Optional.empty(), pendingAccountsManager.getCodeForNumber(number)); - - when(migrationConfiguration.isReadDynamoDb()).thenReturn(true); - - assertEquals(Optional.empty(), pendingAccountsManager.getCodeForNumber(number)); - - when(postgresPendingAccounts.findForNumber(number)).thenReturn(Optional.of(postgresCode)); - - assertEquals(Optional.empty(), pendingAccountsManager.getCodeForNumber(number)); - - when(dynamoDbPendingAccounts.findForNumber(number)).thenReturn(Optional.of(dynamoDbCode)); - - assertEquals(Optional.of(dynamoDbCode), pendingAccountsManager.getCodeForNumber(number)); - } - - { - when(migrationConfiguration.isReadPostgres()).thenReturn(false); - when(migrationConfiguration.isReadDynamoDb()).thenReturn(false); - when(postgresPendingAccounts.findForNumber(number)).thenReturn(Optional.of(postgresCode)); - when(dynamoDbPendingAccounts.findForNumber(number)).thenReturn(Optional.of(dynamoDbCode)); - - assertEquals(Optional.empty(), pendingAccountsManager.getCodeForNumber(number)); - - when(migrationConfiguration.isReadDynamoDb()).thenReturn(true); - - assertEquals(Optional.of(dynamoDbCode), pendingAccountsManager.getCodeForNumber(number)); - - when(migrationConfiguration.isReadDynamoDb()).thenReturn(false); - when(migrationConfiguration.isReadPostgres()).thenReturn(true); - - assertEquals(Optional.of(postgresCode), pendingAccountsManager.getCodeForNumber(number)); - - when(migrationConfiguration.isReadDynamoDb()).thenReturn(true); - - assertEquals(Optional.of(postgresCode), pendingAccountsManager.getCodeForNumber(number)); - } - } -} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingAccountsTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingAccountsTest.java deleted file mode 100644 index f1cba1cba..000000000 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingAccountsTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2013-2021 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.storage; - -import com.opentable.db.postgres.embedded.LiquibasePreparer; -import com.opentable.db.postgres.junit5.EmbeddedPostgresExtension; -import com.opentable.db.postgres.junit5.PreparedDbExtension; -import org.jdbi.v3.core.Jdbi; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration; -import java.sql.SQLException; -import java.sql.Statement; - -class PendingAccountsTest extends VerificationCodeStoreTest { - - @RegisterExtension - public static PreparedDbExtension db = EmbeddedPostgresExtension.preparedDatabase(LiquibasePreparer.forClasspathLocation("accountsdb.xml")); - - private PendingAccounts pendingAccounts; - - @BeforeEach - void setupAccountsDao() throws SQLException { - this.pendingAccounts = new PendingAccounts(new FaultTolerantDatabase("pending_accounts-test", Jdbi.create(db.getTestDatabase()), new CircuitBreakerConfiguration())); - - try (final Statement deleteStatement = db.getTestDatabase().getConnection().createStatement()) { - deleteStatement.execute("DELETE FROM pending_accounts"); - } - } - - @Override - protected VerificationCodeStore getVerificationCodeStore() { - return pendingAccounts; - } - - @Override - protected boolean expectNullPushCode() { - return false; - } - - @Override - protected boolean expectEmptyTwilioSid() { - return false; - } -} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingDevicesManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingDevicesManagerTest.java deleted file mode 100644 index 799aeabbb..000000000 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingDevicesManagerTest.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2013-2021 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.storage; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Optional; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicVerificationCodeStoreMigrationConfiguration; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicVerificationCodeStoreMigrationConfiguration.WriteDestination; - -class PendingDevicesManagerTest { - - private PendingDevices postgresPendingDevices; - private VerificationCodeStoreDynamoDb dynamoDbPendingDevices; - private DynamicVerificationCodeStoreMigrationConfiguration migrationConfiguration; - - private PendingDevicesManager pendingDevicesManager; - - @BeforeEach - void setUp() { - final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class); - final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class); - migrationConfiguration = mock(DynamicVerificationCodeStoreMigrationConfiguration.class); - - when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration); - when(dynamicConfiguration.getPendingDevicesMigrationConfiguration()).thenReturn(migrationConfiguration); - - postgresPendingDevices = mock(PendingDevices.class); - dynamoDbPendingDevices = mock(VerificationCodeStoreDynamoDb.class); - - pendingDevicesManager = new PendingDevicesManager(postgresPendingDevices, dynamoDbPendingDevices, dynamicConfigurationManager); - } - - @Test - void storePostgres() { - final String number = "+18005551234"; - final StoredVerificationCode code = mock(StoredVerificationCode.class); - - when(migrationConfiguration.getWriteDestination()).thenReturn(WriteDestination.POSTGRES); - - pendingDevicesManager.store(number, code); - - verify(postgresPendingDevices).insert(number, code); - verify(dynamoDbPendingDevices, never()).insert(any(), any()); - } - - @Test - void storeDynamoDb() { - final String number = "+18005551234"; - final StoredVerificationCode code = mock(StoredVerificationCode.class); - - when(migrationConfiguration.getWriteDestination()).thenReturn(WriteDestination.DYNAMODB); - - pendingDevicesManager.store(number, code); - - verify(dynamoDbPendingDevices).insert(number, code); - verify(postgresPendingDevices, never()).insert(any(), any()); - } - - @Test - void remove() { - final String number = "+18005551234"; - - pendingDevicesManager.remove(number); - - verify(postgresPendingDevices).remove(number); - verify(dynamoDbPendingDevices).remove(number); - } - - @Test - void getCodeForNumber() { - final String number = "+18005551234"; - - final StoredVerificationCode postgresCode = mock(StoredVerificationCode.class); - final StoredVerificationCode dynamoDbCode = mock(StoredVerificationCode.class); - - { - when(migrationConfiguration.isReadPostgres()).thenReturn(false); - when(migrationConfiguration.isReadDynamoDb()).thenReturn(false); - when(postgresPendingDevices.findForNumber(number)).thenReturn(Optional.empty()); - when(dynamoDbPendingDevices.findForNumber(number)).thenReturn(Optional.empty()); - - assertEquals(Optional.empty(), pendingDevicesManager.getCodeForNumber(number)); - - when(migrationConfiguration.isReadPostgres()).thenReturn(true); - - assertEquals(Optional.empty(), pendingDevicesManager.getCodeForNumber(number)); - - when(dynamoDbPendingDevices.findForNumber(number)).thenReturn(Optional.of(dynamoDbCode)); - - assertEquals(Optional.empty(), pendingDevicesManager.getCodeForNumber(number)); - - when(postgresPendingDevices.findForNumber(number)).thenReturn(Optional.of(postgresCode)); - - assertEquals(Optional.of(postgresCode), pendingDevicesManager.getCodeForNumber(number)); - } - - { - when(migrationConfiguration.isReadPostgres()).thenReturn(false); - when(migrationConfiguration.isReadDynamoDb()).thenReturn(false); - when(postgresPendingDevices.findForNumber(number)).thenReturn(Optional.empty()); - when(dynamoDbPendingDevices.findForNumber(number)).thenReturn(Optional.empty()); - - assertEquals(Optional.empty(), pendingDevicesManager.getCodeForNumber(number)); - - when(migrationConfiguration.isReadDynamoDb()).thenReturn(true); - - assertEquals(Optional.empty(), pendingDevicesManager.getCodeForNumber(number)); - - when(postgresPendingDevices.findForNumber(number)).thenReturn(Optional.of(postgresCode)); - - assertEquals(Optional.empty(), pendingDevicesManager.getCodeForNumber(number)); - - when(dynamoDbPendingDevices.findForNumber(number)).thenReturn(Optional.of(dynamoDbCode)); - - assertEquals(Optional.of(dynamoDbCode), pendingDevicesManager.getCodeForNumber(number)); - } - - { - when(migrationConfiguration.isReadPostgres()).thenReturn(false); - when(migrationConfiguration.isReadDynamoDb()).thenReturn(false); - when(postgresPendingDevices.findForNumber(number)).thenReturn(Optional.of(postgresCode)); - when(dynamoDbPendingDevices.findForNumber(number)).thenReturn(Optional.of(dynamoDbCode)); - - assertEquals(Optional.empty(), pendingDevicesManager.getCodeForNumber(number)); - - when(migrationConfiguration.isReadDynamoDb()).thenReturn(true); - - assertEquals(Optional.of(dynamoDbCode), pendingDevicesManager.getCodeForNumber(number)); - - when(migrationConfiguration.isReadDynamoDb()).thenReturn(false); - when(migrationConfiguration.isReadPostgres()).thenReturn(true); - - assertEquals(Optional.of(postgresCode), pendingDevicesManager.getCodeForNumber(number)); - - when(migrationConfiguration.isReadDynamoDb()).thenReturn(true); - - assertEquals(Optional.of(postgresCode), pendingDevicesManager.getCodeForNumber(number)); - } - } -} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingDevicesTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingDevicesTest.java deleted file mode 100644 index 887bc1c2c..000000000 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/PendingDevicesTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2013-2021 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.storage; - -import com.opentable.db.postgres.embedded.LiquibasePreparer; -import com.opentable.db.postgres.junit5.EmbeddedPostgresExtension; -import com.opentable.db.postgres.junit5.PreparedDbExtension; -import org.jdbi.v3.core.Jdbi; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration; -import java.sql.SQLException; -import java.sql.Statement; - -public class PendingDevicesTest extends VerificationCodeStoreTest { - - @RegisterExtension - public static PreparedDbExtension db = EmbeddedPostgresExtension.preparedDatabase(LiquibasePreparer.forClasspathLocation("accountsdb.xml")); - - private PendingDevices pendingDevices; - - @BeforeEach - public void setupAccountsDao() throws SQLException { - this.pendingDevices = new PendingDevices(new FaultTolerantDatabase("peding_devices-test", Jdbi.create(db.getTestDatabase()), new CircuitBreakerConfiguration())); - - try (final Statement deleteStatement = db.getTestDatabase().getConnection().createStatement()) { - deleteStatement.execute("DELETE FROM pending_devices"); - } - } - - @Override - protected VerificationCodeStore getVerificationCodeStore() { - return pendingDevices; - } - - @Override - protected boolean expectNullPushCode() { - return true; - } - - @Override - protected boolean expectEmptyTwilioSid() { - return true; - } -} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/StoredVerificationCodeManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/StoredVerificationCodeManagerTest.java new file mode 100644 index 000000000..9636d9783 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/StoredVerificationCodeManagerTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013-2021 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; + +class StoredVerificationCodeManagerTest { + + private VerificationCodeStore verificationCodeStore; + + private StoredVerificationCodeManager storedVerificationCodeManager; + + @BeforeEach + void setUp() { + verificationCodeStore = mock(VerificationCodeStore.class); + + storedVerificationCodeManager = new StoredVerificationCodeManager(verificationCodeStore); + } + + @Test + void store() { + final String number = "+18005551234"; + final StoredVerificationCode code = mock(StoredVerificationCode.class); + + storedVerificationCodeManager.store(number, code); + + verify(verificationCodeStore).insert(number, code); + } + + @Test + void remove() { + final String number = "+18005551234"; + + storedVerificationCodeManager.remove(number); + + verify(verificationCodeStore).remove(number); + } + + @Test + void getCodeForNumber() { + final String number = "+18005551234"; + + when(verificationCodeStore.findForNumber(number)).thenReturn(Optional.empty()); + assertEquals(Optional.empty(), storedVerificationCodeManager.getCodeForNumber(number)); + + final StoredVerificationCode storedVerificationCode = mock(StoredVerificationCode.class); + + when(verificationCodeStore.findForNumber(number)).thenReturn(Optional.of(storedVerificationCode)); + assertEquals(Optional.of(storedVerificationCode), storedVerificationCodeManager.getCodeForNumber(number)); + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreDynamoDbTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreDynamoDbTest.java deleted file mode 100644 index 82d6d192f..000000000 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreDynamoDbTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2013-2021 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.storage; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.RegisterExtension; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; - -class VerificationCodeStoreDynamoDbTest extends VerificationCodeStoreTest { - - private VerificationCodeStoreDynamoDb verificationCodeStore; - - private static final String TABLE_NAME = "verification_code_test"; - - @RegisterExtension - static final DynamoDbExtension DYNAMO_DB_EXTENSION = DynamoDbExtension.builder() - .tableName(TABLE_NAME) - .hashKey(VerificationCodeStoreDynamoDb.KEY_E164) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(VerificationCodeStoreDynamoDb.KEY_E164) - .attributeType(ScalarAttributeType.S) - .build()) - .build(); - - @BeforeEach - void setUp() { - verificationCodeStore = new VerificationCodeStoreDynamoDb(DYNAMO_DB_EXTENSION.getDynamoDbClient(), TABLE_NAME); - } - - @Override - protected VerificationCodeStore getVerificationCodeStore() { - return verificationCodeStore; - } - - @Override - protected boolean expectNullPushCode() { - return false; - } - - @Override - protected boolean expectEmptyTwilioSid() { - return false; - } -} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreTest.java index a91cfa36a..b3fb86e56 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreTest.java @@ -5,76 +5,87 @@ package org.whispersystems.textsecuregcm.storage; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; +import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; +import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; +import java.util.Objects; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -abstract class VerificationCodeStoreTest { +class VerificationCodeStoreTest { + private VerificationCodeStore verificationCodeStore; + + private static final String TABLE_NAME = "verification_code_test"; + private static final String PHONE_NUMBER = "+14151112222"; - protected abstract VerificationCodeStore getVerificationCodeStore(); + @RegisterExtension + static final DynamoDbExtension DYNAMO_DB_EXTENSION = DynamoDbExtension.builder() + .tableName(TABLE_NAME) + .hashKey(VerificationCodeStore.KEY_E164) + .attributeDefinition(AttributeDefinition.builder() + .attributeName(VerificationCodeStore.KEY_E164) + .attributeType(ScalarAttributeType.S) + .build()) + .build(); - protected abstract boolean expectNullPushCode(); - - protected abstract boolean expectEmptyTwilioSid(); + @BeforeEach + void setUp() { + verificationCodeStore = new VerificationCodeStore(DYNAMO_DB_EXTENSION.getDynamoDbClient(), TABLE_NAME); + } @Test void testStoreAndFind() { - assertEquals(Optional.empty(), getVerificationCodeStore().findForNumber(PHONE_NUMBER)); + assertEquals(Optional.empty(), verificationCodeStore.findForNumber(PHONE_NUMBER)); final StoredVerificationCode originalCode = new StoredVerificationCode("1234", 1111, "abcd", "0987"); final StoredVerificationCode secondCode = new StoredVerificationCode("5678", 2222, "efgh", "7890"); - getVerificationCodeStore().insert(PHONE_NUMBER, originalCode); - + verificationCodeStore.insert(PHONE_NUMBER, originalCode); { - final Optional maybeRetrievedCode = getVerificationCodeStore().findForNumber(PHONE_NUMBER); + final Optional maybeCode = verificationCodeStore.findForNumber(PHONE_NUMBER); - assertTrue(maybeRetrievedCode.isPresent()); - compareStoredVerificationCode(originalCode, maybeRetrievedCode.get()); + assertTrue(maybeCode.isPresent()); + assertTrue(storedVerificationCodesAreEqual(originalCode, maybeCode.get())); } - getVerificationCodeStore().insert(PHONE_NUMBER, secondCode); - + verificationCodeStore.insert(PHONE_NUMBER, secondCode); { - final Optional maybeRetrievedCode = getVerificationCodeStore().findForNumber(PHONE_NUMBER); + final Optional maybeCode = verificationCodeStore.findForNumber(PHONE_NUMBER); - assertTrue(maybeRetrievedCode.isPresent()); - compareStoredVerificationCode(secondCode, maybeRetrievedCode.get()); + assertTrue(maybeCode.isPresent()); + assertTrue(storedVerificationCodesAreEqual(secondCode, maybeCode.get())); } } @Test void testRemove() { - assertEquals(Optional.empty(), getVerificationCodeStore().findForNumber(PHONE_NUMBER)); + assertEquals(Optional.empty(), verificationCodeStore.findForNumber(PHONE_NUMBER)); - getVerificationCodeStore().insert(PHONE_NUMBER, new StoredVerificationCode("1234", 1111, "abcd", "0987")); - assertTrue(getVerificationCodeStore().findForNumber(PHONE_NUMBER).isPresent()); + verificationCodeStore.insert(PHONE_NUMBER, new StoredVerificationCode("1234", 1111, "abcd", "0987")); + assertTrue(verificationCodeStore.findForNumber(PHONE_NUMBER).isPresent()); - getVerificationCodeStore().remove(PHONE_NUMBER); - assertFalse(getVerificationCodeStore().findForNumber(PHONE_NUMBER).isPresent()); + verificationCodeStore.remove(PHONE_NUMBER); + assertFalse(verificationCodeStore.findForNumber(PHONE_NUMBER).isPresent()); } - private void compareStoredVerificationCode(final StoredVerificationCode original, final StoredVerificationCode retrieved) { - assertEquals(original.getCode(), retrieved.getCode()); - assertEquals(original.getTimestamp(), retrieved.getTimestamp()); - - if (expectNullPushCode()) { - assertNull(retrieved.getPushCode()); - } else { - assertEquals(original.getPushCode(), retrieved.getPushCode()); + private static boolean storedVerificationCodesAreEqual(final StoredVerificationCode first, final StoredVerificationCode second) { + if (first == null && second == null) { + return true; + } else if (first == null || second == null) { + return false; } - if (expectEmptyTwilioSid()) { - assertEquals(Optional.empty(), retrieved.getTwilioVerificationSid()); - } else { - assertEquals(original.getTwilioVerificationSid(), retrieved.getTwilioVerificationSid()); - } + return Objects.equals(first.getCode(), second.getCode()) && + first.getTimestamp() == second.getTimestamp() && + Objects.equals(first.getPushCode(), second.getPushCode()) && + Objects.equals(first.getTwilioVerificationSid(), second.getTwilioVerificationSid()); } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java index ec7e1a8e2..cfbcf3521 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java @@ -76,7 +76,7 @@ import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.MessagesManager; -import org.whispersystems.textsecuregcm.storage.PendingAccountsManager; +import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager; import org.whispersystems.textsecuregcm.storage.UsernamesManager; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.Hex; @@ -107,7 +107,7 @@ class AccountControllerTest { private static final String VALID_CAPTCHA_TOKEN = "valid_token"; private static final String INVALID_CAPTCHA_TOKEN = "invalid_token"; - private static PendingAccountsManager pendingAccountsManager = mock(PendingAccountsManager.class); + private static StoredVerificationCodeManager pendingAccountsManager = mock(StoredVerificationCodeManager.class); private static AccountsManager accountsManager = mock(AccountsManager.class); private static AbusiveHostRules abusiveHostRules = mock(AbusiveHostRules.class); private static RateLimiters rateLimiters = mock(RateLimiters.class); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java index 21e69ac59..2c984cc98 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java @@ -46,7 +46,7 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities; import org.whispersystems.textsecuregcm.storage.MessagesManager; -import org.whispersystems.textsecuregcm.storage.PendingDevicesManager; +import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.VerificationCode; @@ -54,7 +54,7 @@ import org.whispersystems.textsecuregcm.util.VerificationCode; public class DeviceControllerTest { @Path("/v1/devices") static class DumbVerificationDeviceController extends DeviceController { - public DumbVerificationDeviceController(PendingDevicesManager pendingDevices, + public DumbVerificationDeviceController(StoredVerificationCodeManager pendingDevices, AccountsManager accounts, MessagesManager messages, DirectoryQueue cdsSender, @@ -70,7 +70,7 @@ public class DeviceControllerTest { } } - private PendingDevicesManager pendingDevicesManager = mock(PendingDevicesManager.class); + private StoredVerificationCodeManager pendingDevicesManager = mock(StoredVerificationCodeManager.class); private AccountsManager accountsManager = mock(AccountsManager.class ); private MessagesManager messagesManager = mock(MessagesManager.class); private DirectoryQueue directoryQueue = mock(DirectoryQueue.class);