mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-20 05:28:05 +01:00
Consolidate account creation/directory updates into AccountsManager
This commit is contained in:
@@ -442,7 +442,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, Metrics.globalRegistry);
|
||||
MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, pushLatencyManager, reportMessageManager);
|
||||
DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, deletedAccountsLockDynamoDbClient, config.getDeletedAccountsLockDynamoDbConfiguration().getTableName());
|
||||
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccountsManager, directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
|
||||
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccountsManager, directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, pendingAccountsManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
|
||||
RemoteConfigsManager remoteConfigsManager = new RemoteConfigsManager(remoteConfigs);
|
||||
DeadLetterHandler deadLetterHandler = new DeadLetterHandler(accountsManager, messagesManager);
|
||||
DispatchManager dispatchManager = new DispatchManager(pubSubClientFactory, Optional.of(deadLetterHandler));
|
||||
@@ -476,7 +476,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
|
||||
final List<DeletedAccountsDirectoryReconciler> deletedAccountsDirectoryReconcilers = new ArrayList<>();
|
||||
final List<AccountDatabaseCrawlerListener> accountDatabaseCrawlerListeners = new ArrayList<>();
|
||||
accountDatabaseCrawlerListeners.add(new PushFeedbackProcessor(accountsManager, directoryQueue));
|
||||
accountDatabaseCrawlerListeners.add(new PushFeedbackProcessor(accountsManager));
|
||||
accountDatabaseCrawlerListeners.add(new ActiveUserCounter(config.getMetricsFactory(), cacheCluster));
|
||||
for (DirectoryServerConfiguration directoryServerConfiguration : config.getDirectoryConfiguration().getDirectoryServerConfiguration()) {
|
||||
final DirectoryReconciliationClient directoryReconciliationClient = new DirectoryReconciliationClient(directoryServerConfiguration);
|
||||
@@ -555,8 +555,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
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, directoryQueue, messagesManager, keysDynamoDb, dynamicConfigurationManager, turnTokenGenerator, config.getTestDevices(), recaptchaClient, gcmSender, apnSender, backupCredentialsGenerator, verifyExperimentEnrollmentManager));
|
||||
environment.jersey().register(new KeysController(rateLimiters, keysDynamoDb, accountsManager, directoryQueue, preKeyRateLimiter, dynamicConfigurationManager, rateLimitChallengeManager));
|
||||
environment.jersey().register(new AccountController(pendingAccountsManager, accountsManager, usernamesManager, abusiveHostRules, rateLimiters, smsSender, dynamicConfigurationManager, turnTokenGenerator, config.getTestDevices(), recaptchaClient, gcmSender, apnSender, backupCredentialsGenerator, verifyExperimentEnrollmentManager));
|
||||
environment.jersey().register(new KeysController(rateLimiters, keysDynamoDb, accountsManager, preKeyRateLimiter, dynamicConfigurationManager, rateLimitChallengeManager));
|
||||
|
||||
final List<Object> commonControllers = List.of(
|
||||
new AttachmentControllerV1(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getBucket()),
|
||||
@@ -564,7 +564,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
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, isZkEnabled),
|
||||
new ChallengeController(rateLimitChallengeManager),
|
||||
new DeviceController(pendingDevicesManager, accountsManager, messagesManager, keysDynamoDb, directoryQueue, rateLimiters, config.getMaxDevices()),
|
||||
new DeviceController(pendingDevicesManager, accountsManager, messagesManager, keysDynamoDb, rateLimiters, config.getMaxDevices()),
|
||||
new DirectoryController(directoryCredentialsGenerator),
|
||||
new DonationController(donationExecutor, config.getDonationConfiguration()),
|
||||
new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, messagesManager, unsealedSenderRateLimiter, apnFallbackManager, dynamicConfigurationManager, rateLimitChallengeManager, reportMessageManager, metricsCluster, declinedMessageReceiptExecutor),
|
||||
@@ -576,6 +576,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
new SecureStorageController(storageCredentialsGenerator),
|
||||
new StickerController(rateLimiters, config.getCdnConfiguration().getAccessKey(), config.getCdnConfiguration().getAccessSecret(), config.getCdnConfiguration().getRegion(), config.getCdnConfiguration().getBucket())
|
||||
);
|
||||
|
||||
for (Object controller : commonControllers) {
|
||||
environment.jersey().register(controller);
|
||||
webSocketEnvironment.jersey().register(controller);
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.validation.Valid;
|
||||
@@ -68,15 +67,12 @@ import org.whispersystems.textsecuregcm.push.GcmMessage;
|
||||
import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
|
||||
import org.whispersystems.textsecuregcm.sms.SmsSender;
|
||||
import org.whispersystems.textsecuregcm.sms.TwilioVerifyExperimentEnrollmentManager;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.storage.AbusiveHostRule;
|
||||
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.storage.KeysDynamoDb;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernamesManager;
|
||||
import org.whispersystems.textsecuregcm.util.Constants;
|
||||
@@ -91,7 +87,6 @@ public class AccountController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AccountController.class);
|
||||
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
||||
private final Meter newUserMeter = metricRegistry.meter(name(AccountController.class, "brand_new_user" ));
|
||||
private final Meter blockedHostMeter = metricRegistry.meter(name(AccountController.class, "blocked_host" ));
|
||||
private final Meter filteredHostMeter = metricRegistry.meter(name(AccountController.class, "filtered_host" ));
|
||||
private final Meter rateLimitedHostMeter = metricRegistry.meter(name(AccountController.class, "rate_limited_host" ));
|
||||
@@ -119,9 +114,6 @@ public class AccountController {
|
||||
private final AbusiveHostRules abusiveHostRules;
|
||||
private final RateLimiters rateLimiters;
|
||||
private final SmsSender smsSender;
|
||||
private final DirectoryQueue directoryQueue;
|
||||
private final MessagesManager messagesManager;
|
||||
private final KeysDynamoDb keys;
|
||||
private final DynamicConfigurationManager dynamicConfigurationManager;
|
||||
private final TurnTokenGenerator turnTokenGenerator;
|
||||
private final Map<String, Integer> testDevices;
|
||||
@@ -138,9 +130,6 @@ public class AccountController {
|
||||
AbusiveHostRules abusiveHostRules,
|
||||
RateLimiters rateLimiters,
|
||||
SmsSender smsSenderFactory,
|
||||
DirectoryQueue directoryQueue,
|
||||
MessagesManager messagesManager,
|
||||
KeysDynamoDb keys,
|
||||
DynamicConfigurationManager dynamicConfigurationManager,
|
||||
TurnTokenGenerator turnTokenGenerator,
|
||||
Map<String, Integer> testDevices,
|
||||
@@ -156,9 +145,6 @@ public class AccountController {
|
||||
this.abusiveHostRules = abusiveHostRules;
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.smsSender = smsSenderFactory;
|
||||
this.directoryQueue = directoryQueue;
|
||||
this.messagesManager = messagesManager;
|
||||
this.keys = keys;
|
||||
this.dynamicConfigurationManager = dynamicConfigurationManager;
|
||||
this.testDevices = testDevices;
|
||||
this.turnTokenGenerator = turnTokenGenerator;
|
||||
@@ -318,6 +304,7 @@ public class AccountController {
|
||||
});
|
||||
});
|
||||
|
||||
// TODO Remove this meter when external dependencies have been resolved
|
||||
metricRegistry.meter(name(AccountController.class, "create", Util.getCountryCode(number))).mark();
|
||||
|
||||
{
|
||||
@@ -392,7 +379,7 @@ public class AccountController {
|
||||
throw new WebApplicationException(Response.status(409).build());
|
||||
}
|
||||
|
||||
Account account = createAccount(number, password, signalAgent, accountAttributes);
|
||||
Account account = accounts.create(number, password, signalAgent, accountAttributes);
|
||||
|
||||
{
|
||||
metricRegistry.meter(name(AccountController.class, "verify", Util.getCountryCode(number))).mark();
|
||||
@@ -431,7 +418,6 @@ public class AccountController {
|
||||
public void setGcmRegistrationId(@Auth DisabledPermittedAccount disabledPermittedAccount, @Valid GcmRegistrationId registrationId) {
|
||||
Account account = disabledPermittedAccount.getAccount();
|
||||
Device device = account.getAuthenticatedDevice().get();
|
||||
boolean wasAccountEnabled = account.isEnabled();
|
||||
|
||||
if (device.getGcmId() != null &&
|
||||
device.getGcmId().equals(registrationId.getGcmRegistrationId()))
|
||||
@@ -439,16 +425,12 @@ public class AccountController {
|
||||
return;
|
||||
}
|
||||
|
||||
account = accounts.updateDevice(account, device.getId(), d -> {
|
||||
accounts.updateDevice(account, device.getId(), d -> {
|
||||
d.setApnId(null);
|
||||
d.setVoipApnId(null);
|
||||
d.setGcmId(registrationId.getGcmRegistrationId());
|
||||
d.setFetchesMessages(false);
|
||||
});
|
||||
|
||||
if (!wasAccountEnabled && account.isEnabled()) {
|
||||
directoryQueue.refreshRegisteredUser(account);
|
||||
}
|
||||
}
|
||||
|
||||
@Timed
|
||||
@@ -458,12 +440,11 @@ public class AccountController {
|
||||
Account account = disabledPermittedAccount.getAccount();
|
||||
Device device = account.getAuthenticatedDevice().get();
|
||||
|
||||
account = accounts.updateDevice(account, device.getId(), d -> {
|
||||
accounts.updateDevice(account, device.getId(), d -> {
|
||||
d.setGcmId(null);
|
||||
d.setFetchesMessages(false);
|
||||
d.setUserAgent("OWA");
|
||||
});
|
||||
directoryQueue.refreshRegisteredUser(account);
|
||||
}
|
||||
|
||||
@Timed
|
||||
@@ -473,18 +454,13 @@ public class AccountController {
|
||||
public void setApnRegistrationId(@Auth DisabledPermittedAccount disabledPermittedAccount, @Valid ApnRegistrationId registrationId) {
|
||||
Account account = disabledPermittedAccount.getAccount();
|
||||
Device device = account.getAuthenticatedDevice().get();
|
||||
boolean wasAccountEnabled = account.isEnabled();
|
||||
|
||||
account = accounts.updateDevice(account, device.getId(), d -> {
|
||||
accounts.updateDevice(account, device.getId(), d -> {
|
||||
d.setApnId(registrationId.getApnRegistrationId());
|
||||
d.setVoipApnId(registrationId.getVoipRegistrationId());
|
||||
d.setGcmId(null);
|
||||
d.setFetchesMessages(false);
|
||||
});
|
||||
|
||||
if (!wasAccountEnabled && account.isEnabled()) {
|
||||
directoryQueue.refreshRegisteredUser(account);
|
||||
}
|
||||
}
|
||||
|
||||
@Timed
|
||||
@@ -503,7 +479,6 @@ public class AccountController {
|
||||
d.setUserAgent("OWP");
|
||||
}
|
||||
});
|
||||
directoryQueue.refreshRegisteredUser(account);
|
||||
}
|
||||
|
||||
@Timed
|
||||
@@ -577,7 +552,7 @@ public class AccountController {
|
||||
Account account = disabledPermittedAccount.getAccount();
|
||||
long deviceId = account.getAuthenticatedDevice().get().getId();
|
||||
|
||||
account = accounts.update(account, a-> {
|
||||
accounts.update(account, a-> {
|
||||
|
||||
a.getDevice(deviceId).ifPresent(d -> {
|
||||
d.setFetchesMessages(attributes.getFetchesMessages());
|
||||
@@ -588,19 +563,12 @@ public class AccountController {
|
||||
d.setUserAgent(userAgent);
|
||||
});
|
||||
|
||||
setAccountRegistrationLockFromAttributes(a, attributes);
|
||||
a.setRegistrationLockFromAttributes(attributes);
|
||||
|
||||
a.setUnidentifiedAccessKey(attributes.getUnidentifiedAccessKey());
|
||||
a.setUnrestrictedUnidentifiedAccess(attributes.isUnrestrictedUnidentifiedAccess());
|
||||
a.setDiscoverableByPhoneNumber(attributes.isDiscoverableByPhoneNumber());
|
||||
|
||||
});
|
||||
|
||||
final boolean hasDiscoverabilityChange = (account.isDiscoverableByPhoneNumber() != attributes.isDiscoverableByPhoneNumber());
|
||||
|
||||
if (hasDiscoverabilityChange) {
|
||||
directoryQueue.refreshRegisteredUser(account);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@@ -752,57 +720,6 @@ public class AccountController {
|
||||
return false;
|
||||
}
|
||||
|
||||
private Account createAccount(String number, String password, String signalAgent, AccountAttributes accountAttributes) {
|
||||
Optional<Account> maybeExistingAccount = accounts.get(number);
|
||||
|
||||
Device device = new Device();
|
||||
device.setId(Device.MASTER_ID);
|
||||
device.setAuthenticationCredentials(new AuthenticationCredentials(password));
|
||||
device.setFetchesMessages(accountAttributes.getFetchesMessages());
|
||||
device.setRegistrationId(accountAttributes.getRegistrationId());
|
||||
device.setName(accountAttributes.getName());
|
||||
device.setCapabilities(accountAttributes.getCapabilities());
|
||||
device.setCreated(System.currentTimeMillis());
|
||||
device.setLastSeen(Util.todayInMillis());
|
||||
device.setUserAgent(signalAgent);
|
||||
|
||||
Account account = new Account();
|
||||
account.setNumber(number);
|
||||
account.setUuid(UUID.randomUUID());
|
||||
account.addDevice(device);
|
||||
setAccountRegistrationLockFromAttributes(account, accountAttributes);
|
||||
account.setUnidentifiedAccessKey(accountAttributes.getUnidentifiedAccessKey());
|
||||
account.setUnrestrictedUnidentifiedAccess(accountAttributes.isUnrestrictedUnidentifiedAccess());
|
||||
account.setDiscoverableByPhoneNumber(accountAttributes.isDiscoverableByPhoneNumber());
|
||||
|
||||
if (accounts.create(account)) {
|
||||
newUserMeter.mark();
|
||||
}
|
||||
|
||||
directoryQueue.refreshRegisteredUser(account);
|
||||
|
||||
maybeExistingAccount.ifPresent(definitelyExistingAccount -> {
|
||||
messagesManager.clear(definitelyExistingAccount.getUuid());
|
||||
keys.delete(definitelyExistingAccount);
|
||||
});
|
||||
|
||||
pendingAccounts.remove(number);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
private void setAccountRegistrationLockFromAttributes(Account account, @Valid AccountAttributes attributes) {
|
||||
if (!Util.isEmpty(attributes.getPin())) {
|
||||
account.setPin(attributes.getPin());
|
||||
} else if (!Util.isEmpty(attributes.getRegistrationLock())) {
|
||||
AuthenticationCredentials credentials = new AuthenticationCredentials(attributes.getRegistrationLock());
|
||||
account.setRegistrationLock(credentials.getHashedAuthenticationToken(), credentials.getSalt());
|
||||
} else {
|
||||
account.setPin(null);
|
||||
account.setRegistrationLock(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting protected
|
||||
VerificationCode generateVerificationCode(String number) {
|
||||
if (testDevices.containsKey(number)) {
|
||||
|
||||
@@ -62,13 +62,11 @@ public class DeviceController {
|
||||
private final KeysDynamoDb keys;
|
||||
private final RateLimiters rateLimiters;
|
||||
private final Map<String, Integer> maxDeviceConfiguration;
|
||||
private final DirectoryQueue directoryQueue;
|
||||
|
||||
public DeviceController(StoredVerificationCodeManager pendingDevices,
|
||||
AccountsManager accounts,
|
||||
MessagesManager messages,
|
||||
KeysDynamoDb keys,
|
||||
DirectoryQueue directoryQueue,
|
||||
RateLimiters rateLimiters,
|
||||
Map<String, Integer> maxDeviceConfiguration)
|
||||
{
|
||||
@@ -76,7 +74,6 @@ public class DeviceController {
|
||||
this.accounts = accounts;
|
||||
this.messages = messages;
|
||||
this.keys = keys;
|
||||
this.directoryQueue = directoryQueue;
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.maxDeviceConfiguration = maxDeviceConfiguration;
|
||||
}
|
||||
@@ -105,7 +102,6 @@ public class DeviceController {
|
||||
|
||||
messages.clear(account.getUuid(), deviceId);
|
||||
account = accounts.update(account, a -> a.removeDevice(deviceId));
|
||||
directoryQueue.refreshRegisteredUser(account);
|
||||
keys.delete(account, deviceId);
|
||||
// ensure any messages that came in after the first clear() are also removed
|
||||
messages.clear(account.getUuid(), deviceId);
|
||||
|
||||
@@ -55,7 +55,6 @@ public class KeysController {
|
||||
private final RateLimiters rateLimiters;
|
||||
private final KeysDynamoDb keysDynamoDb;
|
||||
private final AccountsManager accounts;
|
||||
private final DirectoryQueue directoryQueue;
|
||||
private final PreKeyRateLimiter preKeyRateLimiter;
|
||||
|
||||
private final DynamicConfigurationManager dynamicConfigurationManager;
|
||||
@@ -69,13 +68,12 @@ public class KeysController {
|
||||
private static final String PREKEY_TARGET_IDENTIFIER_TAG_NAME = "identifierType";
|
||||
|
||||
public KeysController(RateLimiters rateLimiters, KeysDynamoDb keysDynamoDb, AccountsManager accounts,
|
||||
DirectoryQueue directoryQueue, PreKeyRateLimiter preKeyRateLimiter,
|
||||
PreKeyRateLimiter preKeyRateLimiter,
|
||||
DynamicConfigurationManager dynamicConfigurationManager,
|
||||
RateLimitChallengeManager rateLimitChallengeManager) {
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.keysDynamoDb = keysDynamoDb;
|
||||
this.accounts = accounts;
|
||||
this.directoryQueue = directoryQueue;
|
||||
this.preKeyRateLimiter = preKeyRateLimiter;
|
||||
|
||||
this.dynamicConfigurationManager = dynamicConfigurationManager;
|
||||
@@ -100,7 +98,6 @@ public class KeysController {
|
||||
public void setKeys(@Auth DisabledPermittedAccount disabledPermittedAccount, @Valid PreKeyState preKeys) {
|
||||
Account account = disabledPermittedAccount.getAccount();
|
||||
Device device = account.getAuthenticatedDevice().get();
|
||||
boolean wasAccountEnabled = account.isEnabled();
|
||||
boolean updateAccount = false;
|
||||
|
||||
if (!preKeys.getSignedPreKey().equals(device.getSignedPreKey())) {
|
||||
@@ -116,10 +113,6 @@ public class KeysController {
|
||||
a.getDevice(device.getId()).ifPresent(d -> d.setSignedPreKey(preKeys.getSignedPreKey()));
|
||||
a.setIdentityKey(preKeys.getIdentityKey());
|
||||
});
|
||||
|
||||
if (!wasAccountEnabled && account.isEnabled()) {
|
||||
directoryQueue.refreshRegisteredUser(account);
|
||||
}
|
||||
}
|
||||
|
||||
keysDynamoDb.store(account, device.getId(), preKeys.getPreKeys());
|
||||
@@ -198,14 +191,9 @@ public class KeysController {
|
||||
@Path("/signed")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void setSignedKey(@Auth Account account, @Valid SignedPreKey signedPreKey) {
|
||||
Device device = account.getAuthenticatedDevice().get();
|
||||
boolean wasAccountEnabled = account.isEnabled();
|
||||
Device device = account.getAuthenticatedDevice().get();
|
||||
|
||||
account = accounts.updateDevice(account, device.getId(), d -> d.setSignedPreKey(signedPreKey));
|
||||
|
||||
if (!wasAccountEnabled && account.isEnabled()) {
|
||||
directoryQueue.refreshRegisteredUser(account);
|
||||
}
|
||||
accounts.updateDevice(account, device.getId(), d -> d.setSignedPreKey(signedPreKey));
|
||||
}
|
||||
|
||||
@Timed
|
||||
|
||||
@@ -61,11 +61,15 @@ public class DirectoryQueue {
|
||||
this.sqs = sqs;
|
||||
}
|
||||
|
||||
public void refreshRegisteredUser(final Account account) {
|
||||
refreshRegisteredUsers(List.of(account));
|
||||
public boolean isDiscoverable(final Account account) {
|
||||
return account.isEnabled() && account.isDiscoverableByPhoneNumber();
|
||||
}
|
||||
|
||||
public void refreshRegisteredUsers(final List<Account> accounts) {
|
||||
public void refreshAccount(final Account account) {
|
||||
refreshAccounts(List.of(account));
|
||||
}
|
||||
|
||||
public void refreshAccounts(final List<Account> accounts) {
|
||||
final List<Pair<Account, String>> accountsAndActions = accounts.stream()
|
||||
.map(account -> new Pair<>(account, account.isEnabled() && account.isDiscoverableByPhoneNumber() ? "add" : "delete"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -17,7 +17,10 @@ import javax.security.auth.Subject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
|
||||
import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
public class Account implements Principal {
|
||||
|
||||
@@ -312,6 +315,18 @@ public class Account implements Principal {
|
||||
this.pin = pin;
|
||||
}
|
||||
|
||||
public void setRegistrationLockFromAttributes(final AccountAttributes attributes) {
|
||||
if (!Util.isEmpty(attributes.getPin())) {
|
||||
setPin(attributes.getPin());
|
||||
} else if (!Util.isEmpty(attributes.getRegistrationLock())) {
|
||||
AuthenticationCredentials credentials = new AuthenticationCredentials(attributes.getRegistrationLock());
|
||||
setRegistrationLock(credentials.getHashedAuthenticationToken(), credentials.getSalt());
|
||||
} else {
|
||||
setPin(null);
|
||||
setRegistrationLock(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void setRegistrationLock(String registrationLock, String registrationLockSalt) {
|
||||
requireNotStale();
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.SharedMetricRegistries;
|
||||
import com.codahale.metrics.Timer;
|
||||
@@ -17,6 +18,7 @@ import io.lettuce.core.RedisException;
|
||||
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
@@ -34,6 +36,9 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
|
||||
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
|
||||
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
||||
@@ -52,11 +57,16 @@ public class AccountsManager {
|
||||
private static final Timer getByUuidTimer = metricRegistry.timer(name(AccountsManager.class, "getByUuid" ));
|
||||
private static final Timer deleteTimer = metricRegistry.timer(name(AccountsManager.class, "delete"));
|
||||
|
||||
// TODO Remove this meter when external dependencies have been resolved
|
||||
// Note that this is deliberately namespaced to `AccountController` for metric continuity.
|
||||
private static final Meter newUserMeter = metricRegistry.meter(name(AccountController.class, "brand_new_user"));
|
||||
|
||||
private static final Timer redisSetTimer = metricRegistry.timer(name(AccountsManager.class, "redisSet" ));
|
||||
private static final Timer redisNumberGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisNumberGet"));
|
||||
private static final Timer redisUuidGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisUuidGet" ));
|
||||
private static final Timer redisDeleteTimer = metricRegistry.timer(name(AccountsManager.class, "redisDelete" ));
|
||||
|
||||
private static final String CREATE_COUNTER_NAME = name(AccountsManager.class, "createCounter");
|
||||
private static final String DELETE_COUNTER_NAME = name(AccountsManager.class, "deleteCounter");
|
||||
private static final String DELETE_ERROR_COUNTER_NAME = name(AccountsManager.class, "deleteError");
|
||||
private static final String COUNTRY_CODE_TAG_NAME = "country";
|
||||
@@ -77,6 +87,7 @@ public class AccountsManager {
|
||||
private final MessagesManager messagesManager;
|
||||
private final UsernamesManager usernamesManager;
|
||||
private final ProfilesManager profilesManager;
|
||||
private final StoredVerificationCodeManager pendingAccounts;
|
||||
private final SecureStorageClient secureStorageClient;
|
||||
private final SecureBackupClient secureBackupClient;
|
||||
private final ObjectMapper mapper;
|
||||
@@ -102,7 +113,9 @@ public class AccountsManager {
|
||||
final DeletedAccountsManager deletedAccountsManager,
|
||||
final DirectoryQueue directoryQueue,
|
||||
final KeysDynamoDb keysDynamoDb, final MessagesManager messagesManager, final UsernamesManager usernamesManager,
|
||||
final ProfilesManager profilesManager, final SecureStorageClient secureStorageClient,
|
||||
final ProfilesManager profilesManager,
|
||||
final StoredVerificationCodeManager pendingAccounts,
|
||||
final SecureStorageClient secureStorageClient,
|
||||
final SecureBackupClient secureBackupClient,
|
||||
final ExperimentEnrollmentManager experimentEnrollmentManager,
|
||||
final DynamicConfigurationManager dynamicConfigurationManager) {
|
||||
@@ -115,6 +128,7 @@ public class AccountsManager {
|
||||
this.messagesManager = messagesManager;
|
||||
this.usernamesManager = usernamesManager;
|
||||
this.profilesManager = profilesManager;
|
||||
this.pendingAccounts = pendingAccounts;
|
||||
this.secureStorageClient = secureStorageClient;
|
||||
this.secureBackupClient = secureBackupClient;
|
||||
this.mapper = SystemMapper.getMapper();
|
||||
@@ -126,8 +140,34 @@ public class AccountsManager {
|
||||
this.experimentEnrollmentManager = experimentEnrollmentManager;
|
||||
}
|
||||
|
||||
public boolean create(Account account) {
|
||||
public Account create(final String number,
|
||||
final String password,
|
||||
final String signalAgent,
|
||||
final AccountAttributes accountAttributes) {
|
||||
|
||||
try (Timer.Context ignored = createTimer.time()) {
|
||||
Optional<Account> maybeExistingAccount = get(number);
|
||||
|
||||
Device device = new Device();
|
||||
device.setId(Device.MASTER_ID);
|
||||
device.setAuthenticationCredentials(new AuthenticationCredentials(password));
|
||||
device.setFetchesMessages(accountAttributes.getFetchesMessages());
|
||||
device.setRegistrationId(accountAttributes.getRegistrationId());
|
||||
device.setName(accountAttributes.getName());
|
||||
device.setCapabilities(accountAttributes.getCapabilities());
|
||||
device.setCreated(System.currentTimeMillis());
|
||||
device.setLastSeen(Util.todayInMillis());
|
||||
device.setUserAgent(signalAgent);
|
||||
|
||||
Account account = new Account();
|
||||
account.setNumber(number);
|
||||
account.setUuid(UUID.randomUUID());
|
||||
account.addDevice(device);
|
||||
account.setRegistrationLockFromAttributes(accountAttributes);
|
||||
account.setUnidentifiedAccessKey(accountAttributes.getUnidentifiedAccessKey());
|
||||
account.setUnrestrictedUnidentifiedAccess(accountAttributes.isUnrestrictedUnidentifiedAccess());
|
||||
account.setDiscoverableByPhoneNumber(accountAttributes.isDiscoverableByPhoneNumber());
|
||||
|
||||
final UUID originalUuid = account.getUuid();
|
||||
boolean freshUser = databaseCreate(account);
|
||||
|
||||
@@ -165,12 +205,36 @@ public class AccountsManager {
|
||||
|
||||
redisSet(account);
|
||||
|
||||
return freshUser;
|
||||
final Tags tags;
|
||||
|
||||
if (freshUser) {
|
||||
tags = Tags.of("type", "new");
|
||||
} else {
|
||||
tags = Tags.of("type", "reregister");
|
||||
}
|
||||
|
||||
Metrics.counter(CREATE_COUNTER_NAME, tags).increment();
|
||||
|
||||
if (!account.isDiscoverableByPhoneNumber()) {
|
||||
// The newly-created account has explicitly opted out of discoverability
|
||||
directoryQueue.deleteAccount(account);
|
||||
}
|
||||
|
||||
maybeExistingAccount.ifPresent(definitelyExistingAccount -> {
|
||||
messagesManager.clear(definitelyExistingAccount.getUuid());
|
||||
keysDynamoDb.delete(definitelyExistingAccount);
|
||||
});
|
||||
|
||||
pendingAccounts.remove(number);
|
||||
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
public Account update(Account account, Consumer<Account> updater) {
|
||||
|
||||
final boolean wasDiscoverableBeforeUpdate = directoryQueue.isDiscoverable(account);
|
||||
|
||||
final Account updatedAccount;
|
||||
|
||||
try (Timer.Context ignored = updateTimer.time()) {
|
||||
@@ -212,6 +276,12 @@ public class AccountsManager {
|
||||
redisSet(updatedAccount);
|
||||
}
|
||||
|
||||
final boolean isDiscoverableAfterUpdate = directoryQueue.isDiscoverable(updatedAccount);
|
||||
|
||||
if (wasDiscoverableBeforeUpdate != isDiscoverableAfterUpdate) {
|
||||
directoryQueue.refreshAccount(updatedAccount);
|
||||
}
|
||||
|
||||
return updatedAccount;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,11 +27,9 @@ public class PushFeedbackProcessor extends AccountDatabaseCrawlerListener {
|
||||
private final Meter recovered = metricRegistry.meter(name(getClass(), "unregistered", "recovered"));
|
||||
|
||||
private final AccountsManager accountsManager;
|
||||
private final DirectoryQueue directoryQueue;
|
||||
|
||||
public PushFeedbackProcessor(AccountsManager accountsManager, DirectoryQueue directoryQueue) {
|
||||
public PushFeedbackProcessor(AccountsManager accountsManager) {
|
||||
this.accountsManager = accountsManager;
|
||||
this.directoryQueue = directoryQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,8 +40,6 @@ public class PushFeedbackProcessor extends AccountDatabaseCrawlerListener {
|
||||
|
||||
@Override
|
||||
protected void onCrawlChunk(Optional<UUID> fromUuid, List<Account> chunkAccounts) {
|
||||
final List<Account> directoryUpdateAccounts = new ArrayList<>();
|
||||
|
||||
for (Account account : chunkAccounts) {
|
||||
boolean update = false;
|
||||
|
||||
@@ -62,7 +58,7 @@ public class PushFeedbackProcessor extends AccountDatabaseCrawlerListener {
|
||||
if (update) {
|
||||
// fetch a new version, since the chunk is shared and implicitly read-only
|
||||
accountsManager.get(account.getUuid()).ifPresent(accountToUpdate -> {
|
||||
Account updatedAccount = accountsManager.update(accountToUpdate, a -> {
|
||||
accountsManager.update(accountToUpdate, a -> {
|
||||
for (Device device : a.getDevices()) {
|
||||
if (deviceNeedsUpdate(device)) {
|
||||
if (deviceExpired(device)) {
|
||||
@@ -85,14 +81,9 @@ public class PushFeedbackProcessor extends AccountDatabaseCrawlerListener {
|
||||
}
|
||||
}
|
||||
});
|
||||
directoryUpdateAccounts.add(updatedAccount);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!directoryUpdateAccounts.isEmpty()) {
|
||||
directoryQueue.refreshRegisteredUsers(directoryUpdateAccounts);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean deviceNeedsUpdate(final Device device) {
|
||||
|
||||
@@ -56,8 +56,10 @@ import org.whispersystems.textsecuregcm.storage.ProfilesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.ReportMessageDynamoDb;
|
||||
import org.whispersystems.textsecuregcm.storage.ReportMessageManager;
|
||||
import org.whispersystems.textsecuregcm.storage.ReservedUsernames;
|
||||
import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Usernames;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernamesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.VerificationCodeStore;
|
||||
import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
|
||||
@@ -137,6 +139,8 @@ public class DeleteUserCommand extends EnvironmentCommand<WhisperServerConfigura
|
||||
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
|
||||
DynamoDbClient migrationRetryAccountsDynamoDb = DynamoDbFromConfig.client(configuration.getMigrationRetryAccountsDynamoDbConfiguration(),
|
||||
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
|
||||
DynamoDbClient pendingAccountsDynamoDbClient = DynamoDbFromConfig.client(configuration.getPendingAccountsDynamoDbConfiguration(),
|
||||
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
|
||||
|
||||
AmazonDynamoDB deletedAccountsLockDynamoDbClient = AmazonDynamoDBClientBuilder.standard()
|
||||
.withRegion(configuration.getDeletedAccountsLockDynamoDbConfiguration().getRegion())
|
||||
@@ -148,6 +152,7 @@ public class DeleteUserCommand extends EnvironmentCommand<WhisperServerConfigura
|
||||
DeletedAccounts deletedAccounts = new DeletedAccounts(deletedAccountsDynamoDbClient, configuration.getDeletedAccountsDynamoDbConfiguration().getTableName(), configuration.getDeletedAccountsDynamoDbConfiguration().getNeedsReconciliationIndexName());
|
||||
MigrationDeletedAccounts migrationDeletedAccounts = new MigrationDeletedAccounts(migrationDeletedAccountsDynamoDb, configuration.getMigrationDeletedAccountsDynamoDbConfiguration().getTableName());
|
||||
MigrationRetryAccounts migrationRetryAccounts = new MigrationRetryAccounts(migrationRetryAccountsDynamoDb, configuration.getMigrationRetryAccountsDynamoDbConfiguration().getTableName());
|
||||
VerificationCodeStore pendingAccounts = new VerificationCodeStore(pendingAccountsDynamoDbClient, configuration.getPendingAccountsDynamoDbConfiguration().getTableName());
|
||||
|
||||
Accounts accounts = new Accounts(accountDatabase);
|
||||
AccountsDynamoDb accountsDynamoDb = new AccountsDynamoDb(accountsDynamoDbClient, accountsDynamoDbAsyncClient, accountsDynamoDbMigrationThreadPool, configuration.getAccountsDynamoDbConfiguration().getTableName(), configuration.getAccountsDynamoDbConfiguration().getPhoneNumberTableName(), migrationDeletedAccounts, migrationRetryAccounts);
|
||||
@@ -170,7 +175,8 @@ public class DeleteUserCommand extends EnvironmentCommand<WhisperServerConfigura
|
||||
ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, Metrics.globalRegistry);
|
||||
MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, pushLatencyManager, reportMessageManager);
|
||||
DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, deletedAccountsLockDynamoDbClient, configuration.getDeletedAccountsLockDynamoDbConfiguration().getTableName());
|
||||
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccountsManager, directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
|
||||
StoredVerificationCodeManager pendingAccountsManager = new StoredVerificationCodeManager(pendingAccounts);
|
||||
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccountsManager, directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, pendingAccountsManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
|
||||
|
||||
for (String user: users) {
|
||||
Optional<Account> account = accountsManager.get(user);
|
||||
|
||||
Reference in New Issue
Block a user