mirror of
https://github.com/signalapp/Signal-Server.git
synced 2025-12-15 02:00:48 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb4ed20ff5 | ||
|
|
cb50b44d8f | ||
|
|
ae57853ec4 | ||
|
|
2881c0fd7e | ||
|
|
483fb0968b | ||
|
|
4d37418c15 | ||
|
|
e8ee4b50ff | ||
|
|
4f8aa2eee2 |
Submodule abusive-message-filter updated: 1df3bf41f6...51ddf2a890
@@ -163,7 +163,6 @@ import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator;
|
||||
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
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;
|
||||
@@ -404,6 +403,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
ExecutorService fcmSenderExecutor = environment.lifecycle().executorService(name(getClass(), "fcmSender-%d")).maxThreads(32).minThreads(32).workQueue(fcmSenderQueue).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 accountDeletionExecutor = environment.lifecycle().executorService(name(getClass(), "accountCleaner-%d")).maxThreads(16).minThreads(16).build();
|
||||
|
||||
// TODO: generally speaking this is a DynamoDB I/O executor for the accounts table; we should eventually have a general executor for speaking to the accounts table, but most of the server is still synchronous so this isn't widely useful yet
|
||||
ExecutorService batchIdentityCheckExecutor = environment.lifecycle().executorService(name(getClass(), "batchIdentityCheck-%d")).minThreads(32).maxThreads(32).build();
|
||||
@@ -457,7 +457,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
ExternalServiceCredentialGenerator paymentsCredentialsGenerator = new ExternalServiceCredentialGenerator(
|
||||
config.getPaymentsServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true);
|
||||
|
||||
AbusiveHostRules abusiveHostRules = new AbusiveHostRules(rateLimitersCluster, dynamicConfigurationManager);
|
||||
RegistrationServiceClient registrationServiceClient = new RegistrationServiceClient(config.getRegistrationServiceConfiguration().getHost(), config.getRegistrationServiceConfiguration().getPort(), config.getRegistrationServiceConfiguration().getApiKey(), config.getRegistrationServiceConfiguration().getRegistrationCaCertificate(), registrationCallbackExecutor);
|
||||
SecureBackupClient secureBackupClient = new SecureBackupClient(backupCredentialsGenerator, backupServiceExecutor, config.getSecureBackupServiceConfiguration());
|
||||
SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, storageServiceExecutor, config.getSecureStorageServiceConfiguration());
|
||||
@@ -554,7 +553,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
new AccountDatabaseCrawlerCache(cacheCluster, AccountDatabaseCrawlerCache.ACCOUNT_CLEANER_PREFIX);
|
||||
AccountDatabaseCrawler accountCleanerAccountDatabaseCrawler = new AccountDatabaseCrawler("Account cleaner crawler",
|
||||
accountsManager,
|
||||
accountCleanerAccountDatabaseCrawlerCache, List.of(new AccountCleaner(accountsManager)),
|
||||
accountCleanerAccountDatabaseCrawlerCache, List.of(new AccountCleaner(accountsManager, accountDeletionExecutor)),
|
||||
config.getAccountDatabaseCrawlerConfiguration().getChunkSize(),
|
||||
config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs()
|
||||
);
|
||||
@@ -653,7 +652,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
|
||||
// these should be common, but use @Auth DisabledPermittedAccount, which isn’t supported yet on websocket
|
||||
environment.jersey().register(
|
||||
new AccountController(pendingAccountsManager, accountsManager, abusiveHostRules, rateLimiters,
|
||||
new AccountController(pendingAccountsManager, accountsManager, rateLimiters,
|
||||
registrationServiceClient, dynamicConfigurationManager, turnTokenGenerator, config.getTestDevices(),
|
||||
recaptchaClient, pushNotificationManager, changeNumberManager, backupCredentialsGenerator,
|
||||
clientPresenceManager, clock));
|
||||
|
||||
@@ -91,7 +91,6 @@ import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
|
||||
import org.whispersystems.textsecuregcm.registration.ClientType;
|
||||
import org.whispersystems.textsecuregcm.registration.MessageTransport;
|
||||
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
|
||||
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.ChangeNumberManager;
|
||||
@@ -115,8 +114,6 @@ public class AccountController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AccountController.class);
|
||||
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
||||
private final Meter blockedHostMeter = metricRegistry.meter(name(AccountController.class, "blocked_host" ));
|
||||
private final Meter countryFilterApplicable = metricRegistry.meter(name(AccountController.class, "country_filter_applicable"));
|
||||
private final Meter countryFilteredHostMeter = metricRegistry.meter(name(AccountController.class, "country_limited_host" ));
|
||||
private final Meter rateLimitedHostMeter = metricRegistry.meter(name(AccountController.class, "rate_limited_host" ));
|
||||
private final Meter rateLimitedPrefixMeter = metricRegistry.meter(name(AccountController.class, "rate_limited_prefix" ));
|
||||
@@ -150,7 +147,6 @@ public class AccountController {
|
||||
|
||||
private final StoredVerificationCodeManager pendingAccounts;
|
||||
private final AccountsManager accounts;
|
||||
private final AbusiveHostRules abusiveHostRules;
|
||||
private final RateLimiters rateLimiters;
|
||||
private final RegistrationServiceClient registrationServiceClient;
|
||||
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
|
||||
@@ -171,7 +167,6 @@ public class AccountController {
|
||||
public AccountController(
|
||||
StoredVerificationCodeManager pendingAccounts,
|
||||
AccountsManager accounts,
|
||||
AbusiveHostRules abusiveHostRules,
|
||||
RateLimiters rateLimiters,
|
||||
RegistrationServiceClient registrationServiceClient,
|
||||
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
|
||||
@@ -186,7 +181,6 @@ public class AccountController {
|
||||
) {
|
||||
this.pendingAccounts = pendingAccounts;
|
||||
this.accounts = accounts;
|
||||
this.abusiveHostRules = abusiveHostRules;
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.registrationServiceClient = registrationServiceClient;
|
||||
this.dynamicConfigurationManager = dynamicConfigurationManager;
|
||||
@@ -204,7 +198,6 @@ public class AccountController {
|
||||
public AccountController(
|
||||
StoredVerificationCodeManager pendingAccounts,
|
||||
AccountsManager accounts,
|
||||
AbusiveHostRules abusiveHostRules,
|
||||
RateLimiters rateLimiters,
|
||||
RegistrationServiceClient registrationServiceClient,
|
||||
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
|
||||
@@ -215,7 +208,7 @@ public class AccountController {
|
||||
ChangeNumberManager changeNumberManager,
|
||||
ExternalServiceCredentialGenerator backupServiceCredentialGenerator
|
||||
) {
|
||||
this(pendingAccounts, accounts, abusiveHostRules, rateLimiters,
|
||||
this(pendingAccounts, accounts, rateLimiters,
|
||||
registrationServiceClient, dynamicConfigurationManager, turnTokenGenerator, testDevices, recaptchaClient,
|
||||
pushNotificationManager, changeNumberManager,
|
||||
backupServiceCredentialGenerator, null, Clock.systemUTC());
|
||||
@@ -886,26 +879,12 @@ public class AccountController {
|
||||
boolean countryFiltered = captchaConfig.getSignupCountryCodes().contains(countryCode) ||
|
||||
captchaConfig.getSignupRegions().contains(region);
|
||||
|
||||
if (abusiveHostRules.isBlocked(sourceHost)) {
|
||||
blockedHostMeter.mark();
|
||||
logger.info("Blocked host: {}, {}, {} ({})", transport, number, sourceHost, forwardedFor);
|
||||
if (countryFiltered) {
|
||||
// this host was caught in the abusiveHostRules filter, but
|
||||
// would be caught by country filter as well
|
||||
countryFilterApplicable.mark();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
rateLimiters.getSmsVoiceIpLimiter().validate(sourceHost);
|
||||
} catch (RateLimitExceededException e) {
|
||||
logger.info("Rate limit exceeded: {}, {}, {} ({})", transport, number, sourceHost, forwardedFor);
|
||||
rateLimitedHostMeter.mark();
|
||||
if (shouldAutoBlock(sourceHost)) {
|
||||
logger.info("Auto-block: {}", sourceHost);
|
||||
abusiveHostRules.setBlockedHost(sourceHost);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -914,10 +893,7 @@ public class AccountController {
|
||||
} catch (RateLimitExceededException e) {
|
||||
logger.info("Prefix rate limit exceeded: {}, {}, {} ({})", transport, number, sourceHost, forwardedFor);
|
||||
rateLimitedPrefixMeter.mark();
|
||||
if (shouldAutoBlock(sourceHost)) {
|
||||
logger.info("Auto-block: {}", sourceHost);
|
||||
abusiveHostRules.setBlockedHost(sourceHost);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -925,6 +901,7 @@ public class AccountController {
|
||||
countryFilteredHostMeter.mark();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -944,16 +921,6 @@ public class AccountController {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldAutoBlock(String sourceHost) {
|
||||
try {
|
||||
rateLimiters.getAutoBlockLimiter().validate(sourceHost);
|
||||
} catch (RateLimitExceededException e) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private String generatePushChallenge() {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] challenge = new byte[16];
|
||||
|
||||
@@ -188,7 +188,7 @@ public class DeviceController {
|
||||
}
|
||||
|
||||
final DeviceCapabilities capabilities = accountAttributes.getCapabilities();
|
||||
if (capabilities != null && isCapabilityDowngrade(account.get(), capabilities, userAgent)) {
|
||||
if (capabilities != null && isCapabilityDowngrade(account.get(), capabilities)) {
|
||||
throw new WebApplicationException(Response.status(409).build());
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ public class DeviceController {
|
||||
return new VerificationCode(randomInt);
|
||||
}
|
||||
|
||||
private boolean isCapabilityDowngrade(Account account, DeviceCapabilities capabilities, String userAgent) {
|
||||
private boolean isCapabilityDowngrade(Account account, DeviceCapabilities capabilities) {
|
||||
boolean isDowngrade = false;
|
||||
|
||||
isDowngrade |= account.isStoriesSupported() && !capabilities.isStories();
|
||||
@@ -244,35 +244,8 @@ public class DeviceController {
|
||||
isDowngrade |= account.isChangeNumberSupported() && !capabilities.isChangeNumber();
|
||||
isDowngrade |= account.isAnnouncementGroupSupported() && !capabilities.isAnnouncementGroup();
|
||||
isDowngrade |= account.isSenderKeySupported() && !capabilities.isSenderKey();
|
||||
isDowngrade |= account.isGv1MigrationSupported() && !capabilities.isGv1Migration();
|
||||
isDowngrade |= account.isGiftBadgesSupported() && !capabilities.isGiftBadges();
|
||||
|
||||
if (account.isGroupsV2Supported()) {
|
||||
try {
|
||||
switch (UserAgentUtil.parseUserAgentString(userAgent).getPlatform()) {
|
||||
case DESKTOP:
|
||||
case ANDROID: {
|
||||
if (!capabilities.isGv2_3()) {
|
||||
isDowngrade = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case IOS: {
|
||||
if (!capabilities.isGv2_2() && !capabilities.isGv2_3()) {
|
||||
isDowngrade = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (final UnrecognizedUserAgentException e) {
|
||||
// If we can't parse the UA string, the client is for sure too old to support groups V2
|
||||
isDowngrade = true;
|
||||
}
|
||||
}
|
||||
|
||||
return isDowngrade;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,18 +194,22 @@ public class SubscriptionController {
|
||||
levels.put(String.valueOf(levelId), levelConfiguration);
|
||||
});
|
||||
|
||||
final Badge boostBadge = badgeTranslator.translate(acceptableLanguages,
|
||||
oneTimeDonationConfiguration.boost().badge());
|
||||
levels.put(String.valueOf(oneTimeDonationConfiguration.boost().level()),
|
||||
new LevelConfiguration(
|
||||
levelTranslator.translate(acceptableLanguages, oneTimeDonationConfiguration.boost().badge()),
|
||||
boostBadge.getName(),
|
||||
// NB: the one-time badges are PurchasableBadge, which has a `duration` field
|
||||
new PurchasableBadge(
|
||||
badgeTranslator.translate(acceptableLanguages, oneTimeDonationConfiguration.boost().badge()),
|
||||
boostBadge,
|
||||
oneTimeDonationConfiguration.boost().expiration())));
|
||||
|
||||
final Badge giftBadge = badgeTranslator.translate(acceptableLanguages, oneTimeDonationConfiguration.gift().badge());
|
||||
levels.put(String.valueOf(oneTimeDonationConfiguration.gift().level()),
|
||||
new LevelConfiguration(
|
||||
levelTranslator.translate(acceptableLanguages, oneTimeDonationConfiguration.gift().badge()),
|
||||
giftBadge.getName(),
|
||||
new PurchasableBadge(
|
||||
badgeTranslator.translate(acceptableLanguages, oneTimeDonationConfiguration.gift().badge()),
|
||||
giftBadge,
|
||||
oneTimeDonationConfiguration.gift().expiration())));
|
||||
|
||||
return new GetSubscriptionConfigurationResponse(currencyConfiguration, levels);
|
||||
|
||||
@@ -12,8 +12,7 @@ public class UserCapabilities {
|
||||
|
||||
public static UserCapabilities createForAccount(Account account) {
|
||||
return new UserCapabilities(
|
||||
account.isGroupsV2Supported(),
|
||||
account.isGv1MigrationSupported(),
|
||||
true,
|
||||
account.isSenderKeySupported(),
|
||||
account.isAnnouncementGroupSupported(),
|
||||
account.isChangeNumberSupported(),
|
||||
@@ -21,9 +20,6 @@ public class UserCapabilities {
|
||||
account.isGiftBadgesSupported());
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
private boolean gv2;
|
||||
|
||||
@JsonProperty("gv1-migration")
|
||||
private boolean gv1Migration;
|
||||
|
||||
@@ -45,7 +41,7 @@ public class UserCapabilities {
|
||||
public UserCapabilities() {
|
||||
}
|
||||
|
||||
public UserCapabilities(final boolean gv2,
|
||||
public UserCapabilities(
|
||||
boolean gv1Migration,
|
||||
final boolean senderKey,
|
||||
final boolean announcementGroup,
|
||||
@@ -53,7 +49,6 @@ public class UserCapabilities {
|
||||
final boolean stories,
|
||||
final boolean giftBadges) {
|
||||
|
||||
this.gv2 = gv2;
|
||||
this.gv1Migration = gv1Migration;
|
||||
this.senderKey = senderKey;
|
||||
this.announcementGroup = announcementGroup;
|
||||
@@ -62,10 +57,6 @@ public class UserCapabilities {
|
||||
this.giftBadges = giftBadges;
|
||||
}
|
||||
|
||||
public boolean isGv2() {
|
||||
return gv2;
|
||||
}
|
||||
|
||||
public boolean isGv1Migration() {
|
||||
return gv1Migration;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ public class RateLimiters {
|
||||
private final RateLimiter voiceDestinationDailyLimiter;
|
||||
private final RateLimiter smsVoiceIpLimiter;
|
||||
private final RateLimiter smsVoicePrefixLimiter;
|
||||
private final RateLimiter autoBlockLimiter;
|
||||
private final RateLimiter verifyLimiter;
|
||||
private final RateLimiter pinLimiter;
|
||||
|
||||
@@ -60,10 +59,6 @@ public class RateLimiters {
|
||||
config.getSmsVoicePrefix().getBucketSize(),
|
||||
config.getSmsVoicePrefix().getLeakRatePerMinute());
|
||||
|
||||
this.autoBlockLimiter = new RateLimiter(cacheCluster, "autoBlock",
|
||||
config.getAutoBlock().getBucketSize(),
|
||||
config.getAutoBlock().getLeakRatePerMinute());
|
||||
|
||||
this.verifyLimiter = new LockingRateLimiter(cacheCluster, "verify",
|
||||
config.getVerifyNumber().getBucketSize(),
|
||||
config.getVerifyNumber().getLeakRatePerMinute());
|
||||
@@ -158,10 +153,6 @@ public class RateLimiters {
|
||||
return smsVoicePrefixLimiter;
|
||||
}
|
||||
|
||||
public RateLimiter getAutoBlockLimiter() {
|
||||
return autoBlockLimiter;
|
||||
}
|
||||
|
||||
public RateLimiter getVoiceDestinationLimiter() {
|
||||
return voiceDestinationLimiter;
|
||||
}
|
||||
|
||||
@@ -1,52 +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.time.Duration;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
|
||||
import org.whispersystems.textsecuregcm.util.Constants;
|
||||
|
||||
public class AbusiveHostRules {
|
||||
|
||||
private static final String KEY_PREFIX = "abusive_hosts::";
|
||||
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
||||
private final Timer getTimer = metricRegistry.timer(name(AbusiveHostRules.class, "get"));
|
||||
private final Timer insertTimer = metricRegistry.timer(name(AbusiveHostRules.class, "setBlockedHost"));
|
||||
|
||||
private final FaultTolerantRedisCluster redisCluster;
|
||||
private final DynamicConfigurationManager<DynamicConfiguration> configurationManager;
|
||||
|
||||
public AbusiveHostRules(FaultTolerantRedisCluster redisCluster, final DynamicConfigurationManager<DynamicConfiguration> configurationManager) {
|
||||
this.redisCluster = redisCluster;
|
||||
this.configurationManager = configurationManager;
|
||||
}
|
||||
|
||||
public boolean isBlocked(String host) {
|
||||
try (Timer.Context timer = getTimer.time()) {
|
||||
return this.redisCluster.withCluster(connection -> connection.sync().exists(prefix(host))) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void setBlockedHost(String host) {
|
||||
Duration expireTime = configurationManager.getConfiguration().getAbusiveHostRules().getExpirationTime();
|
||||
try (Timer.Context timer = insertTimer.time()) {
|
||||
this.redisCluster.useCluster(connection -> connection.sync().setex(prefix(host), expireTime.toSeconds(), "1"));
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static String prefix(String keyName) {
|
||||
return KEY_PREFIX + keyName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -181,14 +181,6 @@ public class Account {
|
||||
return devices.stream().filter(device -> device.getId() == deviceId).findFirst();
|
||||
}
|
||||
|
||||
public boolean isGroupsV2Supported() {
|
||||
requireNotStale();
|
||||
|
||||
return devices.stream()
|
||||
.filter(Device::isEnabled)
|
||||
.allMatch(Device::isGroupsV2Supported);
|
||||
}
|
||||
|
||||
public boolean isStorageSupported() {
|
||||
requireNotStale();
|
||||
|
||||
@@ -201,10 +193,6 @@ public class Account {
|
||||
return getMasterDevice().map(Device::getCapabilities).map(Device.DeviceCapabilities::isTransfer).orElse(false);
|
||||
}
|
||||
|
||||
public boolean isGv1MigrationSupported() {
|
||||
return allEnabledDevicesHaveCapability(DeviceCapabilities::isGv1Migration);
|
||||
}
|
||||
|
||||
public boolean isSenderKeySupported() {
|
||||
return allEnabledDevicesHaveCapability(DeviceCapabilities::isSenderKey);
|
||||
}
|
||||
|
||||
@@ -4,20 +4,21 @@
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
|
||||
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
|
||||
|
||||
public class AccountCleaner extends AccountDatabaseCrawlerListener {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AccountCleaner.class);
|
||||
@@ -25,13 +26,12 @@ public class AccountCleaner extends AccountDatabaseCrawlerListener {
|
||||
private static final String DELETED_ACCOUNT_COUNTER_NAME = name(AccountCleaner.class, "deletedAccounts");
|
||||
private static final String DELETION_REASON_TAG_NAME = "reason";
|
||||
|
||||
@VisibleForTesting
|
||||
static final int MAX_ACCOUNT_DELETIONS_PER_CHUNK = 256;
|
||||
|
||||
private final AccountsManager accountsManager;
|
||||
private final Executor deletionExecutor;
|
||||
|
||||
public AccountCleaner(AccountsManager accountsManager) {
|
||||
public AccountCleaner(final AccountsManager accountsManager, final Executor deletionExecutor) {
|
||||
this.accountsManager = accountsManager;
|
||||
this.deletionExecutor = deletionExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,30 +44,34 @@ public class AccountCleaner extends AccountDatabaseCrawlerListener {
|
||||
|
||||
@Override
|
||||
protected void onCrawlChunk(Optional<UUID> fromUuid, List<Account> chunkAccounts) {
|
||||
int accountUpdateCount = 0;
|
||||
final List<CompletableFuture<Void>> deletionFutures = new ArrayList<>();
|
||||
|
||||
for (Account account : chunkAccounts) {
|
||||
if (isExpired(account) || needsExplicitRemoval(account)) {
|
||||
final Tag deletionReason;
|
||||
final String deletionReason = needsExplicitRemoval(account) ? "newlyExpired" : "previouslyExpired";
|
||||
|
||||
if (needsExplicitRemoval(account)) {
|
||||
deletionReason = Tag.of(DELETION_REASON_TAG_NAME, "newlyExpired");
|
||||
} else {
|
||||
deletionReason = Tag.of(DELETION_REASON_TAG_NAME, "previouslyExpired");
|
||||
}
|
||||
|
||||
if (accountUpdateCount < MAX_ACCOUNT_DELETIONS_PER_CHUNK) {
|
||||
try {
|
||||
accountsManager.delete(account, AccountsManager.DeletionReason.EXPIRED);
|
||||
accountUpdateCount++;
|
||||
|
||||
Metrics.counter(DELETED_ACCOUNT_COUNTER_NAME, Tags.of(deletionReason)).increment();
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to delete account {}", account.getUuid(), e);
|
||||
}
|
||||
}
|
||||
deletionFutures.add(CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
accountsManager.delete(account, AccountsManager.DeletionReason.EXPIRED);
|
||||
} catch (final InterruptedException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}, deletionExecutor)
|
||||
.whenComplete((ignored, throwable) -> {
|
||||
if (throwable != null) {
|
||||
log.warn("Failed to delete account {}", account.getUuid(), throwable);
|
||||
} else {
|
||||
Metrics.counter(DELETED_ACCOUNT_COUNTER_NAME, DELETION_REASON_TAG_NAME, deletionReason).increment();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
CompletableFuture.allOf(deletionFutures.toArray(new CompletableFuture[0])).join();
|
||||
} catch (final Exception e) {
|
||||
log.debug("Failed to delete one or more accounts in chunk", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean needsExplicitRemoval(Account account) {
|
||||
|
||||
@@ -252,39 +252,13 @@ public class Device {
|
||||
return this.userAgent;
|
||||
}
|
||||
|
||||
public boolean isGroupsV2Supported() {
|
||||
final boolean groupsV2Supported;
|
||||
|
||||
if (this.capabilities != null) {
|
||||
final boolean ios = this.apnId != null || this.voipApnId != null;
|
||||
|
||||
groupsV2Supported = this.capabilities.isGv2_3() || (ios && this.capabilities.isGv2_2());
|
||||
} else {
|
||||
groupsV2Supported = false;
|
||||
}
|
||||
|
||||
return groupsV2Supported;
|
||||
}
|
||||
|
||||
public static class DeviceCapabilities {
|
||||
@JsonProperty
|
||||
private boolean gv2;
|
||||
|
||||
@JsonProperty("gv2-2")
|
||||
private boolean gv2_2;
|
||||
|
||||
@JsonProperty("gv2-3")
|
||||
private boolean gv2_3;
|
||||
|
||||
@JsonProperty
|
||||
private boolean storage;
|
||||
|
||||
@JsonProperty
|
||||
private boolean transfer;
|
||||
|
||||
@JsonProperty("gv1-migration")
|
||||
private boolean gv1Migration;
|
||||
|
||||
@JsonProperty
|
||||
private boolean senderKey;
|
||||
|
||||
@@ -306,15 +280,11 @@ public class Device {
|
||||
public DeviceCapabilities() {
|
||||
}
|
||||
|
||||
public DeviceCapabilities(boolean gv2, final boolean gv2_2, final boolean gv2_3, boolean storage, boolean transfer,
|
||||
boolean gv1Migration, final boolean senderKey, final boolean announcementGroup, final boolean changeNumber,
|
||||
public DeviceCapabilities(boolean storage, boolean transfer,
|
||||
final boolean senderKey, final boolean announcementGroup, final boolean changeNumber,
|
||||
final boolean pni, final boolean stories, final boolean giftBadges) {
|
||||
this.gv2 = gv2;
|
||||
this.gv2_2 = gv2_2;
|
||||
this.gv2_3 = gv2_3;
|
||||
this.storage = storage;
|
||||
this.transfer = transfer;
|
||||
this.gv1Migration = gv1Migration;
|
||||
this.senderKey = senderKey;
|
||||
this.announcementGroup = announcementGroup;
|
||||
this.changeNumber = changeNumber;
|
||||
@@ -323,18 +293,6 @@ public class Device {
|
||||
this.giftBadges = giftBadges;
|
||||
}
|
||||
|
||||
public boolean isGv2() {
|
||||
return gv2;
|
||||
}
|
||||
|
||||
public boolean isGv2_2() {
|
||||
return gv2_2;
|
||||
}
|
||||
|
||||
public boolean isGv2_3() {
|
||||
return gv2_3;
|
||||
}
|
||||
|
||||
public boolean isStorage() {
|
||||
return storage;
|
||||
}
|
||||
@@ -343,10 +301,6 @@ public class Device {
|
||||
return transfer;
|
||||
}
|
||||
|
||||
public boolean isGv1Migration() {
|
||||
return gv1Migration;
|
||||
}
|
||||
|
||||
public boolean isSenderKey() {
|
||||
return senderKey;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@@ -100,7 +99,6 @@ import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
|
||||
import org.whispersystems.textsecuregcm.registration.ClientType;
|
||||
import org.whispersystems.textsecuregcm.registration.MessageTransport;
|
||||
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
|
||||
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.ChangeNumberManager;
|
||||
@@ -134,7 +132,6 @@ class AccountControllerTest {
|
||||
private static final UUID SENDER_TRANSFER_UUID = UUID.randomUUID();
|
||||
private static final UUID RESERVATION_TOKEN = UUID.randomUUID();
|
||||
|
||||
private static final String ABUSIVE_HOST = "192.168.1.1";
|
||||
private static final String NICE_HOST = "127.0.0.1";
|
||||
private static final String RATE_LIMITED_IP_HOST = "10.0.0.1";
|
||||
private static final String RATE_LIMITED_PREFIX_HOST = "10.0.0.2";
|
||||
@@ -147,7 +144,6 @@ class AccountControllerTest {
|
||||
|
||||
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);
|
||||
private static RateLimiter rateLimiter = mock(RateLimiter.class);
|
||||
private static RateLimiter pinLimiter = mock(RateLimiter.class);
|
||||
@@ -187,7 +183,6 @@ class AccountControllerTest {
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new AccountController(pendingAccountsManager,
|
||||
accountsManager,
|
||||
abusiveHostRules,
|
||||
rateLimiters,
|
||||
registrationServiceClient,
|
||||
dynamicConfigurationManager,
|
||||
@@ -218,7 +213,6 @@ class AccountControllerTest {
|
||||
when(rateLimiters.getPinLimiter()).thenReturn(pinLimiter);
|
||||
when(rateLimiters.getSmsVoiceIpLimiter()).thenReturn(smsVoiceIpLimiter);
|
||||
when(rateLimiters.getSmsVoicePrefixLimiter()).thenReturn(smsVoicePrefixLimiter);
|
||||
when(rateLimiters.getAutoBlockLimiter()).thenReturn(autoBlockLimiter);
|
||||
when(rateLimiters.getUsernameSetLimiter()).thenReturn(usernameSetLimiter);
|
||||
when(rateLimiters.getUsernameReserveLimiter()).thenReturn(usernameReserveLimiter);
|
||||
when(rateLimiters.getUsernameLookupLimiter()).thenReturn(usernameLookupLimiter);
|
||||
@@ -303,9 +297,6 @@ class AccountControllerTest {
|
||||
|
||||
when(dynamicConfiguration.getCaptchaConfiguration()).thenReturn(signupCaptchaConfig);
|
||||
}
|
||||
when(abusiveHostRules.isBlocked(eq(ABUSIVE_HOST))).thenReturn(true);
|
||||
when(abusiveHostRules.isBlocked(eq(NICE_HOST))).thenReturn(false);
|
||||
|
||||
when(recaptchaClient.verify(eq(INVALID_CAPTCHA_TOKEN), anyString()))
|
||||
.thenReturn(RecaptchaClient.AssessmentResult.invalid());
|
||||
when(recaptchaClient.verify(eq(VALID_CAPTCHA_TOKEN), anyString()))
|
||||
@@ -313,9 +304,6 @@ class AccountControllerTest {
|
||||
|
||||
doThrow(new RateLimitExceededException(Duration.ZERO)).when(pinLimiter).validate(eq(SENDER_OVER_PIN));
|
||||
|
||||
doThrow(new RateLimitExceededException(Duration.ZERO)).when(autoBlockLimiter).validate(eq(RATE_LIMITED_PREFIX_HOST));
|
||||
doThrow(new RateLimitExceededException(Duration.ZERO)).when(autoBlockLimiter).validate(eq(RATE_LIMITED_IP_HOST));
|
||||
|
||||
doThrow(new RateLimitExceededException(Duration.ZERO)).when(smsVoicePrefixLimiter).validate(SENDER_OVER_PREFIX.substring(0, 4+2));
|
||||
doThrow(new RateLimitExceededException(Duration.ZERO)).when(smsVoiceIpLimiter).validate(RATE_LIMITED_IP_HOST);
|
||||
doThrow(new RateLimitExceededException(Duration.ZERO)).when(smsVoiceIpLimiter).validate(RATE_LIMITED_HOST2);
|
||||
@@ -326,13 +314,11 @@ class AccountControllerTest {
|
||||
reset(
|
||||
pendingAccountsManager,
|
||||
accountsManager,
|
||||
abusiveHostRules,
|
||||
rateLimiters,
|
||||
rateLimiter,
|
||||
pinLimiter,
|
||||
smsVoiceIpLimiter,
|
||||
smsVoicePrefixLimiter,
|
||||
autoBlockLimiter,
|
||||
usernameSetLimiter,
|
||||
usernameReserveLimiter,
|
||||
usernameLookupLimiter,
|
||||
@@ -489,7 +475,6 @@ class AccountControllerTest {
|
||||
final Phonenumber.PhoneNumber expectedPhoneNumber = PhoneNumberUtil.getInstance().parse(SENDER, null);
|
||||
|
||||
verify(registrationServiceClient).sendRegistrationCode(expectedPhoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
|
||||
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
|
||||
verify(pendingAccountsManager).store(eq(SENDER), argThat(
|
||||
storedVerificationCode -> Arrays.equals(storedVerificationCode.sessionId(), sessionId) &&
|
||||
"1234-push".equals(storedVerificationCode.pushCode())));
|
||||
@@ -550,7 +535,6 @@ class AccountControllerTest {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.VOICE, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
|
||||
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -572,7 +556,6 @@ class AccountControllerTest {
|
||||
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER_PREAUTH, null);
|
||||
|
||||
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
|
||||
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -588,7 +571,6 @@ class AccountControllerTest {
|
||||
assertThat(response.getStatus()).isEqualTo(403);
|
||||
|
||||
verifyNoInteractions(registrationServiceClient);
|
||||
verifyNoInteractions(abusiveHostRules);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -649,24 +631,7 @@ class AccountControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendAbusiveHost() {
|
||||
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.queryParam("challenge", "1234-push")
|
||||
.request()
|
||||
.header(HttpHeaders.X_FORWARDED_FOR, ABUSIVE_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verify(abusiveHostRules).isBlocked(eq(ABUSIVE_HOST));
|
||||
verifyNoInteractions(registrationServiceClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendAbusiveHostWithValidCaptcha() throws NumberParseException {
|
||||
void testSendWithValidCaptcha() throws NumberParseException {
|
||||
|
||||
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(new byte[16]));
|
||||
@@ -676,38 +641,36 @@ class AccountControllerTest {
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.queryParam("captcha", VALID_CAPTCHA_TOKEN)
|
||||
.request()
|
||||
.header(HttpHeaders.X_FORWARDED_FOR, ABUSIVE_HOST)
|
||||
.header("X-Forwarded-For", NICE_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
|
||||
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER, null);
|
||||
|
||||
verifyNoInteractions(abusiveHostRules);
|
||||
verify(recaptchaClient).verify(eq(VALID_CAPTCHA_TOKEN), eq(ABUSIVE_HOST));
|
||||
verify(recaptchaClient).verify(eq(VALID_CAPTCHA_TOKEN), eq(NICE_HOST));
|
||||
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendAbusiveHostWithInvalidCaptcha() {
|
||||
void testSendWithInvalidCaptcha() {
|
||||
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.queryParam("captcha", INVALID_CAPTCHA_TOKEN)
|
||||
.request()
|
||||
.header(HttpHeaders.X_FORWARDED_FOR, ABUSIVE_HOST)
|
||||
.header("X-Forwarded-For", NICE_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verifyNoInteractions(abusiveHostRules);
|
||||
verify(recaptchaClient).verify(eq(INVALID_CAPTCHA_TOKEN), eq(ABUSIVE_HOST));
|
||||
verify(recaptchaClient).verify(eq(INVALID_CAPTCHA_TOKEN), eq(NICE_HOST));
|
||||
verifyNoInteractions(registrationServiceClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendRateLimitedHostAutoBlock() {
|
||||
void testSendRateLimitedHost() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
@@ -718,10 +681,6 @@ class AccountControllerTest {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verify(abusiveHostRules).isBlocked(eq(RATE_LIMITED_IP_HOST));
|
||||
verify(abusiveHostRules).setBlockedHost(eq(RATE_LIMITED_IP_HOST));
|
||||
verifyNoMoreInteractions(abusiveHostRules);
|
||||
|
||||
verifyNoInteractions(recaptchaClient);
|
||||
verifyNoInteractions(registrationServiceClient);
|
||||
}
|
||||
@@ -739,55 +698,10 @@ class AccountControllerTest {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verify(abusiveHostRules).isBlocked(eq(RATE_LIMITED_PREFIX_HOST));
|
||||
verify(abusiveHostRules).setBlockedHost(eq(RATE_LIMITED_PREFIX_HOST));
|
||||
verifyNoMoreInteractions(abusiveHostRules);
|
||||
|
||||
verifyNoInteractions(recaptchaClient);
|
||||
verifyNoInteractions(registrationServiceClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendRateLimitedHostNoAutoBlock() {
|
||||
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.queryParam("challenge", "1234-push")
|
||||
.request()
|
||||
.header(HttpHeaders.X_FORWARDED_FOR, RATE_LIMITED_HOST2)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verify(abusiveHostRules).isBlocked(eq(RATE_LIMITED_HOST2));
|
||||
verifyNoMoreInteractions(abusiveHostRules);
|
||||
|
||||
verifyNoInteractions(recaptchaClient);
|
||||
verifyNoInteractions(registrationServiceClient);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSendMultipleHost() {
|
||||
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.queryParam("challenge", "1234-push")
|
||||
.request()
|
||||
.header(HttpHeaders.X_FORWARDED_FOR, NICE_HOST + ", " + ABUSIVE_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verify(abusiveHostRules, times(1)).isBlocked(eq(ABUSIVE_HOST));
|
||||
|
||||
verifyNoMoreInteractions(abusiveHostRules);
|
||||
verifyNoInteractions(registrationServiceClient);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSendRestrictedHostOut() {
|
||||
|
||||
@@ -805,7 +719,6 @@ class AccountControllerTest {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
|
||||
verifyNoInteractions(registrationServiceClient);
|
||||
}
|
||||
|
||||
@@ -884,7 +797,7 @@ class AccountControllerTest {
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", TEST_NUMBER))
|
||||
.request()
|
||||
.header(HttpHeaders.X_FORWARDED_FOR, ABUSIVE_HOST)
|
||||
.header("X-Forwarded-For", RATE_LIMITED_IP_HOST)
|
||||
.get();
|
||||
|
||||
final ArgumentCaptor<StoredVerificationCode> captor = ArgumentCaptor.forClass(StoredVerificationCode.class);
|
||||
|
||||
@@ -193,8 +193,6 @@ class ProfileControllerTest {
|
||||
when(profileAccount.getUuid()).thenReturn(AuthHelper.VALID_UUID_TWO);
|
||||
when(profileAccount.getPhoneNumberIdentifier()).thenReturn(AuthHelper.VALID_PNI_TWO);
|
||||
when(profileAccount.isEnabled()).thenReturn(true);
|
||||
when(profileAccount.isGroupsV2Supported()).thenReturn(false);
|
||||
when(profileAccount.isGv1MigrationSupported()).thenReturn(false);
|
||||
when(profileAccount.isSenderKeySupported()).thenReturn(false);
|
||||
when(profileAccount.isAnnouncementGroupSupported()).thenReturn(false);
|
||||
when(profileAccount.isChangeNumberSupported()).thenReturn(false);
|
||||
@@ -207,8 +205,6 @@ class ProfileControllerTest {
|
||||
when(capabilitiesAccount.getIdentityKey()).thenReturn(ACCOUNT_IDENTITY_KEY);
|
||||
when(capabilitiesAccount.getPhoneNumberIdentityKey()).thenReturn(ACCOUNT_PHONE_NUMBER_IDENTITY_KEY);
|
||||
when(capabilitiesAccount.isEnabled()).thenReturn(true);
|
||||
when(capabilitiesAccount.isGroupsV2Supported()).thenReturn(true);
|
||||
when(capabilitiesAccount.isGv1MigrationSupported()).thenReturn(true);
|
||||
when(capabilitiesAccount.isSenderKeySupported()).thenReturn(true);
|
||||
when(capabilitiesAccount.isAnnouncementGroupSupported()).thenReturn(true);
|
||||
when(capabilitiesAccount.isChangeNumberSupported()).thenReturn(true);
|
||||
@@ -396,7 +392,6 @@ class ProfileControllerTest {
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get(BaseProfileResponse.class);
|
||||
|
||||
assertThat(profile.getCapabilities().isGv2()).isTrue();
|
||||
assertThat(profile.getCapabilities().isGv1Migration()).isTrue();
|
||||
assertThat(profile.getCapabilities().isSenderKey()).isTrue();
|
||||
assertThat(profile.getCapabilities().isAnnouncementGroup()).isTrue();
|
||||
@@ -408,8 +403,7 @@ class ProfileControllerTest {
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO))
|
||||
.get(BaseProfileResponse.class);
|
||||
|
||||
assertThat(profile.getCapabilities().isGv2()).isFalse();
|
||||
assertThat(profile.getCapabilities().isGv1Migration()).isFalse();
|
||||
assertThat(profile.getCapabilities().isGv1Migration()).isTrue();
|
||||
assertThat(profile.getCapabilities().isSenderKey()).isFalse();
|
||||
assertThat(profile.getCapabilities().isAnnouncementGroup()).isFalse();
|
||||
}
|
||||
@@ -753,8 +747,7 @@ class ProfileControllerTest {
|
||||
assertThat(profile.getAbout()).isEqualTo("about");
|
||||
assertThat(profile.getAboutEmoji()).isEqualTo("emoji");
|
||||
assertThat(profile.getAvatar()).isEqualTo("profiles/validavatar");
|
||||
assertThat(profile.getBaseProfileResponse().getCapabilities().isGv2()).isFalse();
|
||||
assertThat(profile.getBaseProfileResponse().getCapabilities().isGv1Migration()).isFalse();
|
||||
assertThat(profile.getBaseProfileResponse().getCapabilities().isGv1Migration()).isTrue();
|
||||
assertThat(profile.getBaseProfileResponse().getUuid()).isEqualTo(AuthHelper.VALID_UUID_TWO);
|
||||
assertThat(profile.getBaseProfileResponse().getBadges()).hasSize(1).element(0).has(new Condition<>(
|
||||
badge -> "Test Badge".equals(badge.getName()), "has badge with expected name"));
|
||||
|
||||
@@ -392,8 +392,6 @@ class SubscriptionControllerTest {
|
||||
when(LEVEL_TRANSLATOR.translate(any(), eq("B1"))).thenReturn("Z1");
|
||||
when(LEVEL_TRANSLATOR.translate(any(), eq("B2"))).thenReturn("Z2");
|
||||
when(LEVEL_TRANSLATOR.translate(any(), eq("B3"))).thenReturn("Z3");
|
||||
when(LEVEL_TRANSLATOR.translate(any(), eq("BOOST"))).thenReturn("ZBOOST");
|
||||
when(LEVEL_TRANSLATOR.translate(any(), eq("GIFT"))).thenReturn("ZGIFT");
|
||||
|
||||
GetSubscriptionConfigurationResponse response = RESOURCE_EXTENSION.target("/v1/subscription/configuration")
|
||||
.request()
|
||||
@@ -445,7 +443,7 @@ class SubscriptionControllerTest {
|
||||
|
||||
assertThat(response.levels()).containsKeys("1", "5", "15", "35", "100").satisfies(levelsMap -> {
|
||||
assertThat(levelsMap).extractingByKey("1").satisfies(level -> {
|
||||
assertThat(level.name()).isEqualTo("ZBOOST");
|
||||
assertThat(level.name()).isEqualTo("boost1"); // level name is the same as badge name
|
||||
assertThat(level).extracting(SubscriptionController.LevelConfiguration::badge).satisfies(badge -> {
|
||||
assertThat(badge.getId()).isEqualTo("BOOST");
|
||||
assertThat(badge.getName()).isEqualTo("boost1");
|
||||
@@ -453,7 +451,7 @@ class SubscriptionControllerTest {
|
||||
});
|
||||
|
||||
assertThat(levelsMap).extractingByKey("100").satisfies(level -> {
|
||||
assertThat(level.name()).isEqualTo("ZGIFT");
|
||||
assertThat(level.name()).isEqualTo("gift1"); // level name is the same as badge name
|
||||
assertThat(level).extracting(SubscriptionController.LevelConfiguration::badge).satisfies(badge -> {
|
||||
assertThat(badge.getId()).isEqualTo("GIFT");
|
||||
assertThat(badge.getName()).isEqualTo("gift1");
|
||||
|
||||
@@ -8,19 +8,19 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager.DeletionReason;
|
||||
|
||||
class AccountCleanerTest {
|
||||
@@ -35,6 +35,8 @@ class AccountCleanerTest {
|
||||
private final Device undeletedDisabledDevice = mock(Device.class );
|
||||
private final Device undeletedEnabledDevice = mock(Device.class );
|
||||
|
||||
private ExecutorService deletionExecutor;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
@@ -64,11 +66,19 @@ class AccountCleanerTest {
|
||||
when(undeletedEnabledAccount.getNumber()).thenReturn("+14153333333");
|
||||
when(undeletedEnabledAccount.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(364));
|
||||
when(undeletedEnabledAccount.getUuid()).thenReturn(UUID.randomUUID());
|
||||
|
||||
deletionExecutor = Executors.newFixedThreadPool(2);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws InterruptedException {
|
||||
deletionExecutor.shutdown();
|
||||
deletionExecutor.awaitTermination(2, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAccounts() throws AccountDatabaseCrawlerRestartException, InterruptedException {
|
||||
AccountCleaner accountCleaner = new AccountCleaner(accountsManager);
|
||||
AccountCleaner accountCleaner = new AccountCleaner(accountsManager, deletionExecutor);
|
||||
accountCleaner.onCrawlStart();
|
||||
accountCleaner.timeAndProcessCrawlChunk(Optional.empty(), Arrays.asList(deletedDisabledAccount, undeletedDisabledAccount, undeletedEnabledAccount));
|
||||
accountCleaner.onCrawlEnd(Optional.empty());
|
||||
@@ -80,25 +90,4 @@ class AccountCleanerTest {
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMaxAccountUpdates() throws AccountDatabaseCrawlerRestartException, InterruptedException {
|
||||
List<Account> accounts = new LinkedList<>();
|
||||
accounts.add(undeletedEnabledAccount);
|
||||
|
||||
int activeExpiredAccountCount = AccountCleaner.MAX_ACCOUNT_DELETIONS_PER_CHUNK + 1;
|
||||
for (int addedAccountCount = 0; addedAccountCount < activeExpiredAccountCount; addedAccountCount++) {
|
||||
accounts.add(undeletedDisabledAccount);
|
||||
}
|
||||
|
||||
accounts.add(deletedDisabledAccount);
|
||||
|
||||
AccountCleaner accountCleaner = new AccountCleaner(accountsManager);
|
||||
accountCleaner.onCrawlStart();
|
||||
accountCleaner.timeAndProcessCrawlChunk(Optional.empty(), accounts);
|
||||
accountCleaner.onCrawlEnd(Optional.empty());
|
||||
|
||||
verify(accountsManager, times(AccountCleaner.MAX_ACCOUNT_DELETIONS_PER_CHUNK)).delete(undeletedDisabledAccount, AccountsManager.DeletionReason.EXPIRED);
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -603,7 +603,7 @@ class AccountsManagerTest {
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testCreateWithStorageCapability(final boolean hasStorage) throws InterruptedException {
|
||||
final AccountAttributes attributes = new AccountAttributes(false, 0, null, null, true,
|
||||
new DeviceCapabilities(false, false, false, hasStorage, false, false, false, false, false, false, false, false));
|
||||
new DeviceCapabilities(hasStorage, false, false, false, false, false, false, false));
|
||||
|
||||
final Account account = accountsManager.create("+18005550123", "password", null, attributes, new ArrayList<>());
|
||||
|
||||
|
||||
@@ -73,66 +73,4 @@ class DeviceTest {
|
||||
Arguments.of(false, true, null, null, mock(SignedPreKey.class), Duration.ofDays(1), true)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("argumentsForTestIsGroupsV2Supported")
|
||||
void testIsGroupsV2Supported(final boolean master, final String apnId, final boolean gv2Capability,
|
||||
final boolean gv2_2Capability, final boolean gv2_3Capability, final boolean expectGv2Supported) {
|
||||
final Device.DeviceCapabilities capabilities = new Device.DeviceCapabilities(gv2Capability, gv2_2Capability,
|
||||
gv2_3Capability, false, false, false,
|
||||
false, false, false, false, false, false);
|
||||
|
||||
final Device device = new Device();
|
||||
device.setId(master ? Device.MASTER_ID : Device.MASTER_ID + 1);
|
||||
device.setApnId(apnId);
|
||||
device.setCapabilities(capabilities);
|
||||
|
||||
assertEquals(expectGv2Supported, device.isGroupsV2Supported());
|
||||
}
|
||||
|
||||
private static Stream<Arguments> argumentsForTestIsGroupsV2Supported() {
|
||||
return Stream.of(
|
||||
// master apnId gv2 gv2-2 gv2-3 capable
|
||||
|
||||
// Android master
|
||||
Arguments.of(true, null, false, false, false, false),
|
||||
Arguments.of(true, null, true, false, false, false),
|
||||
Arguments.of(true, null, false, true, false, false),
|
||||
Arguments.of(true, null, true, true, false, false),
|
||||
Arguments.of(true, null, false, false, true, true),
|
||||
Arguments.of(true, null, true, false, true, true),
|
||||
Arguments.of(true, null, false, true, true, true),
|
||||
Arguments.of(true, null, true, true, true, true),
|
||||
|
||||
// iOS master
|
||||
Arguments.of(true, "apn-id", false, false, false, false),
|
||||
Arguments.of(true, "apn-id", true, false, false, false),
|
||||
Arguments.of(true, "apn-id", false, true, false, true),
|
||||
Arguments.of(true, "apn-id", true, true, false, true),
|
||||
Arguments.of(true, "apn-id", false, false, true, true),
|
||||
Arguments.of(true, "apn-id", true, false, true, true),
|
||||
Arguments.of(true, "apn-id", false, true, true, true),
|
||||
Arguments.of(true, "apn-id", true, true, true, true),
|
||||
|
||||
// iOS linked
|
||||
Arguments.of(false, "apn-id", false, false, false, false),
|
||||
Arguments.of(false, "apn-id", true, false, false, false),
|
||||
Arguments.of(false, "apn-id", false, true, false, true),
|
||||
Arguments.of(false, "apn-id", true, true, false, true),
|
||||
Arguments.of(false, "apn-id", false, false, true, true),
|
||||
Arguments.of(false, "apn-id", true, false, true, true),
|
||||
Arguments.of(false, "apn-id", false, true, true, true),
|
||||
Arguments.of(false, "apn-id", true, true, true, true),
|
||||
|
||||
// desktop linked
|
||||
Arguments.of(false, null, false, false, false, false),
|
||||
Arguments.of(false, null, true, false, false, false),
|
||||
Arguments.of(false, null, false, true, false, false),
|
||||
Arguments.of(false, null, true, true, false, false),
|
||||
Arguments.of(false, null, false, false, true, true),
|
||||
Arguments.of(false, null, true, false, true, true),
|
||||
Arguments.of(false, null, false, true, true, true),
|
||||
Arguments.of(false, null, true, true, true, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
@@ -568,6 +569,7 @@ class MessagesCacheTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("flaky test")
|
||||
void testGetAllMessagesLimitsAndBackpressure() {
|
||||
// this test makes sure that we don’t fetch and buffer all messages from the cache when the publisher
|
||||
// is subscribed. Rather, we should be fetching in pages to satisfy downstream requests, so that memory usage
|
||||
|
||||
@@ -124,8 +124,6 @@ class DeviceControllerTest {
|
||||
when(account.getNumber()).thenReturn(AuthHelper.VALID_NUMBER);
|
||||
when(account.getUuid()).thenReturn(AuthHelper.VALID_UUID);
|
||||
when(account.isEnabled()).thenReturn(false);
|
||||
when(account.isGroupsV2Supported()).thenReturn(true);
|
||||
when(account.isGv1MigrationSupported()).thenReturn(true);
|
||||
when(account.isSenderKeySupported()).thenReturn(true);
|
||||
when(account.isAnnouncementGroupSupported()).thenReturn(true);
|
||||
when(account.isChangeNumberSupported()).thenReturn(true);
|
||||
@@ -303,78 +301,9 @@ class DeviceControllerTest {
|
||||
verifyNoMoreInteractions(messagesManager);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void deviceDowngradeCapabilitiesTest(final String userAgent, final boolean gv2, final boolean gv2_2,
|
||||
final boolean gv2_3, final int expectedStatus) {
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(gv2, gv2_2, gv2_3, true, false, true, true, true,
|
||||
true, true, true, true);
|
||||
AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/5678901")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getProvisioningAuthHeader(AuthHelper.VALID_NUMBER, "password1"))
|
||||
.header(HttpHeaders.USER_AGENT, userAgent)
|
||||
.put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(expectedStatus);
|
||||
|
||||
if (expectedStatus >= 300) {
|
||||
verifyNoMoreInteractions(messagesManager);
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Arguments> deviceDowngradeCapabilitiesTest() {
|
||||
return Stream.of(
|
||||
// User-Agent gv2 gv2-2 gv2-3 expected
|
||||
Arguments.of( "Signal-Android/4.68.3 Android/25", false, false, false, 409 ),
|
||||
Arguments.of( "Signal-Android/4.68.3 Android/25", true, false, false, 409 ),
|
||||
Arguments.of( "Signal-Android/4.68.3 Android/25", false, true, false, 409 ),
|
||||
Arguments.of( "Signal-Android/4.68.3 Android/25", false, false, true, 200 ),
|
||||
Arguments.of( "Signal-iOS/3.9.0", false, false, false, 409 ),
|
||||
Arguments.of( "Signal-iOS/3.9.0", true, false, false, 409 ),
|
||||
Arguments.of( "Signal-iOS/3.9.0", false, true, false, 200 ),
|
||||
Arguments.of( "Signal-iOS/3.9.0", false, false, true, 200 ),
|
||||
Arguments.of( "Signal-Desktop/1.32.0-beta.3", false, false, false, 409 ),
|
||||
Arguments.of( "Signal-Desktop/1.32.0-beta.3", true, false, false, 409 ),
|
||||
Arguments.of( "Signal-Desktop/1.32.0-beta.3", false, true, false, 409 ),
|
||||
Arguments.of( "Signal-Desktop/1.32.0-beta.3", false, false, true, 200 ),
|
||||
Arguments.of( "Old client with unparsable UA", false, false, false, 409 ),
|
||||
Arguments.of( "Old client with unparsable UA", true, false, false, 409 ),
|
||||
Arguments.of( "Old client with unparsable UA", false, true, false, 409 ),
|
||||
Arguments.of( "Old client with unparsable UA", false, false, true, 409 )
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deviceDowngradeGv1MigrationTest() {
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, false, false, true, true,
|
||||
true, true, true, true);
|
||||
AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/5678901")
|
||||
.request()
|
||||
.header("authorization", AuthHelper.getProvisioningAuthHeader(AuthHelper.VALID_NUMBER, "password1"))
|
||||
.header(HttpHeaders.USER_AGENT, "Signal-Android/4.68.3 Android/25")
|
||||
.put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(409);
|
||||
|
||||
deviceCapabilities = new DeviceCapabilities(true, true, true, true, false, true, true, true, true, true, true, true);
|
||||
accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
response = resources.getJerseyTest()
|
||||
.target("/v1/devices/5678901")
|
||||
.request()
|
||||
.header("authorization", AuthHelper.getProvisioningAuthHeader(AuthHelper.VALID_NUMBER, "password1"))
|
||||
.header(HttpHeaders.USER_AGENT, "Signal-Android/4.68.3 Android/25")
|
||||
.put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deviceDowngradeSenderKeyTest() {
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, false, true,
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, false, true,
|
||||
true, true, true, true);
|
||||
AccountAttributes accountAttributes =
|
||||
new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
@@ -387,7 +316,7 @@ class DeviceControllerTest {
|
||||
.put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE));
|
||||
assertThat(response.getStatus()).isEqualTo(409);
|
||||
|
||||
deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true, true, true);
|
||||
deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true);
|
||||
accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
response = resources
|
||||
.getJerseyTest()
|
||||
@@ -401,7 +330,7 @@ class DeviceControllerTest {
|
||||
|
||||
@Test
|
||||
void deviceDowngradeAnnouncementGroupTest() {
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, false,
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, false,
|
||||
true, true, true, true);
|
||||
AccountAttributes accountAttributes =
|
||||
new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
@@ -414,7 +343,7 @@ class DeviceControllerTest {
|
||||
.put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE));
|
||||
assertThat(response.getStatus()).isEqualTo(409);
|
||||
|
||||
deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true, true, true);
|
||||
deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true);
|
||||
accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
response = resources
|
||||
.getJerseyTest()
|
||||
@@ -428,7 +357,7 @@ class DeviceControllerTest {
|
||||
|
||||
@Test
|
||||
void deviceDowngradeChangeNumberTest() {
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true,
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true,
|
||||
false, true, true, true);
|
||||
AccountAttributes accountAttributes =
|
||||
new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
@@ -442,7 +371,7 @@ class DeviceControllerTest {
|
||||
.put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE));
|
||||
assertThat(response.getStatus()).isEqualTo(409);
|
||||
|
||||
deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true, true, true);
|
||||
deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true);
|
||||
accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
response = resources
|
||||
.getJerseyTest()
|
||||
@@ -457,7 +386,7 @@ class DeviceControllerTest {
|
||||
|
||||
@Test
|
||||
void deviceDowngradePniTest() {
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true,
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true,
|
||||
false, true, true);
|
||||
AccountAttributes accountAttributes =
|
||||
new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
@@ -470,7 +399,7 @@ class DeviceControllerTest {
|
||||
.put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE));
|
||||
assertThat(response.getStatus()).isEqualTo(409);
|
||||
|
||||
deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true, true, true);
|
||||
deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true);
|
||||
accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
response = resources
|
||||
.getJerseyTest()
|
||||
@@ -485,7 +414,7 @@ class DeviceControllerTest {
|
||||
|
||||
@Test
|
||||
void deviceDowngradeStoriesTest() {
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true,
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true,
|
||||
true, false, true);
|
||||
AccountAttributes accountAttributes =
|
||||
new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
@@ -499,7 +428,7 @@ class DeviceControllerTest {
|
||||
.put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE));
|
||||
assertThat(response.getStatus()).isEqualTo(409);
|
||||
|
||||
deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true, true, true);
|
||||
deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true);
|
||||
accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
response = resources
|
||||
.getJerseyTest()
|
||||
@@ -514,7 +443,7 @@ class DeviceControllerTest {
|
||||
|
||||
@Test
|
||||
void deviceDowngradeGiftBadgesTest() {
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true, true, false);
|
||||
DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, false);
|
||||
AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
Response response = resources
|
||||
.getJerseyTest()
|
||||
@@ -525,7 +454,7 @@ class DeviceControllerTest {
|
||||
.put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE));
|
||||
assertThat(response.getStatus()).isEqualTo(409);
|
||||
|
||||
deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true, true, true);
|
||||
deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true);
|
||||
accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities);
|
||||
response = resources
|
||||
.getJerseyTest()
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
|
||||
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
|
||||
class AbusiveHostRulesTest {
|
||||
|
||||
@RegisterExtension
|
||||
private static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
|
||||
private AbusiveHostRules abusiveHostRules;
|
||||
private DynamicConfigurationManager<DynamicConfiguration> mockDynamicConfigManager;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws JsonProcessingException {
|
||||
@SuppressWarnings("unchecked")
|
||||
DynamicConfigurationManager<DynamicConfiguration> m = mock(DynamicConfigurationManager.class);
|
||||
this.mockDynamicConfigManager = m;
|
||||
when(mockDynamicConfigManager.getConfiguration()).thenReturn(generateConfig(Duration.ofHours(1)));
|
||||
this.abusiveHostRules = new AbusiveHostRules(REDIS_CLUSTER_EXTENSION.getRedisCluster(), mockDynamicConfigManager);
|
||||
}
|
||||
|
||||
DynamicConfiguration generateConfig(Duration expireDuration) throws JsonProcessingException {
|
||||
final String configString = String.format("""
|
||||
captcha:
|
||||
scoreFloor: 1.0
|
||||
abusiveHostRules:
|
||||
expirationTime: %s
|
||||
""", expireDuration);
|
||||
return DynamicConfigurationManager
|
||||
.parseConfiguration(configString, DynamicConfiguration.class)
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBlockedHost() {
|
||||
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection ->
|
||||
connection.sync().set(AbusiveHostRules.prefix("192.168.1.1"), "1"));
|
||||
assertThat(abusiveHostRules.isBlocked("192.168.1.1")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnblocked() {
|
||||
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection ->
|
||||
connection.sync().set(AbusiveHostRules.prefix("192.168.1.1"), "1"));
|
||||
assertThat(abusiveHostRules.isBlocked("172.17.1.1")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInsertBlocked() {
|
||||
abusiveHostRules.setBlockedHost("172.17.0.1");
|
||||
assertThat(abusiveHostRules.isBlocked("172.17.0.1")).isTrue();
|
||||
abusiveHostRules.setBlockedHost("172.17.0.1");
|
||||
assertThat(abusiveHostRules.isBlocked("172.17.0.1")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExpiration() throws Exception {
|
||||
when(mockDynamicConfigManager.getConfiguration()).thenReturn(generateConfig(Duration.ofSeconds(1)));
|
||||
abusiveHostRules.setBlockedHost("192.168.1.1");
|
||||
assertTimeoutPreemptively(Duration.ofSeconds(5), () -> {
|
||||
while (true) {
|
||||
if (!abusiveHostRules.isBlocked("192.168.1.1")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,14 +40,6 @@ class AccountTest {
|
||||
private final Device recentSecondaryDevice = mock(Device.class);
|
||||
private final Device oldSecondaryDevice = mock(Device.class);
|
||||
|
||||
private final Device gv2CapableDevice = mock(Device.class);
|
||||
private final Device gv2IncapableDevice = mock(Device.class);
|
||||
private final Device gv2IncapableExpiredDevice = mock(Device.class);
|
||||
|
||||
private final Device gv1MigrationCapableDevice = mock(Device.class);
|
||||
private final Device gv1MigrationIncapableDevice = mock(Device.class);
|
||||
private final Device gv1MigrationIncapableExpiredDevice = mock(Device.class);
|
||||
|
||||
private final Device senderKeyCapableDevice = mock(Device.class);
|
||||
private final Device senderKeyIncapableDevice = mock(Device.class);
|
||||
private final Device senderKeyIncapableExpiredDevice = mock(Device.class);
|
||||
@@ -94,101 +86,77 @@ class AccountTest {
|
||||
when(oldSecondaryDevice.isEnabled()).thenReturn(false);
|
||||
when(oldSecondaryDevice.getId()).thenReturn(2L);
|
||||
|
||||
when(gv2CapableDevice.isGroupsV2Supported()).thenReturn(true);
|
||||
when(gv2CapableDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1));
|
||||
when(gv2CapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(gv2IncapableDevice.isGroupsV2Supported()).thenReturn(false);
|
||||
when(gv2IncapableDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1));
|
||||
when(gv2IncapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(gv2IncapableExpiredDevice.isGroupsV2Supported()).thenReturn(false);
|
||||
when(gv2IncapableExpiredDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31));
|
||||
when(gv2IncapableExpiredDevice.isEnabled()).thenReturn(false);
|
||||
|
||||
when(gv1MigrationCapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, false, false, false, false, false, false));
|
||||
when(gv1MigrationCapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(gv1MigrationIncapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, false, false, false, false, false, false, false));
|
||||
when(gv1MigrationIncapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(gv1MigrationIncapableExpiredDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, false, false, false, false, false, false, false));
|
||||
when(gv1MigrationIncapableExpiredDevice.isEnabled()).thenReturn(false);
|
||||
|
||||
when(senderKeyCapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false, false, false));
|
||||
new DeviceCapabilities(true, true, true, false, false, false, false, false));
|
||||
when(senderKeyCapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(senderKeyIncapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, false, false, false, false, false, false));
|
||||
new DeviceCapabilities(true, true, false, false, false, false, false, false));
|
||||
when(senderKeyIncapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(senderKeyIncapableExpiredDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, false, false, false, false, false, false));
|
||||
new DeviceCapabilities(true, true, false, false, false, false, false, false));
|
||||
when(senderKeyIncapableExpiredDevice.isEnabled()).thenReturn(false);
|
||||
|
||||
when(announcementGroupCapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, true, false, false, false, false));
|
||||
new DeviceCapabilities(true, true, true, true, false, false, false, false));
|
||||
when(announcementGroupCapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(announcementGroupIncapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false, false, false));
|
||||
new DeviceCapabilities(true, true, true, false, false, false, false, false));
|
||||
when(announcementGroupIncapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(announcementGroupIncapableExpiredDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false, false, false));
|
||||
new DeviceCapabilities(true, true, true, false, false, false, false, false));
|
||||
when(announcementGroupIncapableExpiredDevice.isEnabled()).thenReturn(false);
|
||||
|
||||
when(changeNumberCapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false, true, false, false, false));
|
||||
new DeviceCapabilities(true, true, true, false, true, false, false, false));
|
||||
when(changeNumberCapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(changeNumberIncapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false, false, false));
|
||||
new DeviceCapabilities(true, true, true, false, false, false, false, false));
|
||||
when(changeNumberIncapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(changeNumberIncapableExpiredDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false, false, false));
|
||||
new DeviceCapabilities(true, true, true, false, false, false, false, false));
|
||||
when(changeNumberIncapableExpiredDevice.isEnabled()).thenReturn(false);
|
||||
|
||||
when(pniCapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false, false, true, false, false));
|
||||
new DeviceCapabilities(true, true, true, false, false, true, false, false));
|
||||
when(pniCapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(pniIncapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false, false, false));
|
||||
new DeviceCapabilities(true, true, true, false, false, false, false, false));
|
||||
when(pniIncapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(pniIncapableExpiredDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false, false, false));
|
||||
new DeviceCapabilities(true, true, true, false, false, false, false, false));
|
||||
when(pniIncapableExpiredDevice.isEnabled()).thenReturn(false);
|
||||
|
||||
when(storiesCapableDevice.getId()).thenReturn(1L);
|
||||
when(storiesCapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false, true, false));
|
||||
new DeviceCapabilities(true, true, true, false, false, false, true, false));
|
||||
when(storiesCapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(storiesCapableDevice.getId()).thenReturn(2L);
|
||||
when(storiesIncapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false, false, false));
|
||||
new DeviceCapabilities(true, true, true, false, false, false, false, false));
|
||||
when(storiesIncapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(storiesCapableDevice.getId()).thenReturn(3L);
|
||||
when(storiesIncapableExpiredDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false, false, false));
|
||||
new DeviceCapabilities(true, true, true, false, false, false, false, false));
|
||||
when(storiesIncapableExpiredDevice.isEnabled()).thenReturn(false);
|
||||
|
||||
when(giftBadgesCapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true, true, true));
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, true));
|
||||
when(giftBadgesCapableDevice.isEnabled()).thenReturn(true);
|
||||
when(giftBadgesIncapableDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true, true, false));
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false));
|
||||
when(giftBadgesIncapableDevice.isEnabled()).thenReturn(true);
|
||||
when(giftBadgesIncapableExpiredDevice.getCapabilities()).thenReturn(
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true, true, false));
|
||||
new DeviceCapabilities(true, true, true, true, true, true, true, false));
|
||||
when(giftBadgesIncapableExpiredDevice.isEnabled()).thenReturn(false);
|
||||
}
|
||||
|
||||
@@ -217,17 +185,6 @@ class AccountTest {
|
||||
assertFalse(AccountsHelper.generateTestAccount("+14151234567", List.of(disabledMasterDevice, disabledLinkedDevice)).isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCapabilities() {
|
||||
final Account uuidCapable = AccountsHelper.generateTestAccount("+14152222222", UUID.randomUUID(), UUID.randomUUID(), List.of(gv2CapableDevice), "1234".getBytes());
|
||||
final Account uuidIncapable = AccountsHelper.generateTestAccount("+14152222222", UUID.randomUUID(), UUID.randomUUID(), List.of(gv2CapableDevice, gv2IncapableDevice), "1234".getBytes());
|
||||
final Account uuidCapableWithExpiredIncapable = AccountsHelper.generateTestAccount("+14152222222", UUID.randomUUID(), UUID.randomUUID(), List.of(gv2CapableDevice, gv2IncapableExpiredDevice), "1234".getBytes());
|
||||
|
||||
assertTrue(uuidCapable.isGroupsV2Supported());
|
||||
assertFalse(uuidIncapable.isGroupsV2Supported());
|
||||
assertTrue(uuidCapableWithExpiredIncapable.isGroupsV2Supported());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsTransferSupported() {
|
||||
final Device transferCapableMasterDevice = mock(Device.class);
|
||||
@@ -288,31 +245,6 @@ class AccountTest {
|
||||
assertTrue(account.isDiscoverableByPhoneNumber());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isGroupsV2Supported() {
|
||||
assertTrue(AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), List.of(gv2CapableDevice),
|
||||
"1234".getBytes(StandardCharsets.UTF_8)).isGroupsV2Supported());
|
||||
assertTrue(AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(),
|
||||
List.of(gv2CapableDevice, gv2IncapableExpiredDevice),
|
||||
"1234".getBytes(StandardCharsets.UTF_8)).isGroupsV2Supported());
|
||||
assertFalse(AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(),
|
||||
List.of(gv2CapableDevice, gv2IncapableDevice),
|
||||
"1234".getBytes(StandardCharsets.UTF_8)).isGroupsV2Supported());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isGv1MigrationSupported() {
|
||||
assertTrue(AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), List.of(gv1MigrationCapableDevice),
|
||||
"1234".getBytes(StandardCharsets.UTF_8)).isGv1MigrationSupported());
|
||||
assertFalse(
|
||||
AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(),
|
||||
List.of(gv1MigrationCapableDevice, gv1MigrationIncapableDevice),
|
||||
"1234".getBytes(StandardCharsets.UTF_8)).isGv1MigrationSupported());
|
||||
assertTrue(AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(),
|
||||
UUID.randomUUID(), List.of(gv1MigrationCapableDevice, gv1MigrationIncapableExpiredDevice), "1234".getBytes(StandardCharsets.UTF_8))
|
||||
.isGv1MigrationSupported());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isSenderKeySupported() {
|
||||
assertThat(AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), List.of(senderKeyCapableDevice),
|
||||
|
||||
@@ -120,8 +120,6 @@ public class AccountsHelper {
|
||||
case "isEnabled" -> when(updatedAccount.isEnabled()).thenAnswer(stubbing);
|
||||
case "isDiscoverableByPhoneNumber" -> when(updatedAccount.isDiscoverableByPhoneNumber()).thenAnswer(stubbing);
|
||||
case "getNextDeviceId" -> when(updatedAccount.getNextDeviceId()).thenAnswer(stubbing);
|
||||
case "isGroupsV2Supported" -> when(updatedAccount.isGroupsV2Supported()).thenAnswer(stubbing);
|
||||
case "isGv1MigrationSupported" -> when(updatedAccount.isGv1MigrationSupported()).thenAnswer(stubbing);
|
||||
case "isSenderKeySupported" -> when(updatedAccount.isSenderKeySupported()).thenAnswer(stubbing);
|
||||
case "isAnnouncementGroupSupported" -> when(updatedAccount.isAnnouncementGroupSupported()).thenAnswer(stubbing);
|
||||
case "isChangeNumberSupported" -> when(updatedAccount.isChangeNumberSupported()).thenAnswer(stubbing);
|
||||
|
||||
Reference in New Issue
Block a user