/* * Copyright 2013-2021 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm; 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.codahale.metrics.SharedMetricRegistries; import com.codahale.metrics.jdbi3.strategies.DefaultNameStrategy; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.DeserializationFeature; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import io.dropwizard.Application; import io.dropwizard.auth.AuthFilter; import io.dropwizard.auth.PolymorphicAuthDynamicFeature; import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.auth.basic.BasicCredentialAuthFilter; import io.dropwizard.auth.basic.BasicCredentials; import io.dropwizard.db.DataSourceFactory; import io.dropwizard.db.PooledDataSourceFactory; import io.dropwizard.jdbi3.JdbiFactory; import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Environment; import io.lettuce.core.resource.ClientResources; import io.micrometer.core.instrument.Meter.Id; import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.config.MeterFilter; import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; import io.micrometer.datadog.DatadogMeterRegistry; import java.net.http.HttpClient; import java.time.Clock; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Optional; import java.util.ServiceLoader; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; import javax.servlet.ServletRegistration; import org.eclipse.jetty.servlets.CrossOriginFilter; import org.glassfish.jersey.server.ServerProperties; import org.jdbi.v3.core.Jdbi; import org.signal.i18n.HeaderControlledResourceBundleLookup; import org.signal.zkgroup.ServerSecretParams; import org.signal.zkgroup.auth.ServerZkAuthOperations; import org.signal.zkgroup.profiles.ServerZkProfileOperations; import org.signal.zkgroup.receipts.ReceiptCredentialPresentation; import org.signal.zkgroup.receipts.ServerZkReceiptOperations; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.dispatch.DispatchManager; import org.whispersystems.textsecuregcm.abuse.AbusiveMessageFilter; import org.whispersystems.textsecuregcm.abuse.FilterAbusiveMessages; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.CertificateGenerator; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccountAuthenticator; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; import org.whispersystems.textsecuregcm.auth.WebsocketRefreshApplicationEventListener; import org.whispersystems.textsecuregcm.badges.ConfiguredProfileBadgeConverter; import org.whispersystems.textsecuregcm.badges.ResourceBundleLevelTranslator; import org.whispersystems.textsecuregcm.configuration.DirectoryServerConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.controllers.AcceptNumericOnlineFlagRequestFilter; import org.whispersystems.textsecuregcm.controllers.AccountController; import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV1; import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV2; import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV3; import org.whispersystems.textsecuregcm.controllers.CertificateController; import org.whispersystems.textsecuregcm.controllers.ChallengeController; import org.whispersystems.textsecuregcm.controllers.DeviceController; import org.whispersystems.textsecuregcm.controllers.DirectoryController; import org.whispersystems.textsecuregcm.controllers.DirectoryV2Controller; import org.whispersystems.textsecuregcm.controllers.DonationController; import org.whispersystems.textsecuregcm.controllers.KeepAliveController; import org.whispersystems.textsecuregcm.controllers.KeysController; import org.whispersystems.textsecuregcm.controllers.MessageController; import org.whispersystems.textsecuregcm.controllers.PaymentsController; import org.whispersystems.textsecuregcm.controllers.ProfileController; import org.whispersystems.textsecuregcm.controllers.ProvisioningController; import org.whispersystems.textsecuregcm.controllers.RemoteConfigController; import org.whispersystems.textsecuregcm.controllers.SecureBackupController; import org.whispersystems.textsecuregcm.controllers.SecureStorageController; import org.whispersystems.textsecuregcm.controllers.StickerController; import org.whispersystems.textsecuregcm.controllers.SubscriptionController; import org.whispersystems.textsecuregcm.controllers.VoiceVerificationController; import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager; import org.whispersystems.textsecuregcm.currency.FixerClient; import org.whispersystems.textsecuregcm.currency.FtxClient; import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; import org.whispersystems.textsecuregcm.filters.ContentLengthFilter; import org.whispersystems.textsecuregcm.filters.RemoteDeprecationFilter; import org.whispersystems.textsecuregcm.filters.TimestampResponseFilter; import org.whispersystems.textsecuregcm.limits.PreKeyRateLimiter; import org.whispersystems.textsecuregcm.limits.PushChallengeManager; import org.whispersystems.textsecuregcm.limits.RateLimitChallengeManager; import org.whispersystems.textsecuregcm.limits.RateLimitResetMetricsManager; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.UnsealedSenderRateLimiter; import org.whispersystems.textsecuregcm.liquibase.NameableMigrationsBundle; import org.whispersystems.textsecuregcm.mappers.CompletionExceptionMapper; import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.mappers.IOExceptionMapper; import org.whispersystems.textsecuregcm.mappers.ImpossiblePhoneNumberExceptionMapper; import org.whispersystems.textsecuregcm.mappers.InvalidWebsocketAddressExceptionMapper; import org.whispersystems.textsecuregcm.mappers.NonNormalizedPhoneNumberExceptionMapper; import org.whispersystems.textsecuregcm.mappers.RateLimitChallengeExceptionMapper; import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.mappers.RetryLaterExceptionMapper; import org.whispersystems.textsecuregcm.mappers.ServerRejectedExceptionMapper; import org.whispersystems.textsecuregcm.metrics.ApplicationShutdownMonitor; import org.whispersystems.textsecuregcm.metrics.BufferPoolGauges; import org.whispersystems.textsecuregcm.metrics.CpuUsageGauge; import org.whispersystems.textsecuregcm.metrics.FileDescriptorGauge; import org.whispersystems.textsecuregcm.metrics.FreeMemoryGauge; import org.whispersystems.textsecuregcm.metrics.GarbageCollectionGauges; import org.whispersystems.textsecuregcm.metrics.MaxFileDescriptorGauge; import org.whispersystems.textsecuregcm.metrics.MetricsApplicationEventListener; import org.whispersystems.textsecuregcm.metrics.MetricsRequestEventListener; import org.whispersystems.textsecuregcm.metrics.NetworkReceivedGauge; import org.whispersystems.textsecuregcm.metrics.NetworkSentGauge; import org.whispersystems.textsecuregcm.metrics.OperatingSystemMemoryGauge; import org.whispersystems.textsecuregcm.metrics.PushLatencyManager; import org.whispersystems.textsecuregcm.metrics.TrafficSource; import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider; import org.whispersystems.textsecuregcm.providers.RedisClientFactory; import org.whispersystems.textsecuregcm.providers.RedisClusterHealthCheck; import org.whispersystems.textsecuregcm.push.APNSender; import org.whispersystems.textsecuregcm.push.ApnFallbackManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.GCMSender; import org.whispersystems.textsecuregcm.push.MessageSender; import org.whispersystems.textsecuregcm.push.ProvisioningManager; import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.recaptcha.EnterpriseRecaptchaClient; import org.whispersystems.textsecuregcm.recaptcha.LegacyRecaptchaClient; import org.whispersystems.textsecuregcm.recaptcha.TransitionalRecaptchaClient; import org.whispersystems.textsecuregcm.redis.ConnectionEventLogger; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool; import org.whispersystems.textsecuregcm.s3.PolicySigner; import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator; import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient; import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; import org.whispersystems.textsecuregcm.sms.SmsSender; import org.whispersystems.textsecuregcm.sms.TwilioSmsSender; import org.whispersystems.textsecuregcm.sms.TwilioVerifyExperimentEnrollmentManager; import org.whispersystems.textsecuregcm.sqs.DirectoryQueue; import org.whispersystems.textsecuregcm.storage.AbusiveHostRules; import org.whispersystems.textsecuregcm.storage.AccountCleaner; import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawler; import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerCache; import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerListener; import org.whispersystems.textsecuregcm.storage.Accounts; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.ContactDiscoveryWriter; import org.whispersystems.textsecuregcm.storage.DeletedAccounts; import org.whispersystems.textsecuregcm.storage.DeletedAccountsDirectoryReconciler; import org.whispersystems.textsecuregcm.storage.DeletedAccountsManager; import org.whispersystems.textsecuregcm.storage.DeletedAccountsTableCrawler; import org.whispersystems.textsecuregcm.storage.DirectoryReconciler; import org.whispersystems.textsecuregcm.storage.DirectoryReconciliationClient; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase; import org.whispersystems.textsecuregcm.storage.IssuedReceiptsManager; import org.whispersystems.textsecuregcm.storage.Keys; import org.whispersystems.textsecuregcm.storage.MessagePersister; import org.whispersystems.textsecuregcm.storage.MessagesCache; import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb; import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.storage.NonNormalizedAccountCrawlerListener; import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers; import org.whispersystems.textsecuregcm.storage.Profiles; import org.whispersystems.textsecuregcm.storage.ProfilesManager; import org.whispersystems.textsecuregcm.storage.PubSubManager; import org.whispersystems.textsecuregcm.storage.PushChallengeDynamoDb; import org.whispersystems.textsecuregcm.storage.PushFeedbackProcessor; import org.whispersystems.textsecuregcm.storage.RedeemedReceiptsManager; import org.whispersystems.textsecuregcm.storage.RemoteConfigs; 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.SubscriptionManager; import org.whispersystems.textsecuregcm.storage.Usernames; import org.whispersystems.textsecuregcm.storage.UsernamesManager; import org.whispersystems.textsecuregcm.storage.VerificationCodeStore; import org.whispersystems.textsecuregcm.stripe.StripeManager; import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig; import org.whispersystems.textsecuregcm.util.HostnameUtil; import org.whispersystems.textsecuregcm.util.logging.LoggingUnhandledExceptionMapper; import org.whispersystems.textsecuregcm.util.logging.UncaughtExceptionHandler; import org.whispersystems.textsecuregcm.websocket.AuthenticatedConnectListener; import org.whispersystems.textsecuregcm.websocket.DeadLetterHandler; import org.whispersystems.textsecuregcm.websocket.ProvisioningConnectListener; import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator; import org.whispersystems.textsecuregcm.workers.CertificateCommand; import org.whispersystems.textsecuregcm.workers.CheckDynamicConfigurationCommand; import org.whispersystems.textsecuregcm.workers.DeleteUserCommand; import org.whispersystems.textsecuregcm.workers.ServerVersionCommand; import org.whispersystems.textsecuregcm.workers.SetCrawlerAccelerationTask; import org.whispersystems.textsecuregcm.workers.SetRequestLoggingEnabledTask; import org.whispersystems.textsecuregcm.workers.SetUserDiscoverabilityCommand; import org.whispersystems.textsecuregcm.workers.ZkParamsCommand; import org.whispersystems.websocket.WebSocketResourceProviderFactory; import org.whispersystems.websocket.setup.WebSocketEnvironment; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.s3.S3Client; public class WhisperServerService extends Application { private static final Logger log = LoggerFactory.getLogger(WhisperServerService.class); @Override public void initialize(Bootstrap bootstrap) { bootstrap.addCommand(new DeleteUserCommand()); bootstrap.addCommand(new CertificateCommand()); bootstrap.addCommand(new ZkParamsCommand()); bootstrap.addCommand(new ServerVersionCommand()); bootstrap.addCommand(new CheckDynamicConfigurationCommand()); bootstrap.addCommand(new SetUserDiscoverabilityCommand()); bootstrap.addBundle(new NameableMigrationsBundle("accountdb", "accountsdb.xml") { @Override public DataSourceFactory getDataSourceFactory(WhisperServerConfiguration configuration) { return configuration.getAccountsDatabaseConfiguration(); } }); bootstrap.addBundle(new NameableMigrationsBundle("abusedb", "abusedb.xml") { @Override public PooledDataSourceFactory getDataSourceFactory(WhisperServerConfiguration configuration) { return configuration.getAbuseDatabaseConfiguration(); } }); } @Override public String getName() { return "whisper-server"; } @Override public void run(WhisperServerConfiguration config, Environment environment) throws Exception { final Clock clock = Clock.systemUTC(); final int availableProcessors = Runtime.getRuntime().availableProcessors(); UncaughtExceptionHandler.register(); SharedMetricRegistries.add(Constants.METRICS_NAME, environment.metrics()); final DistributionStatisticConfig defaultDistributionStatisticConfig = DistributionStatisticConfig.builder() .percentiles(.75, .95, .99, .999) .build(); { final DatadogMeterRegistry datadogMeterRegistry = new DatadogMeterRegistry( config.getDatadogConfiguration(), io.micrometer.core.instrument.Clock.SYSTEM); datadogMeterRegistry.config().commonTags( Tags.of( "service", "chat", "host", HostnameUtil.getLocalHostname(), "version", WhisperServerVersion.getServerVersion(), "env", config.getDatadogConfiguration().getEnvironment())) .meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.REQUEST_COUNTER_NAME)) .meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.ANDROID_REQUEST_COUNTER_NAME)) .meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.DESKTOP_REQUEST_COUNTER_NAME)) .meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.IOS_REQUEST_COUNTER_NAME)) .meterFilter(new MeterFilter() { @Override public DistributionStatisticConfig configure(final Id id, final DistributionStatisticConfig config) { return defaultDistributionStatisticConfig.merge(config); } }); Metrics.addRegistry(datadogMeterRegistry); } environment.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); environment.getObjectMapper().setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); environment.getObjectMapper().setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup = new HeaderControlledResourceBundleLookup(); ConfiguredProfileBadgeConverter profileBadgeConverter = new ConfiguredProfileBadgeConverter( clock, config.getBadges(), headerControlledResourceBundleLookup); ResourceBundleLevelTranslator resourceBundleLevelTranslator = new ResourceBundleLevelTranslator( headerControlledResourceBundleLookup); JdbiFactory jdbiFactory = new JdbiFactory(DefaultNameStrategy.CHECK_EMPTY); Jdbi accountJdbi = jdbiFactory.build(environment, config.getAccountsDatabaseConfiguration(), "accountdb"); Jdbi abuseJdbi = jdbiFactory.build(environment, config.getAbuseDatabaseConfiguration(), "abusedb" ); FaultTolerantDatabase accountDatabase = new FaultTolerantDatabase("accounts_database", accountJdbi, config.getAccountsDatabaseConfiguration().getCircuitBreakerConfiguration()); FaultTolerantDatabase abuseDatabase = new FaultTolerantDatabase("abuse_database", abuseJdbi, config.getAbuseDatabaseConfiguration().getCircuitBreakerConfiguration()); DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient( config.getDynamoDbClientConfiguration(), software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); DynamoDbClient messageDynamoDb = DynamoDbFromConfig.client(config.getMessageDynamoDbConfiguration(), software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); DynamoDbClient preKeyDynamoDb = DynamoDbFromConfig.client(config.getKeysDynamoDbConfiguration(), software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); DynamoDbClient accountsDynamoDbClient = DynamoDbFromConfig.client(config.getAccountsDynamoDbConfiguration(), software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); DynamoDbClient reservedUsernamesDynamoDbClient = DynamoDbFromConfig.client(config.getReservedUsernamesDynamoDbConfiguration(), software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); DynamoDbClient phoneNumberIdentifiersDynamoDbClient = DynamoDbFromConfig.client(config.getPhoneNumberIdentifiersDynamoDbConfiguration(), software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); DynamoDbClient deletedAccountsDynamoDbClient = DynamoDbFromConfig.client(config.getDeletedAccountsDynamoDbConfiguration(), software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); DynamoDbClient pushChallengeDynamoDbClient = DynamoDbFromConfig.client( config.getPushChallengeDynamoDbConfiguration(), software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); DynamoDbClient reportMessageDynamoDbClient = DynamoDbFromConfig.client( config.getReportMessageDynamoDbConfiguration(), software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); DynamoDbClient pendingAccountsDynamoDbClient = DynamoDbFromConfig.client( config.getPendingAccountsDynamoDbConfiguration(), software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); DynamoDbClient pendingDevicesDynamoDbClient = DynamoDbFromConfig.client( config.getPendingDevicesDynamoDbConfiguration(), software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); AmazonDynamoDB deletedAccountsLockDynamoDbClient = AmazonDynamoDBClientBuilder.standard() .withRegion(config.getDeletedAccountsLockDynamoDbConfiguration().getRegion()) .withClientConfiguration(new ClientConfiguration().withClientExecutionTimeout( ((int) config.getDeletedAccountsLockDynamoDbConfiguration().getClientExecutionTimeout().toMillis())) .withRequestTimeout( (int) config.getDeletedAccountsLockDynamoDbConfiguration().getClientRequestTimeout().toMillis())) .withCredentials(InstanceProfileCredentialsProvider.getInstance()) .build(); DeletedAccounts deletedAccounts = new DeletedAccounts(deletedAccountsDynamoDbClient, config.getDeletedAccountsDynamoDbConfiguration().getTableName(), config.getDeletedAccountsDynamoDbConfiguration().getNeedsReconciliationIndexName()); Accounts accounts = new Accounts(accountsDynamoDbClient, config.getAccountsDynamoDbConfiguration().getTableName(), config.getAccountsDynamoDbConfiguration().getPhoneNumberTableName(), config.getAccountsDynamoDbConfiguration().getPhoneNumberIdentifierTableName(), config.getAccountsDynamoDbConfiguration().getScanPageSize()); PhoneNumberIdentifiers phoneNumberIdentifiers = new PhoneNumberIdentifiers(phoneNumberIdentifiersDynamoDbClient, config.getPhoneNumberIdentifiersDynamoDbConfiguration().getTableName()); Usernames usernames = new Usernames(accountDatabase); ReservedUsernames reservedUsernames = new ReservedUsernames(reservedUsernamesDynamoDbClient, config.getReservedUsernamesDynamoDbConfiguration().getTableName()); Profiles profiles = new Profiles(accountDatabase); Keys keys = new Keys(preKeyDynamoDb, config.getKeysDynamoDbConfiguration().getTableName()); MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(messageDynamoDb, config.getMessageDynamoDbConfiguration().getTableName(), config.getMessageDynamoDbConfiguration().getTimeToLive()); AbusiveHostRules abusiveHostRules = new AbusiveHostRules(abuseDatabase); RemoteConfigs remoteConfigs = new RemoteConfigs(accountDatabase); PushChallengeDynamoDb pushChallengeDynamoDb = new PushChallengeDynamoDb(pushChallengeDynamoDbClient, config.getPushChallengeDynamoDbConfiguration().getTableName()); ReportMessageDynamoDb reportMessageDynamoDb = new ReportMessageDynamoDb(reportMessageDynamoDbClient, config.getReportMessageDynamoDbConfiguration().getTableName(), config.getReportMessageConfiguration().getReportTtl()); VerificationCodeStore pendingAccounts = new VerificationCodeStore(pendingAccountsDynamoDbClient, config.getPendingAccountsDynamoDbConfiguration().getTableName()); VerificationCodeStore pendingDevices = new VerificationCodeStore(pendingDevicesDynamoDbClient, config.getPendingDevicesDynamoDbConfiguration().getTableName()); RedisClientFactory pubSubClientFactory = new RedisClientFactory("pubsub_cache", config.getPubsubCacheConfiguration().getUrl(), config.getPubsubCacheConfiguration().getReplicaUrls(), config.getPubsubCacheConfiguration().getCircuitBreakerConfiguration()); ReplicatedJedisPool pubsubClient = pubSubClientFactory.getRedisClientPool(); ClientResources redisClientResources = ClientResources.builder().build(); ConnectionEventLogger.logConnectionEvents(redisClientResources); FaultTolerantRedisCluster cacheCluster = new FaultTolerantRedisCluster("main_cache_cluster", config.getCacheClusterConfiguration(), redisClientResources); FaultTolerantRedisCluster messagesCluster = new FaultTolerantRedisCluster("messages_cluster", config.getMessageCacheConfiguration().getRedisClusterConfiguration(), redisClientResources); FaultTolerantRedisCluster clientPresenceCluster = new FaultTolerantRedisCluster("client_presence_cluster", config.getClientPresenceClusterConfiguration(), redisClientResources); FaultTolerantRedisCluster metricsCluster = new FaultTolerantRedisCluster("metrics_cluster", config.getMetricsClusterConfiguration(), redisClientResources); FaultTolerantRedisCluster pushSchedulerCluster = new FaultTolerantRedisCluster("push_scheduler", config.getPushSchedulerCluster(), redisClientResources); FaultTolerantRedisCluster rateLimitersCluster = new FaultTolerantRedisCluster("rate_limiters", config.getRateLimitersCluster(), redisClientResources); BlockingQueue keyspaceNotificationDispatchQueue = new ArrayBlockingQueue<>(10_000); Metrics.gaugeCollectionSize(name(getClass(), "keyspaceNotificationDispatchQueueSize"), Collections.emptyList(), keyspaceNotificationDispatchQueue); ScheduledExecutorService recurringJobExecutor = environment.lifecycle() .scheduledExecutorService(name(getClass(), "recurringJob-%d")).threads(6).build(); ScheduledExecutorService retrySchedulingExecutor = environment.lifecycle().scheduledExecutorService(name(getClass(), "retry-%d")).threads(2).build(); ExecutorService keyspaceNotificationDispatchExecutor = environment.lifecycle().executorService(name(getClass(), "keyspaceNotification-%d")).maxThreads(16).workQueue(keyspaceNotificationDispatchQueue).build(); ExecutorService apnSenderExecutor = environment.lifecycle().executorService(name(getClass(), "apnSender-%d")).maxThreads(1).minThreads(1).build(); ExecutorService gcmSenderExecutor = environment.lifecycle().executorService(name(getClass(), "gcmSender-%d")).maxThreads(1).minThreads(1).build(); ExecutorService backupServiceExecutor = environment.lifecycle().executorService(name(getClass(), "backupService-%d")).maxThreads(1).minThreads(1).build(); ExecutorService storageServiceExecutor = environment.lifecycle().executorService(name(getClass(), "storageService-%d")).maxThreads(1).minThreads(1).build(); ExecutorService multiRecipientMessageExecutor = environment.lifecycle() .executorService(name(getClass(), "multiRecipientMessage-%d")).minThreads(64).maxThreads(64).build(); ExecutorService stripeExecutor = environment.lifecycle().executorService(name(getClass(), "stripe-%d")). maxThreads(availableProcessors). // mostly this is IO bound so tying to number of processors is tenuous at best minThreads(availableProcessors). // mostly this is IO bound so tying to number of processors is tenuous at best allowCoreThreadTimeOut(true). build(); StripeManager stripeManager = new StripeManager(config.getStripe().getApiKey(), stripeExecutor, config.getStripe().getIdempotencyKeyGenerator(), config.getStripe().getBoostDescription()); ExternalServiceCredentialGenerator directoryCredentialsGenerator = new ExternalServiceCredentialGenerator( config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenSharedSecret(), config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenUserIdSecret()); ExternalServiceCredentialGenerator directoryV2CredentialsGenerator = new ExternalServiceCredentialGenerator( config.getDirectoryV2Configuration().getDirectoryV2ClientConfiguration() .getUserAuthenticationTokenSharedSecret(), false); DynamicConfigurationManager dynamicConfigurationManager = new DynamicConfigurationManager<>(config.getAppConfig().getApplication(), config.getAppConfig().getEnvironment(), config.getAppConfig().getConfigurationName(), DynamicConfiguration.class); dynamicConfigurationManager.start(); ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(dynamicConfigurationManager); TwilioVerifyExperimentEnrollmentManager verifyExperimentEnrollmentManager = new TwilioVerifyExperimentEnrollmentManager( config.getVoiceVerificationConfiguration(), experimentEnrollmentManager); ExternalServiceCredentialGenerator storageCredentialsGenerator = new ExternalServiceCredentialGenerator( config.getSecureStorageServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true); ExternalServiceCredentialGenerator backupCredentialsGenerator = new ExternalServiceCredentialGenerator( config.getSecureBackupServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true); ExternalServiceCredentialGenerator paymentsCredentialsGenerator = new ExternalServiceCredentialGenerator( config.getPaymentsServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true); SecureBackupClient secureBackupClient = new SecureBackupClient(backupCredentialsGenerator, backupServiceExecutor, config.getSecureBackupServiceConfiguration()); SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, storageServiceExecutor, config.getSecureStorageServiceConfiguration()); ClientPresenceManager clientPresenceManager = new ClientPresenceManager(clientPresenceCluster, recurringJobExecutor, keyspaceNotificationDispatchExecutor); DirectoryQueue directoryQueue = new DirectoryQueue(config.getDirectoryConfiguration().getSqsConfiguration()); StoredVerificationCodeManager pendingAccountsManager = new StoredVerificationCodeManager(pendingAccounts); StoredVerificationCodeManager pendingDevicesManager = new StoredVerificationCodeManager(pendingDevices); UsernamesManager usernamesManager = new UsernamesManager(usernames, reservedUsernames, cacheCluster); ProfilesManager profilesManager = new ProfilesManager(profiles, cacheCluster); MessagesCache messagesCache = new MessagesCache(messagesCluster, messagesCluster, keyspaceNotificationDispatchExecutor); PushLatencyManager pushLatencyManager = new PushLatencyManager(metricsCluster, dynamicConfigurationManager); ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster, Metrics.globalRegistry, config.getReportMessageConfiguration().getCounterTtl()); MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, pushLatencyManager, reportMessageManager); DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, deletedAccountsLockDynamoDbClient, config.getDeletedAccountsLockDynamoDbConfiguration().getTableName()); AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster, deletedAccountsManager, directoryQueue, keys, messagesManager, usernamesManager, profilesManager, pendingAccountsManager, secureStorageClient, secureBackupClient, clientPresenceManager, clock); RemoteConfigsManager remoteConfigsManager = new RemoteConfigsManager(remoteConfigs); DeadLetterHandler deadLetterHandler = new DeadLetterHandler(accountsManager, messagesManager); DispatchManager dispatchManager = new DispatchManager(pubSubClientFactory, Optional.of(deadLetterHandler)); PubSubManager pubSubManager = new PubSubManager(pubsubClient, dispatchManager); APNSender apnSender = new APNSender(apnSenderExecutor, accountsManager, config.getApnConfiguration()); GCMSender gcmSender = new GCMSender(gcmSenderExecutor, accountsManager, config.getGcmConfiguration().getApiKey()); RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), dynamicConfigurationManager, rateLimitersCluster); ProvisioningManager provisioningManager = new ProvisioningManager(pubSubManager); IssuedReceiptsManager issuedReceiptsManager = new IssuedReceiptsManager( config.getDynamoDbTables().getIssuedReceipts().getTableName(), config.getDynamoDbTables().getIssuedReceipts().getExpiration(), dynamoDbAsyncClient, config.getDynamoDbTables().getIssuedReceipts().getGenerator()); RedeemedReceiptsManager redeemedReceiptsManager = new RedeemedReceiptsManager( clock, config.getDynamoDbTables().getRedeemedReceipts().getTableName(), dynamoDbAsyncClient, config.getDynamoDbTables().getRedeemedReceipts().getExpiration()); SubscriptionManager subscriptionManager = new SubscriptionManager( config.getDynamoDbTables().getSubscriptions().getTableName(), dynamoDbAsyncClient); AccountAuthenticator accountAuthenticator = new AccountAuthenticator(accountsManager); DisabledPermittedAccountAuthenticator disabledPermittedAccountAuthenticator = new DisabledPermittedAccountAuthenticator(accountsManager); RateLimitResetMetricsManager rateLimitResetMetricsManager = new RateLimitResetMetricsManager(metricsCluster, Metrics.globalRegistry); UnsealedSenderRateLimiter unsealedSenderRateLimiter = new UnsealedSenderRateLimiter(rateLimiters, rateLimitersCluster, dynamicConfigurationManager, rateLimitResetMetricsManager); PreKeyRateLimiter preKeyRateLimiter = new PreKeyRateLimiter(rateLimiters, dynamicConfigurationManager, rateLimitResetMetricsManager); ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushSchedulerCluster, apnSender, accountsManager); TwilioSmsSender twilioSmsSender = new TwilioSmsSender(config.getTwilioConfiguration(), dynamicConfigurationManager); SmsSender smsSender = new SmsSender(twilioSmsSender); MessageSender messageSender = new MessageSender(apnFallbackManager, clientPresenceManager, messagesManager, gcmSender, apnSender, pushLatencyManager); ReceiptSender receiptSender = new ReceiptSender(accountsManager, messageSender); TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(config.getTurnConfiguration()); LegacyRecaptchaClient legacyRecaptchaClient = new LegacyRecaptchaClient(config.getRecaptchaConfiguration().getSecret()); EnterpriseRecaptchaClient enterpriseRecaptchaClient = new EnterpriseRecaptchaClient( config.getRecaptchaV2Configuration().getScoreFloor().doubleValue(), config.getRecaptchaV2Configuration().getSiteKey(), config.getRecaptchaV2Configuration().getProjectPath(), config.getRecaptchaV2Configuration().getCredentialConfigurationJson()); TransitionalRecaptchaClient transitionalRecaptchaClient = new TransitionalRecaptchaClient(legacyRecaptchaClient, enterpriseRecaptchaClient); PushChallengeManager pushChallengeManager = new PushChallengeManager(apnSender, gcmSender, pushChallengeDynamoDb); RateLimitChallengeManager rateLimitChallengeManager = new RateLimitChallengeManager(pushChallengeManager, transitionalRecaptchaClient, preKeyRateLimiter, unsealedSenderRateLimiter, rateLimiters, dynamicConfigurationManager); MessagePersister messagePersister = new MessagePersister(messagesCache, messagesManager, accountsManager, dynamicConfigurationManager, Duration.ofMinutes(config.getMessageCacheConfiguration().getPersistDelayMinutes())); final List directoryReconciliationAccountDatabaseCrawlerListeners = new ArrayList<>(); final List deletedAccountsDirectoryReconcilers = new ArrayList<>(); for (DirectoryServerConfiguration directoryServerConfiguration : config.getDirectoryConfiguration() .getDirectoryServerConfiguration()) { final DirectoryReconciliationClient directoryReconciliationClient = new DirectoryReconciliationClient( directoryServerConfiguration); final DirectoryReconciler directoryReconciler = new DirectoryReconciler( directoryServerConfiguration.getReplicationName(), directoryReconciliationClient, dynamicConfigurationManager); // reconcilers are read-only directoryReconciliationAccountDatabaseCrawlerListeners.add(directoryReconciler); final DeletedAccountsDirectoryReconciler deletedAccountsDirectoryReconciler = new DeletedAccountsDirectoryReconciler( directoryServerConfiguration.getReplicationName(), directoryReconciliationClient); deletedAccountsDirectoryReconcilers.add(deletedAccountsDirectoryReconciler); } AccountDatabaseCrawlerCache directoryReconciliationAccountDatabaseCrawlerCache = new AccountDatabaseCrawlerCache( cacheCluster, AccountDatabaseCrawlerCache.DIRECTORY_RECONCILER_PREFIX); AccountDatabaseCrawler directoryReconciliationAccountDatabaseCrawler = new AccountDatabaseCrawler(accountsManager, directoryReconciliationAccountDatabaseCrawlerCache, directoryReconciliationAccountDatabaseCrawlerListeners, config.getAccountDatabaseCrawlerConfiguration().getChunkSize(), config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs() ); // TODO listeners must be ordered so that ones that directly update accounts come last, so that read-only ones are not working with stale data final List accountDatabaseCrawlerListeners = List.of( new NonNormalizedAccountCrawlerListener(accountsManager, metricsCluster), new ContactDiscoveryWriter(accountsManager), // PushFeedbackProcessor may update device properties new PushFeedbackProcessor(accountsManager), // delete accounts last new AccountCleaner(accountsManager) ); AccountDatabaseCrawlerCache accountDatabaseCrawlerCache = new AccountDatabaseCrawlerCache(cacheCluster, AccountDatabaseCrawlerCache.GENERAL_PURPOSE_PREFIX); AccountDatabaseCrawler accountDatabaseCrawler = new AccountDatabaseCrawler(accountsManager, accountDatabaseCrawlerCache, accountDatabaseCrawlerListeners, config.getAccountDatabaseCrawlerConfiguration().getChunkSize(), config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs() ); DeletedAccountsTableCrawler deletedAccountsTableCrawler = new DeletedAccountsTableCrawler(deletedAccountsManager, deletedAccountsDirectoryReconcilers, cacheCluster, recurringJobExecutor); HttpClient currencyClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build(); FixerClient fixerClient = new FixerClient(currencyClient, config.getPaymentsServiceConfiguration().getFixerApiKey()); FtxClient ftxClient = new FtxClient(currencyClient); CurrencyConversionManager currencyManager = new CurrencyConversionManager(fixerClient, ftxClient, config.getPaymentsServiceConfiguration().getPaymentCurrencies()); apnSender.setApnFallbackManager(apnFallbackManager); environment.lifecycle().manage(new ApplicationShutdownMonitor()); environment.lifecycle().manage(apnFallbackManager); environment.lifecycle().manage(pubSubManager); environment.lifecycle().manage(messageSender); environment.lifecycle().manage(accountDatabaseCrawler); environment.lifecycle().manage(directoryReconciliationAccountDatabaseCrawler); environment.lifecycle().manage(deletedAccountsTableCrawler); environment.lifecycle().manage(remoteConfigsManager); environment.lifecycle().manage(messagesCache); environment.lifecycle().manage(messagePersister); environment.lifecycle().manage(clientPresenceManager); environment.lifecycle().manage(currencyManager); environment.lifecycle().manage(directoryQueue); StaticCredentialsProvider cdnCredentialsProvider = StaticCredentialsProvider .create(AwsBasicCredentials.create( config.getCdnConfiguration().getAccessKey(), config.getCdnConfiguration().getAccessSecret())); S3Client cdnS3Client = S3Client.builder() .credentialsProvider(cdnCredentialsProvider) .region(Region.of(config.getCdnConfiguration().getRegion())) .build(); PostPolicyGenerator profileCdnPolicyGenerator = new PostPolicyGenerator(config.getCdnConfiguration().getRegion(), config.getCdnConfiguration().getBucket(), config.getCdnConfiguration().getAccessKey()); PolicySigner profileCdnPolicySigner = new PolicySigner(config.getCdnConfiguration().getAccessSecret(), config.getCdnConfiguration().getRegion()); ServerSecretParams zkSecretParams = new ServerSecretParams(config.getZkConfig().getServerSecret()); ServerZkProfileOperations zkProfileOperations = new ServerZkProfileOperations(zkSecretParams); ServerZkAuthOperations zkAuthOperations = new ServerZkAuthOperations(zkSecretParams); ServerZkReceiptOperations zkReceiptOperations = new ServerZkReceiptOperations(zkSecretParams); AuthFilter accountAuthFilter = new BasicCredentialAuthFilter.Builder().setAuthenticator( accountAuthenticator).buildAuthFilter(); AuthFilter disabledPermittedAccountAuthFilter = new BasicCredentialAuthFilter.Builder().setAuthenticator( disabledPermittedAccountAuthenticator).buildAuthFilter(); environment.servlets() .addFilter("RemoteDeprecationFilter", new RemoteDeprecationFilter(dynamicConfigurationManager)) .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*"); // TODO Remove on or after 2022-03-01 final AcceptNumericOnlineFlagRequestFilter acceptNumericOnlineFlagRequestFilter = new AcceptNumericOnlineFlagRequestFilter("v1/messages/multi_recipient"); environment.jersey().register(new ContentLengthFilter(TrafficSource.HTTP)); environment.jersey().register(acceptNumericOnlineFlagRequestFilter); environment.jersey().register(MultiRecipientMessageProvider.class); environment.jersey().register(new MetricsApplicationEventListener(TrafficSource.HTTP)); environment.jersey() .register(new PolymorphicAuthDynamicFeature<>(ImmutableMap.of(AuthenticatedAccount.class, accountAuthFilter, DisabledPermittedAuthenticatedAccount.class, disabledPermittedAccountAuthFilter))); environment.jersey().register(new PolymorphicAuthValueFactoryProvider.Binder<>( ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))); environment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager)); environment.jersey().register(new TimestampResponseFilter()); environment.jersey().register(new VoiceVerificationController(config.getVoiceVerificationConfiguration().getUrl(), config.getVoiceVerificationConfiguration().getLocales())); /// WebSocketEnvironment webSocketEnvironment = new WebSocketEnvironment<>(environment, config.getWebSocketConfiguration(), 90000); webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(accountAuthenticator)); webSocketEnvironment.setConnectListener( new AuthenticatedConnectListener(receiptSender, messagesManager, messageSender, apnFallbackManager, clientPresenceManager, retrySchedulingExecutor)); webSocketEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager)); webSocketEnvironment.jersey().register(new ContentLengthFilter(TrafficSource.WEBSOCKET)); webSocketEnvironment.jersey().register(acceptNumericOnlineFlagRequestFilter); webSocketEnvironment.jersey().register(MultiRecipientMessageProvider.class); webSocketEnvironment.jersey().register(new MetricsApplicationEventListener(TrafficSource.WEBSOCKET)); webSocketEnvironment.jersey().register(new KeepAliveController(clientPresenceManager)); // these should be common, but use @Auth DisabledPermittedAccount, which isn’t supported yet on websocket environment.jersey().register( new AccountController(pendingAccountsManager, accountsManager, usernamesManager, abusiveHostRules, rateLimiters, smsSender, dynamicConfigurationManager, turnTokenGenerator, config.getTestDevices(), transitionalRecaptchaClient, gcmSender, apnSender, backupCredentialsGenerator, verifyExperimentEnrollmentManager)); environment.jersey().register(new KeysController(rateLimiters, keys, accountsManager, preKeyRateLimiter, rateLimitChallengeManager)); final List commonControllers = Lists.newArrayList( new AttachmentControllerV1(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getBucket()), new AttachmentControllerV2(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getRegion(), config.getAwsAttachmentsConfiguration().getBucket()), new AttachmentControllerV3(rateLimiters, config.getGcpAttachmentsConfiguration().getDomain(), config.getGcpAttachmentsConfiguration().getEmail(), config.getGcpAttachmentsConfiguration().getMaxSizeInBytes(), config.getGcpAttachmentsConfiguration().getPathPrefix(), config.getGcpAttachmentsConfiguration().getRsaSigningKey()), new CertificateController(new CertificateGenerator(config.getDeliveryCertificate().getCertificate(), config.getDeliveryCertificate().getPrivateKey(), config.getDeliveryCertificate().getExpiresDays()), zkAuthOperations), new ChallengeController(rateLimitChallengeManager), new DeviceController(pendingDevicesManager, accountsManager, messagesManager, keys, rateLimiters, config.getMaxDevices()), new DirectoryController(directoryCredentialsGenerator), new DirectoryV2Controller(directoryV2CredentialsGenerator), new DonationController(clock, zkReceiptOperations, redeemedReceiptsManager, accountsManager, config.getBadges(), ReceiptCredentialPresentation::new, stripeExecutor, config.getDonationConfiguration(), config.getStripe()), new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, messagesManager, unsealedSenderRateLimiter, apnFallbackManager, rateLimitChallengeManager, reportMessageManager, multiRecipientMessageExecutor), new PaymentsController(currencyManager, paymentsCredentialsGenerator), new ProfileController(clock, rateLimiters, accountsManager, profilesManager, usernamesManager, dynamicConfigurationManager, profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, config.getCdnConfiguration().getBucket(), zkProfileOperations), new ProvisioningController(rateLimiters, provisioningManager), new RemoteConfigController(remoteConfigsManager, config.getRemoteConfigConfiguration().getAuthorizedTokens(), config.getRemoteConfigConfiguration().getGlobalConfig()), new SecureBackupController(backupCredentialsGenerator), new SecureStorageController(storageCredentialsGenerator), new StickerController(rateLimiters, config.getCdnConfiguration().getAccessKey(), config.getCdnConfiguration().getAccessSecret(), config.getCdnConfiguration().getRegion(), config.getCdnConfiguration().getBucket()) ); if (config.getSubscription() != null && config.getBoost() != null) { commonControllers.add(new SubscriptionController(clock, config.getSubscription(), config.getBoost(), subscriptionManager, stripeManager, zkReceiptOperations, issuedReceiptsManager, profileBadgeConverter, resourceBundleLevelTranslator)); } for (Object controller : commonControllers) { environment.jersey().register(controller); webSocketEnvironment.jersey().register(controller); } boolean registeredAbusiveMessageFilter = false; for (final AbusiveMessageFilter filter : ServiceLoader.load(AbusiveMessageFilter.class)) { if (filter.getClass().isAnnotationPresent(FilterAbusiveMessages.class)) { try { filter.configure(config.getAbusiveMessageFilterConfiguration().getEnvironment()); environment.lifecycle().manage(filter); environment.jersey().register(filter); webSocketEnvironment.jersey().register(filter); log.info("Registered abusive message filter: {}", filter.getClass().getName()); registeredAbusiveMessageFilter = true; } catch (final Exception e) { log.warn("Failed to register abusive message filter: {}", filter.getClass().getName(), e); } } else { log.warn("Abusive message filter {} not annotated with @FilterAbusiveMessages and will not be installed", filter.getClass().getName()); } } if (!registeredAbusiveMessageFilter) { log.warn("No abusive message filters installed"); } WebSocketEnvironment provisioningEnvironment = new WebSocketEnvironment<>(environment, webSocketEnvironment.getRequestLog(), 60000); provisioningEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager)); provisioningEnvironment.setConnectListener(new ProvisioningConnectListener(pubSubManager)); provisioningEnvironment.jersey().register(new MetricsApplicationEventListener(TrafficSource.WEBSOCKET)); provisioningEnvironment.jersey().register(new KeepAliveController(clientPresenceManager)); registerCorsFilter(environment); registerExceptionMappers(environment, webSocketEnvironment, provisioningEnvironment); RateLimitChallengeExceptionMapper rateLimitChallengeExceptionMapper = new RateLimitChallengeExceptionMapper( rateLimitChallengeManager); environment.jersey().register(rateLimitChallengeExceptionMapper); webSocketEnvironment.jersey().register(rateLimitChallengeExceptionMapper); provisioningEnvironment.jersey().register(rateLimitChallengeExceptionMapper); environment.jersey().property(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE); webSocketEnvironment.jersey().property(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE); provisioningEnvironment.jersey().property(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE); WebSocketResourceProviderFactory webSocketServlet = new WebSocketResourceProviderFactory<>( webSocketEnvironment, AuthenticatedAccount.class, config.getWebSocketConfiguration()); WebSocketResourceProviderFactory provisioningServlet = new WebSocketResourceProviderFactory<>( provisioningEnvironment, AuthenticatedAccount.class, config.getWebSocketConfiguration()); ServletRegistration.Dynamic websocket = environment.servlets().addServlet("WebSocket", webSocketServlet); ServletRegistration.Dynamic provisioning = environment.servlets().addServlet("Provisioning", provisioningServlet); websocket.addMapping("/v1/websocket/"); websocket.setAsyncSupported(true); provisioning.addMapping("/v1/websocket/provisioning/"); provisioning.setAsyncSupported(true); environment.admin().addTask(new SetRequestLoggingEnabledTask()); environment.admin().addTask(new SetCrawlerAccelerationTask(accountDatabaseCrawlerCache)); environment.healthChecks().register("cacheCluster", new RedisClusterHealthCheck(cacheCluster)); environment.metrics().register(name(CpuUsageGauge.class, "cpu"), new CpuUsageGauge(3, TimeUnit.SECONDS)); environment.metrics().register(name(FreeMemoryGauge.class, "free_memory"), new FreeMemoryGauge()); environment.metrics().register(name(NetworkSentGauge.class, "bytes_sent"), new NetworkSentGauge()); environment.metrics().register(name(NetworkReceivedGauge.class, "bytes_received"), new NetworkReceivedGauge()); environment.metrics().register(name(FileDescriptorGauge.class, "fd_count"), new FileDescriptorGauge()); environment.metrics().register(name(MaxFileDescriptorGauge.class, "max_fd_count"), new MaxFileDescriptorGauge()); environment.metrics() .register(name(OperatingSystemMemoryGauge.class, "buffers"), new OperatingSystemMemoryGauge("Buffers")); environment.metrics() .register(name(OperatingSystemMemoryGauge.class, "cached"), new OperatingSystemMemoryGauge("Cached")); BufferPoolGauges.registerMetrics(); GarbageCollectionGauges.registerMetrics(); } private void registerExceptionMappers(Environment environment, WebSocketEnvironment webSocketEnvironment, WebSocketEnvironment provisioningEnvironment) { List.of( new LoggingUnhandledExceptionMapper(), new CompletionExceptionMapper(), new IOExceptionMapper(), new RateLimitExceededExceptionMapper(), new InvalidWebsocketAddressExceptionMapper(), new DeviceLimitExceededExceptionMapper(), new RetryLaterExceptionMapper(), new ServerRejectedExceptionMapper(), new ImpossiblePhoneNumberExceptionMapper(), new NonNormalizedPhoneNumberExceptionMapper() ).forEach(exceptionMapper -> { environment.jersey().register(exceptionMapper); webSocketEnvironment.jersey().register(exceptionMapper); provisioningEnvironment.jersey().register(exceptionMapper); }); } private void registerCorsFilter(Environment environment) { FilterRegistration.Dynamic filter = environment.servlets().addFilter("CORS", CrossOriginFilter.class); filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*"); filter.setInitParameter("allowedOrigins", "*"); filter.setInitParameter("allowedHeaders", "Content-Type,Authorization,X-Requested-With,Content-Length,Accept,Origin,X-Signal-Agent"); filter.setInitParameter("allowedMethods", "GET,PUT,POST,DELETE,OPTIONS"); filter.setInitParameter("preflightMaxAge", "5184000"); filter.setInitParameter("allowCredentials", "true"); } public static void main(String[] args) throws Exception { new WhisperServerService().run(args); } }