diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/CommandDependencies.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/CommandDependencies.java new file mode 100644 index 000000000..320b3b557 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/CommandDependencies.java @@ -0,0 +1,202 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.workers; + +import static com.codahale.metrics.MetricRegistry.name; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.InstanceProfileCredentialsProvider; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; +import com.fasterxml.jackson.databind.DeserializationFeature; +import io.dropwizard.setup.Environment; +import io.lettuce.core.resource.ClientResources; +import java.io.IOException; +import java.security.cert.CertificateException; +import java.time.Clock; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.whispersystems.textsecuregcm.WhisperServerConfiguration; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; +import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; +import org.whispersystems.textsecuregcm.controllers.SecureBackupController; +import org.whispersystems.textsecuregcm.controllers.SecureStorageController; +import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; +import org.whispersystems.textsecuregcm.push.ClientPresenceManager; +import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; +import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient; +import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; +import org.whispersystems.textsecuregcm.sqs.DirectoryQueue; +import org.whispersystems.textsecuregcm.storage.Accounts; +import org.whispersystems.textsecuregcm.storage.AccountsManager; +import org.whispersystems.textsecuregcm.storage.DeletedAccounts; +import org.whispersystems.textsecuregcm.storage.DeletedAccountsManager; +import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; +import org.whispersystems.textsecuregcm.storage.Keys; +import org.whispersystems.textsecuregcm.storage.MessagesCache; +import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb; +import org.whispersystems.textsecuregcm.storage.MessagesManager; +import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers; +import org.whispersystems.textsecuregcm.storage.Profiles; +import org.whispersystems.textsecuregcm.storage.ProfilesManager; +import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswords; +import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager; +import org.whispersystems.textsecuregcm.storage.ReportMessageDynamoDb; +import org.whispersystems.textsecuregcm.storage.ReportMessageManager; +import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager; +import org.whispersystems.textsecuregcm.storage.VerificationCodeStore; +import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig; +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + +/** + * Construct utilities commonly used by worker commands + */ +record CommandDependencies( + AccountsManager accountsManager, + ProfilesManager profilesManager, + ReportMessageManager reportMessageManager, + MessagesManager messagesManager, + DeletedAccountsManager deletedAccountsManager, + StoredVerificationCodeManager pendingAccountsManager, + ClientPresenceManager clientPresenceManager, + Keys keys) { + + static CommandDependencies build( + final String name, + final Environment environment, + final WhisperServerConfiguration configuration) throws IOException, CertificateException { + Clock clock = Clock.systemUTC(); + + environment.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + ClientResources redisClusterClientResources = ClientResources.builder().build(); + + FaultTolerantRedisCluster cacheCluster = new FaultTolerantRedisCluster("main_cache_cluster", + configuration.getCacheClusterConfiguration(), redisClusterClientResources); + + ExecutorService keyspaceNotificationDispatchExecutor = environment.lifecycle() + .executorService(name(name, "keyspaceNotification-%d")).maxThreads(4).build(); + ExecutorService messageDeletionExecutor = environment.lifecycle() + .executorService(name(name, "messageDeletion-%d")).maxThreads(4).build(); + ExecutorService backupServiceExecutor = environment.lifecycle() + .executorService(name(name, "backupService-%d")).maxThreads(8).minThreads(1).build(); + ExecutorService storageServiceExecutor = environment.lifecycle() + .executorService(name(name, "storageService-%d")).maxThreads(8).minThreads(1).build(); + + ExternalServiceCredentialsGenerator backupCredentialsGenerator = SecureBackupController.credentialsGenerator( + configuration.getSecureBackupServiceConfiguration()); + ExternalServiceCredentialsGenerator storageCredentialsGenerator = SecureStorageController.credentialsGenerator( + configuration.getSecureStorageServiceConfiguration()); + + DynamicConfigurationManager dynamicConfigurationManager = new DynamicConfigurationManager<>( + configuration.getAppConfig().getApplication(), configuration.getAppConfig().getEnvironment(), + configuration.getAppConfig().getConfigurationName(), DynamicConfiguration.class); + dynamicConfigurationManager.start(); + + ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager( + dynamicConfigurationManager); + + DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient( + configuration.getDynamoDbClientConfiguration(), + software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); + + DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client( + configuration.getDynamoDbClientConfiguration(), + software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); + + AmazonDynamoDB deletedAccountsLockDynamoDbClient = AmazonDynamoDBClientBuilder.standard() + .withRegion(configuration.getDynamoDbClientConfiguration().getRegion()) + .withClientConfiguration(new ClientConfiguration().withClientExecutionTimeout( + ((int) configuration.getDynamoDbClientConfiguration().getClientExecutionTimeout() + .toMillis())) + .withRequestTimeout( + (int) configuration.getDynamoDbClientConfiguration().getClientRequestTimeout() + .toMillis())) + .withCredentials(InstanceProfileCredentialsProvider.getInstance()) + .build(); + + DeletedAccounts deletedAccounts = new DeletedAccounts(dynamoDbClient, + configuration.getDynamoDbTables().getDeletedAccounts().getTableName(), + configuration.getDynamoDbTables().getDeletedAccounts().getNeedsReconciliationIndexName()); + VerificationCodeStore pendingAccounts = new VerificationCodeStore(dynamoDbClient, + configuration.getDynamoDbTables().getPendingAccounts().getTableName()); + RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords( + configuration.getDynamoDbTables().getRegistrationRecovery().getTableName(), + configuration.getDynamoDbTables().getRegistrationRecovery().getExpiration(), + dynamoDbClient, + dynamoDbAsyncClient + ); + + RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager = new RegistrationRecoveryPasswordsManager( + registrationRecoveryPasswords); + + Accounts accounts = new Accounts( + dynamoDbClient, + dynamoDbAsyncClient, + configuration.getDynamoDbTables().getAccounts().getTableName(), + configuration.getDynamoDbTables().getAccounts().getPhoneNumberTableName(), + configuration.getDynamoDbTables().getAccounts().getPhoneNumberIdentifierTableName(), + configuration.getDynamoDbTables().getAccounts().getUsernamesTableName(), + configuration.getDynamoDbTables().getAccounts().getScanPageSize()); + PhoneNumberIdentifiers phoneNumberIdentifiers = new PhoneNumberIdentifiers(dynamoDbClient, + configuration.getDynamoDbTables().getPhoneNumberIdentifiers().getTableName()); + Profiles profiles = new Profiles(dynamoDbClient, dynamoDbAsyncClient, + configuration.getDynamoDbTables().getProfiles().getTableName()); + Keys keys = new Keys(dynamoDbClient, + configuration.getDynamoDbTables().getKeys().getTableName()); + MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(dynamoDbClient, dynamoDbAsyncClient, + configuration.getDynamoDbTables().getMessages().getTableName(), + configuration.getDynamoDbTables().getMessages().getExpiration(), + messageDeletionExecutor); + FaultTolerantRedisCluster messageInsertCacheCluster = new FaultTolerantRedisCluster("message_insert_cluster", + configuration.getMessageCacheConfiguration().getRedisClusterConfiguration(), redisClusterClientResources); + FaultTolerantRedisCluster messageReadDeleteCluster = new FaultTolerantRedisCluster("message_read_delete_cluster", + configuration.getMessageCacheConfiguration().getRedisClusterConfiguration(), redisClusterClientResources); + FaultTolerantRedisCluster clientPresenceCluster = new FaultTolerantRedisCluster("client_presence_cluster", + configuration.getClientPresenceClusterConfiguration(), redisClusterClientResources); + FaultTolerantRedisCluster rateLimitersCluster = new FaultTolerantRedisCluster("rate_limiters", + configuration.getRateLimitersCluster(), redisClusterClientResources); + SecureBackupClient secureBackupClient = new SecureBackupClient(backupCredentialsGenerator, backupServiceExecutor, + configuration.getSecureBackupServiceConfiguration()); + SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, + storageServiceExecutor, configuration.getSecureStorageServiceConfiguration()); + ClientPresenceManager clientPresenceManager = new ClientPresenceManager(clientPresenceCluster, + Executors.newSingleThreadScheduledExecutor(), keyspaceNotificationDispatchExecutor); + MessagesCache messagesCache = new MessagesCache(messageInsertCacheCluster, messageReadDeleteCluster, + Clock.systemUTC(), keyspaceNotificationDispatchExecutor, messageDeletionExecutor); + DirectoryQueue directoryQueue = new DirectoryQueue( + configuration.getDirectoryConfiguration().getSqsConfiguration()); + ProfilesManager profilesManager = new ProfilesManager(profiles, cacheCluster); + ReportMessageDynamoDb reportMessageDynamoDb = new ReportMessageDynamoDb(dynamoDbClient, + configuration.getDynamoDbTables().getReportMessage().getTableName(), + configuration.getReportMessageConfiguration().getReportTtl()); + ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster, + configuration.getReportMessageConfiguration().getCounterTtl()); + MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, + reportMessageManager, messageDeletionExecutor); + DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, + deletedAccountsLockDynamoDbClient, + configuration.getDynamoDbTables().getDeletedAccountsLock().getTableName()); + StoredVerificationCodeManager pendingAccountsManager = new StoredVerificationCodeManager(pendingAccounts); + AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster, + deletedAccountsManager, directoryQueue, keys, messagesManager, profilesManager, + pendingAccountsManager, secureStorageClient, secureBackupClient, clientPresenceManager, + experimentEnrollmentManager, registrationRecoveryPasswordsManager, clock); + + return new CommandDependencies( + accountsManager, + profilesManager, + reportMessageManager, + messagesManager, + deletedAccountsManager, + pendingAccountsManager, + clientPresenceManager, + keys + ); + } + +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/DeleteUserCommand.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/DeleteUserCommand.java index cad61691c..e7d63a5dd 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/workers/DeleteUserCommand.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/DeleteUserCommand.java @@ -5,60 +5,19 @@ package org.whispersystems.textsecuregcm.workers; -import static com.codahale.metrics.MetricRegistry.name; - -import com.amazonaws.ClientConfiguration; -import com.amazonaws.auth.InstanceProfileCredentialsProvider; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; import com.fasterxml.jackson.databind.DeserializationFeature; import io.dropwizard.Application; import io.dropwizard.cli.EnvironmentCommand; import io.dropwizard.setup.Environment; -import io.lettuce.core.resource.ClientResources; -import java.time.Clock; import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.WhisperServerConfiguration; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; -import org.whispersystems.textsecuregcm.controllers.SecureBackupController; -import org.whispersystems.textsecuregcm.controllers.SecureStorageController; -import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; -import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; -import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient; -import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; -import org.whispersystems.textsecuregcm.sqs.DirectoryQueue; import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.storage.Accounts; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager.DeletionReason; -import org.whispersystems.textsecuregcm.storage.DeletedAccounts; -import org.whispersystems.textsecuregcm.storage.DeletedAccountsManager; -import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; -import org.whispersystems.textsecuregcm.storage.Keys; -import org.whispersystems.textsecuregcm.storage.MessagesCache; -import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb; -import org.whispersystems.textsecuregcm.storage.MessagesManager; -import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers; -import org.whispersystems.textsecuregcm.storage.Profiles; -import org.whispersystems.textsecuregcm.storage.ProfilesManager; -import org.whispersystems.textsecuregcm.storage.ProhibitedUsernames; -import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswords; -import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager; -import org.whispersystems.textsecuregcm.storage.ReportMessageDynamoDb; -import org.whispersystems.textsecuregcm.storage.ReportMessageManager; -import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager; -import org.whispersystems.textsecuregcm.storage.VerificationCodeStore; -import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig; -import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; public class DeleteUserCommand extends EnvironmentCommand { @@ -84,130 +43,16 @@ public class DeleteUserCommand extends EnvironmentCommand dynamicConfigurationManager = new DynamicConfigurationManager<>( - configuration.getAppConfig().getApplication(), configuration.getAppConfig().getEnvironment(), - configuration.getAppConfig().getConfigurationName(), DynamicConfiguration.class); - dynamicConfigurationManager.start(); - - ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager( - dynamicConfigurationManager); - - DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient( - configuration.getDynamoDbClientConfiguration(), - software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); - - DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client( - configuration.getDynamoDbClientConfiguration(), - software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); - - AmazonDynamoDB deletedAccountsLockDynamoDbClient = AmazonDynamoDBClientBuilder.standard() - .withRegion(configuration.getDynamoDbClientConfiguration().getRegion()) - .withClientConfiguration(new ClientConfiguration().withClientExecutionTimeout( - ((int) configuration.getDynamoDbClientConfiguration().getClientExecutionTimeout() - .toMillis())) - .withRequestTimeout( - (int) configuration.getDynamoDbClientConfiguration().getClientRequestTimeout() - .toMillis())) - .withCredentials(InstanceProfileCredentialsProvider.getInstance()) - .build(); - - DeletedAccounts deletedAccounts = new DeletedAccounts(dynamoDbClient, - configuration.getDynamoDbTables().getDeletedAccounts().getTableName(), - configuration.getDynamoDbTables().getDeletedAccounts().getNeedsReconciliationIndexName()); - VerificationCodeStore pendingAccounts = new VerificationCodeStore(dynamoDbClient, - configuration.getDynamoDbTables().getPendingAccounts().getTableName()); - RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords( - configuration.getDynamoDbTables().getRegistrationRecovery().getTableName(), - configuration.getDynamoDbTables().getRegistrationRecovery().getExpiration(), - dynamoDbClient, - dynamoDbAsyncClient - ); - - RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager = new RegistrationRecoveryPasswordsManager(registrationRecoveryPasswords); - - Accounts accounts = new Accounts( - dynamoDbClient, - dynamoDbAsyncClient, - configuration.getDynamoDbTables().getAccounts().getTableName(), - configuration.getDynamoDbTables().getAccounts().getPhoneNumberTableName(), - configuration.getDynamoDbTables().getAccounts().getPhoneNumberIdentifierTableName(), - configuration.getDynamoDbTables().getAccounts().getUsernamesTableName(), - configuration.getDynamoDbTables().getAccounts().getScanPageSize()); - PhoneNumberIdentifiers phoneNumberIdentifiers = new PhoneNumberIdentifiers(dynamoDbClient, - configuration.getDynamoDbTables().getPhoneNumberIdentifiers().getTableName()); - Profiles profiles = new Profiles(dynamoDbClient, dynamoDbAsyncClient, - configuration.getDynamoDbTables().getProfiles().getTableName()); - ProhibitedUsernames prohibitedUsernames = new ProhibitedUsernames(dynamoDbClient, - configuration.getDynamoDbTables().getReservedUsernames().getTableName()); - Keys keys = new Keys(dynamoDbClient, - configuration.getDynamoDbTables().getKeys().getTableName()); - MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(dynamoDbClient, dynamoDbAsyncClient, - configuration.getDynamoDbTables().getMessages().getTableName(), - configuration.getDynamoDbTables().getMessages().getExpiration(), - messageDeletionExecutor); - FaultTolerantRedisCluster messageInsertCacheCluster = new FaultTolerantRedisCluster("message_insert_cluster", - configuration.getMessageCacheConfiguration().getRedisClusterConfiguration(), redisClusterClientResources); - FaultTolerantRedisCluster messageReadDeleteCluster = new FaultTolerantRedisCluster("message_read_delete_cluster", - configuration.getMessageCacheConfiguration().getRedisClusterConfiguration(), redisClusterClientResources); - FaultTolerantRedisCluster clientPresenceCluster = new FaultTolerantRedisCluster("client_presence_cluster", - configuration.getClientPresenceClusterConfiguration(), redisClusterClientResources); - FaultTolerantRedisCluster rateLimitersCluster = new FaultTolerantRedisCluster("rate_limiters", - configuration.getRateLimitersCluster(), redisClusterClientResources); - SecureBackupClient secureBackupClient = new SecureBackupClient(backupCredentialsGenerator, backupServiceExecutor, - configuration.getSecureBackupServiceConfiguration()); - SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, - storageServiceExecutor, configuration.getSecureStorageServiceConfiguration()); - ClientPresenceManager clientPresenceManager = new ClientPresenceManager(clientPresenceCluster, - Executors.newSingleThreadScheduledExecutor(), keyspaceNotificationDispatchExecutor); - MessagesCache messagesCache = new MessagesCache(messageInsertCacheCluster, messageReadDeleteCluster, - Clock.systemUTC(), keyspaceNotificationDispatchExecutor, messageDeletionExecutor); - DirectoryQueue directoryQueue = new DirectoryQueue( - configuration.getDirectoryConfiguration().getSqsConfiguration()); - ProfilesManager profilesManager = new ProfilesManager(profiles, cacheCluster); - ReportMessageDynamoDb reportMessageDynamoDb = new ReportMessageDynamoDb(dynamoDbClient, - configuration.getDynamoDbTables().getReportMessage().getTableName(), - configuration.getReportMessageConfiguration().getReportTtl()); - ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster, - configuration.getReportMessageConfiguration().getCounterTtl()); - MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, - reportMessageManager, messageDeletionExecutor); - DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, - deletedAccountsLockDynamoDbClient, - configuration.getDynamoDbTables().getDeletedAccountsLock().getTableName()); - StoredVerificationCodeManager pendingAccountsManager = new StoredVerificationCodeManager(pendingAccounts); - AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster, - deletedAccountsManager, directoryQueue, keys, messagesManager, profilesManager, - pendingAccountsManager, secureStorageClient, secureBackupClient, clientPresenceManager, - experimentEnrollmentManager, registrationRecoveryPasswordsManager, clock); + AccountsManager accountsManager = deps.accountsManager(); for (String user : users) { Optional account = accountsManager.getByE164(user); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/SetUserDiscoverabilityCommand.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/SetUserDiscoverabilityCommand.java index 0cebb31c8..0c46a5d0a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/workers/SetUserDiscoverabilityCommand.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/SetUserDiscoverabilityCommand.java @@ -5,58 +5,17 @@ package org.whispersystems.textsecuregcm.workers; -import static com.codahale.metrics.MetricRegistry.name; - -import com.amazonaws.ClientConfiguration; -import com.amazonaws.auth.InstanceProfileCredentialsProvider; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; import com.fasterxml.jackson.databind.DeserializationFeature; import io.dropwizard.Application; import io.dropwizard.cli.EnvironmentCommand; import io.dropwizard.setup.Environment; -import io.lettuce.core.resource.ClientResources; -import java.time.Clock; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.whispersystems.textsecuregcm.WhisperServerConfiguration; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; -import org.whispersystems.textsecuregcm.controllers.SecureBackupController; -import org.whispersystems.textsecuregcm.controllers.SecureStorageController; -import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; -import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; -import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient; -import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; -import org.whispersystems.textsecuregcm.sqs.DirectoryQueue; import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.storage.Accounts; import org.whispersystems.textsecuregcm.storage.AccountsManager; -import org.whispersystems.textsecuregcm.storage.DeletedAccounts; -import org.whispersystems.textsecuregcm.storage.DeletedAccountsManager; -import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; -import org.whispersystems.textsecuregcm.storage.Keys; -import org.whispersystems.textsecuregcm.storage.MessagesCache; -import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb; -import org.whispersystems.textsecuregcm.storage.MessagesManager; -import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers; -import org.whispersystems.textsecuregcm.storage.Profiles; -import org.whispersystems.textsecuregcm.storage.ProfilesManager; -import org.whispersystems.textsecuregcm.storage.ProhibitedUsernames; -import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswords; -import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager; -import org.whispersystems.textsecuregcm.storage.ReportMessageDynamoDb; -import org.whispersystems.textsecuregcm.storage.ReportMessageManager; -import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager; -import org.whispersystems.textsecuregcm.storage.VerificationCodeStore; -import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig; -import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; public class SetUserDiscoverabilityCommand extends EnvironmentCommand { @@ -92,124 +51,10 @@ public class SetUserDiscoverabilityCommand extends EnvironmentCommand dynamicConfigurationManager = new DynamicConfigurationManager<>( - configuration.getAppConfig().getApplication(), configuration.getAppConfig().getEnvironment(), - configuration.getAppConfig().getConfigurationName(), DynamicConfiguration.class); - dynamicConfigurationManager.start(); - - ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager( - dynamicConfigurationManager); - - DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient( - configuration.getDynamoDbClientConfiguration(), - software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); - - DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client( - configuration.getDynamoDbClientConfiguration(), - software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); - - AmazonDynamoDB deletedAccountsLockDynamoDbClient = AmazonDynamoDBClientBuilder.standard() - .withRegion(configuration.getDynamoDbClientConfiguration().getRegion()) - .withClientConfiguration(new ClientConfiguration().withClientExecutionTimeout( - ((int) configuration.getDynamoDbClientConfiguration().getClientExecutionTimeout() - .toMillis())) - .withRequestTimeout( - (int) configuration.getDynamoDbClientConfiguration().getClientRequestTimeout() - .toMillis())) - .withCredentials(InstanceProfileCredentialsProvider.getInstance()) - .build(); - - DeletedAccounts deletedAccounts = new DeletedAccounts(dynamoDbClient, - configuration.getDynamoDbTables().getDeletedAccounts().getTableName(), - configuration.getDynamoDbTables().getDeletedAccounts().getNeedsReconciliationIndexName()); - VerificationCodeStore pendingAccounts = new VerificationCodeStore(dynamoDbClient, - configuration.getDynamoDbTables().getPendingAccounts().getTableName()); - RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords( - configuration.getDynamoDbTables().getRegistrationRecovery().getTableName(), - configuration.getDynamoDbTables().getRegistrationRecovery().getExpiration(), - dynamoDbClient, - dynamoDbAsyncClient - ); - - RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager = new RegistrationRecoveryPasswordsManager(registrationRecoveryPasswords); - - Accounts accounts = new Accounts( - dynamoDbClient, - dynamoDbAsyncClient, - configuration.getDynamoDbTables().getAccounts().getTableName(), - configuration.getDynamoDbTables().getAccounts().getPhoneNumberTableName(), - configuration.getDynamoDbTables().getAccounts().getPhoneNumberIdentifierTableName(), - configuration.getDynamoDbTables().getAccounts().getUsernamesTableName(), - configuration.getDynamoDbTables().getAccounts().getScanPageSize()); - PhoneNumberIdentifiers phoneNumberIdentifiers = new PhoneNumberIdentifiers(dynamoDbClient, - configuration.getDynamoDbTables().getPhoneNumberIdentifiers().getTableName()); - Profiles profiles = new Profiles(dynamoDbClient, dynamoDbAsyncClient, - configuration.getDynamoDbTables().getProfiles().getTableName()); - ProhibitedUsernames prohibitedUsernames = new ProhibitedUsernames(dynamoDbClient, - configuration.getDynamoDbTables().getReservedUsernames().getTableName()); - Keys keys = new Keys(dynamoDbClient, - configuration.getDynamoDbTables().getKeys().getTableName()); - MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(dynamoDbClient, dynamoDbAsyncClient, - configuration.getDynamoDbTables().getMessages().getTableName(), - configuration.getDynamoDbTables().getMessages().getExpiration(), - messageDeletionExecutor); - FaultTolerantRedisCluster messageInsertCacheCluster = new FaultTolerantRedisCluster("message_insert_cluster", - configuration.getMessageCacheConfiguration().getRedisClusterConfiguration(), redisClusterClientResources); - FaultTolerantRedisCluster messageReadDeleteCluster = new FaultTolerantRedisCluster("message_read_delete_cluster", - configuration.getMessageCacheConfiguration().getRedisClusterConfiguration(), redisClusterClientResources); - FaultTolerantRedisCluster clientPresenceCluster = new FaultTolerantRedisCluster("client_presence", - configuration.getClientPresenceClusterConfiguration(), redisClusterClientResources); - SecureBackupClient secureBackupClient = new SecureBackupClient(backupCredentialsGenerator, backupServiceExecutor, - configuration.getSecureBackupServiceConfiguration()); - SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, - storageServiceExecutor, configuration.getSecureStorageServiceConfiguration()); - ClientPresenceManager clientPresenceManager = new ClientPresenceManager(clientPresenceCluster, - Executors.newSingleThreadScheduledExecutor(), keyspaceNotificationDispatchExecutor); - MessagesCache messagesCache = new MessagesCache(messageInsertCacheCluster, messageReadDeleteCluster, - Clock.systemUTC(), keyspaceNotificationDispatchExecutor, messageDeletionExecutor); - DirectoryQueue directoryQueue = new DirectoryQueue( - configuration.getDirectoryConfiguration().getSqsConfiguration()); - ProfilesManager profilesManager = new ProfilesManager(profiles, cacheCluster); - ReportMessageDynamoDb reportMessageDynamoDb = new ReportMessageDynamoDb(dynamoDbClient, - configuration.getDynamoDbTables().getReportMessage().getTableName(), - configuration.getReportMessageConfiguration().getReportTtl()); - ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster, - configuration.getReportMessageConfiguration().getCounterTtl()); - MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, - reportMessageManager, messageDeletionExecutor); - DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, - deletedAccountsLockDynamoDbClient, - configuration.getDynamoDbTables().getDeletedAccountsLock().getTableName()); - StoredVerificationCodeManager pendingAccountsManager = new StoredVerificationCodeManager(pendingAccounts); - AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster, - deletedAccountsManager, directoryQueue, keys, messagesManager, profilesManager, - pendingAccountsManager, secureStorageClient, secureBackupClient, clientPresenceManager, - experimentEnrollmentManager, registrationRecoveryPasswordsManager, clock); - + final CommandDependencies deps = CommandDependencies.build("set-discoverability", environment, configuration); + final AccountsManager accountsManager = deps.accountsManager(); Optional maybeAccount; try { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/UnlinkDeviceCommand.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/UnlinkDeviceCommand.java new file mode 100644 index 000000000..2f61251b6 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/UnlinkDeviceCommand.java @@ -0,0 +1,80 @@ +/* + * Copyright 2013-2021 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.workers; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import io.dropwizard.Application; +import io.dropwizard.cli.EnvironmentCommand; +import io.dropwizard.setup.Environment; +import java.util.UUID; +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.whispersystems.textsecuregcm.WhisperServerConfiguration; +import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.storage.Device; + +public class UnlinkDeviceCommand extends EnvironmentCommand { + + public UnlinkDeviceCommand() { + super(new Application<>() { + @Override + public void run(WhisperServerConfiguration configuration, Environment environment) { + + } + }, "unlink-device", "Unlink a device and clear messages"); + } + + @Override + public void configure(final Subparser subparser) { + super.configure(subparser); + + subparser.addArgument("-d", "--deviceId") + .dest("deviceId") + .type(Long.class) + .required(true); + + subparser.addArgument("-u", "--uuid") + .help("the UUID of the account to modify") + .dest("uuid") + .type(String.class) + .required(true); + } + + @Override + protected void run(final Environment environment, final Namespace namespace, + final WhisperServerConfiguration configuration) throws Exception { + environment.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + final UUID aci = UUID.fromString(namespace.getString("uuid").trim()); + final long deviceId = namespace.getLong("deviceId"); + + final CommandDependencies deps = CommandDependencies.build("unlink-device", environment, configuration); + + Account account = deps.accountsManager().getByAccountIdentifier(aci) + .orElseThrow(() -> new IllegalArgumentException("account id " + aci +" does not exist")); + + if (deviceId == Device.MASTER_ID) { + throw new IllegalArgumentException("cannot delete primary device"); + } + + /** see {@link org.whispersystems.textsecuregcm.controllers.DeviceController#removeDevice} */ + System.out.format("Removing device %s::%d\n", aci, deviceId); + account = deps.accountsManager().update(account, a -> a.removeDevice(deviceId)); + + System.out.format("Removing keys for device %s::%d\n", aci, deviceId); + deps.keys().delete(account.getUuid(), deviceId); + + System.out.format("Clearing additional messages for %s::%d\n", aci, deviceId); + deps.messagesManager().clear(account.getUuid(), deviceId); + + System.out.format("Clearing presence state for %s::%d\n", aci, deviceId); + deps.clientPresenceManager().disconnectPresence(aci, deviceId); + + System.out.format("Device %s::%d successfully removed\n", aci, deviceId); + + + } +}