Consolidate account creation/directory updates into AccountsManager

This commit is contained in:
Jon Chambers
2021-07-27 10:27:47 -04:00
committed by GitHub
parent 917f667229
commit 8579190cdf
16 changed files with 368 additions and 589 deletions

View File

@@ -40,7 +40,7 @@ public class DirectoryQueueTest {
when(account.isEnabled()).thenReturn(accountEnabled);
when(account.isDiscoverableByPhoneNumber()).thenReturn(accountDiscoverableByPhoneNumber);
directoryQueue.refreshRegisteredUser(account);
directoryQueue.refreshAccount(account);
final ArgumentCaptor<SendMessageBatchRequest> requestCaptor = ArgumentCaptor.forClass(SendMessageBatchRequest.class);
verify(sqs).sendMessageBatch(requestCaptor.capture());
@@ -68,7 +68,7 @@ public class DirectoryQueueTest {
when(undiscoverableAccount.isEnabled()).thenReturn(true);
when(undiscoverableAccount.isDiscoverableByPhoneNumber()).thenReturn(false);
directoryQueue.refreshRegisteredUsers(List.of(discoverableAccount, undiscoverableAccount));
directoryQueue.refreshAccounts(List.of(discoverableAccount, undiscoverableAccount));
final ArgumentCaptor<SendMessageBatchRequest> requestCaptor = ArgumentCaptor.forClass(SendMessageBatchRequest.class);
verify(sqs).sendMessageBatch(requestCaptor.capture());
@@ -97,7 +97,7 @@ public class DirectoryQueueTest {
when(account.isEnabled()).thenReturn(true);
when(account.isDiscoverableByPhoneNumber()).thenReturn(true);
directoryQueue.refreshRegisteredUser(account);
directoryQueue.refreshAccount(account);
final ArgumentCaptor<SendMessageBatchRequest> requestCaptor = ArgumentCaptor.forClass(SendMessageBatchRequest.class);
verify(sqs, times(2)).sendMessageBatch(requestCaptor.capture());

View File

@@ -44,6 +44,7 @@ import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicAccountsDynamoDbMigrationConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
@@ -154,6 +155,7 @@ class AccountsManagerConcurrentModificationIntegrationTest {
mock(MessagesManager.class),
mock(UsernamesManager.class),
mock(ProfilesManager.class),
mock(StoredVerificationCodeManager.class),
mock(SecureStorageClient.class),
mock(SecureBackupClient.class),
experimentEnrollmentManager,
@@ -164,9 +166,30 @@ class AccountsManagerConcurrentModificationIntegrationTest {
@Test
void testConcurrentUpdate() throws IOException {
final UUID uuid = UUID.randomUUID();
final UUID uuid;
{
final Account account = accountsManager.update(
accountsManager.create("+14155551212", "password", null, new AccountAttributes()),
a -> {
a.setUnidentifiedAccessKey(new byte[16]);
accountsManager.create(generateAccount("+14155551212", uuid));
final Random random = new Random();
final SignedPreKey signedPreKey = new SignedPreKey(random.nextInt(), "testPublicKey-" + random.nextInt(),
"testSignature-" + random.nextInt());
a.removeDevice(1);
a.addDevice(new Device(1, "testName-" + random.nextInt(), "testAuthToken-" + random.nextInt(),
"testSalt-" + random.nextInt(),
"testGcmId-" + random.nextInt(), "testApnId-" + random.nextInt(), "testVoipApnId-" + random.nextInt(),
random.nextBoolean(), random.nextInt(), signedPreKey, random.nextInt(), random.nextInt(),
"testUserAgent-" + random.nextInt(), 0,
new Device.DeviceCapabilities(random.nextBoolean(), random.nextBoolean(), random.nextBoolean(),
random.nextBoolean(), random.nextBoolean(), random.nextBoolean(),
random.nextBoolean(), random.nextBoolean())));
});
uuid = account.getUuid();
}
final String profileName = "name";
final String avatar = "avatar";
@@ -249,26 +272,4 @@ class AccountsManagerConcurrentModificationIntegrationTest {
accountsManager.updateDevice(account, deviceId, deviceMutation);
}, mutationExecutor);
}
private Account generateAccount(String number, UUID uuid) {
Device device = generateDevice(1);
return generateAccount(number, uuid, Collections.singleton(device));
}
private Account generateAccount(String number, UUID uuid, Set<Device> devices) {
byte[] unidentifiedAccessKey = new byte[16];
Random random = new Random(System.currentTimeMillis());
Arrays.fill(unidentifiedAccessKey, (byte)random.nextInt(255));
return new Account(number, uuid, devices, unidentifiedAccessKey);
}
private Device generateDevice(long id) {
Random random = new Random(System.currentTimeMillis());
SignedPreKey signedPreKey = new SignedPreKey(random.nextInt(), "testPublicKey-" + random.nextInt(), "testSignature-" + random.nextInt());
return new Device(id, "testName-" + random.nextInt(), "testAuthToken-" + random.nextInt(), "testSalt-" + random.nextInt(),
"testGcmId-" + random.nextInt(), "testApnId-" + random.nextInt(), "testVoipApnId-" + random.nextInt(), random.nextBoolean(), random.nextInt(), signedPreKey, random.nextInt(), random.nextInt(), "testUserAgent-" + random.nextInt() , 0, new Device.DeviceCapabilities(random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean(),
random.nextBoolean(), random.nextBoolean()));
}
}

View File

@@ -10,8 +10,17 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.*;
import static org.whispersystems.textsecuregcm.tests.util.AccountsHelper.eqUuid;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
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 com.google.common.collect.ImmutableSet;
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
@@ -42,7 +51,7 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.stubbing.Answer;
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
@@ -70,14 +79,11 @@ 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.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.tests.util.AccountsHelper;
@@ -121,9 +127,6 @@ class AccountControllerTest {
private static RateLimiter autoBlockLimiter = mock(RateLimiter.class);
private static RateLimiter usernameSetLimiter = mock(RateLimiter.class);
private static SmsSender smsSender = mock(SmsSender.class);
private static DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
private static MessagesManager storedMessages = mock(MessagesManager.class);
private static KeysDynamoDb keys = mock(KeysDynamoDb.class);
private static TurnTokenGenerator turnTokenGenerator = mock(TurnTokenGenerator.class);
private static Account senderPinAccount = mock(Account.class);
private static Account senderRegLockAccount = mock(Account.class);
@@ -155,9 +158,6 @@ class AccountControllerTest {
abusiveHostRules,
rateLimiters,
smsSender,
directoryQueue,
storedMessages,
keys,
dynamicConfigurationManager,
turnTokenGenerator,
new HashMap<>(),
@@ -218,6 +218,14 @@ class AccountControllerTest {
when(accountsManager.get(eq(SENDER_HAS_STORAGE))).thenReturn(Optional.of(senderHasStorage));
when(accountsManager.get(eq(SENDER_TRANSFER))).thenReturn(Optional.of(senderTransfer));
when(accountsManager.create(any(), any(), any(), any())).thenAnswer((Answer<Account>) invocation -> {
final Account account = mock(Account.class);
when(account.getUuid()).thenReturn(UUID.randomUUID());
when(account.getNumber()).thenReturn(invocation.getArgument(0, String.class));
return account;
});
when(usernamesManager.put(eq(AuthHelper.VALID_UUID), eq("n00bkiller"))).thenReturn(true);
when(usernamesManager.put(eq(AuthHelper.VALID_UUID), eq("takenusername"))).thenReturn(false);
@@ -261,9 +269,6 @@ class AccountControllerTest {
autoBlockLimiter,
usernameSetLimiter,
smsSender,
directoryQueue,
storedMessages,
keys,
turnTokenGenerator,
senderPinAccount,
senderRegLockAccount,
@@ -922,23 +927,13 @@ class AccountControllerTest {
Optional.of(new StoredVerificationCode("1234", System.currentTimeMillis(), "1234-push", "VerificationSid")));;
}
AccountCreationResult result =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "1234"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER, "bar"))
.put(Entity.entity(new AccountAttributes(false, 2222, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "1234"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER, "bar"))
.put(Entity.entity(new AccountAttributes(), MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
assertThat(result.getUuid()).isNotNull();
assertThat(result.isStorageCapable()).isFalse();
final ArgumentCaptor<Account> accountArgumentCaptor = ArgumentCaptor.forClass(Account.class);
verify(accountsManager, times(1)).create(accountArgumentCaptor.capture());
verify(directoryQueue, times(1)).refreshRegisteredUser(argThat(account -> SENDER.equals(account.getNumber())));
assertThat(accountArgumentCaptor.getValue().isDiscoverableByPhoneNumber()).isTrue();
verify(accountsManager).create(eq(SENDER), eq("bar"), any(), any());
if (enrolledInVerifyExperiment) {
verify(smsSender).reportVerificationSucceeded("VerificationSid");
@@ -946,52 +941,14 @@ class AccountControllerTest {
}
@Test
void testVerifyCodeUndiscoverable() throws Exception {
AccountCreationResult result =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "1234"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER, "bar"))
.put(Entity.entity(new AccountAttributes(false, 2222, null, null, null, false, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
assertThat(result.getUuid()).isNotNull();
assertThat(result.isStorageCapable()).isFalse();
final ArgumentCaptor<Account> accountArgumentCaptor = ArgumentCaptor.forClass(Account.class);
verify(accountsManager, times(1)).create(accountArgumentCaptor.capture());
verify(directoryQueue, times(1)).refreshRegisteredUser(argThat(account -> SENDER.equals(account.getNumber())));
assertThat(accountArgumentCaptor.getValue().isDiscoverableByPhoneNumber()).isFalse();
}
@Test
void testVerifySupportsStorage() throws Exception {
AccountCreationResult result =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "666666"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_HAS_STORAGE, "bar"))
.put(Entity.entity(new AccountAttributes(false, 2222, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
assertThat(result.getUuid()).isNotNull();
assertThat(result.isStorageCapable()).isTrue();
verify(accountsManager, times(1)).create(isA(Account.class));
verify(directoryQueue, times(1)).refreshRegisteredUser(argThat(account -> SENDER_HAS_STORAGE.equals(account.getNumber())));
}
@Test
void testVerifyCodeOld() throws Exception {
void testVerifyCodeOld() {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "1234"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_OLD, "bar"))
.put(Entity.entity(new AccountAttributes(false, 2222, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE));
.target(String.format("/v1/accounts/code/%s", "1234"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_OLD, "bar"))
.put(Entity.entity(new AccountAttributes(false, 2222, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(403);
@@ -999,14 +956,14 @@ class AccountControllerTest {
}
@Test
void testVerifyBadCode() throws Exception {
void testVerifyBadCode() {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "1111"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE));
.target(String.format("/v1/accounts/code/%s", "1111"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(403);
@@ -1017,11 +974,11 @@ class AccountControllerTest {
void testVerifyPin() throws Exception {
AccountCreationResult result =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "333333"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_PIN, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, "31337", null, true, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
.target(String.format("/v1/accounts/code/%s", "333333"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_PIN, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, "31337", null, true, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
assertThat(result.getUuid()).isNotNull();
@@ -1032,11 +989,11 @@ class AccountControllerTest {
void testVerifyRegistrationLock() throws Exception {
AccountCreationResult result =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "666666"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, Hex.toStringCondensed(registration_lock_key), true, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
.target(String.format("/v1/accounts/code/%s", "666666"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, Hex.toStringCondensed(registration_lock_key), true, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
assertThat(result.getUuid()).isNotNull();
@@ -1045,36 +1002,24 @@ class AccountControllerTest {
@Test
void testVerifyRegistrationLockSetsRegistrationLockOnNewAccount() throws Exception {
AccountCreationResult result =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "666666"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, Hex.toStringCondensed(registration_lock_key), true, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
.target(String.format("/v1/accounts/code/%s", "666666"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, Hex.toStringCondensed(registration_lock_key), true, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
assertThat(result.getUuid()).isNotNull();
verify(pinLimiter).validate(eq(SENDER_REG_LOCK));
verify(accountsManager).create(argThat(new ArgumentMatcher<>() {
@Override
public boolean matches(final Account account) {
final StoredRegistrationLock regLock = account.getRegistrationLock();
return regLock.requiresClientRegistrationLock() && regLock.verify(Hex.toStringCondensed(registration_lock_key), null);
}
@Override
public String toString() {
return "Account that has registration lock set";
}
}));
verify(accountsManager).create(eq(SENDER_REG_LOCK), eq("bar"), any(), argThat(
attributes -> Hex.toStringCondensed(registration_lock_key).equals(attributes.getRegistrationLock())));
}
@Test
void testVerifyRegistrationLockOld() throws Exception {
void testVerifyRegistrationLockOld() {
StoredRegistrationLock lock = senderRegLockAccount.getRegistrationLock();
try {
@@ -1082,11 +1027,11 @@ class AccountControllerTest {
AccountCreationResult result =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "666666"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
.target(String.format("/v1/accounts/code/%s", "666666"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
assertThat(result.getUuid()).isNotNull();
@@ -1100,11 +1045,11 @@ class AccountControllerTest {
void testVerifyWrongPin() throws Exception {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "333333"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_PIN, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, "31338", null, true, null),
MediaType.APPLICATION_JSON_TYPE));
.target(String.format("/v1/accounts/code/%s", "333333"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_PIN, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, "31338", null, true, null),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(423);
@@ -1115,12 +1060,12 @@ class AccountControllerTest {
void testVerifyWrongRegistrationLock() throws Exception {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "666666"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null,
Hex.toStringCondensed(new byte[32]), null, true, null),
MediaType.APPLICATION_JSON_TYPE));
.target(String.format("/v1/accounts/code/%s", "666666"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null,
Hex.toStringCondensed(new byte[32]), null, true, null),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(423);
@@ -1131,11 +1076,11 @@ class AccountControllerTest {
void testVerifyNoPin() throws Exception {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "333333"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_PIN, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE));
.target(String.format("/v1/accounts/code/%s", "333333"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_PIN, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(423);
@@ -1149,11 +1094,11 @@ class AccountControllerTest {
void testVerifyNoRegistrationLock() throws Exception {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "666666"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE));
.target(String.format("/v1/accounts/code/%s", "666666"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(423);
@@ -1172,11 +1117,11 @@ class AccountControllerTest {
void testVerifyLimitPin() throws Exception {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "444444"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_OVER_PIN, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, "31337", null, true, null),
MediaType.APPLICATION_JSON_TYPE));
.target(String.format("/v1/accounts/code/%s", "444444"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_OVER_PIN, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, "31337", null, true, null),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(413);
@@ -1190,11 +1135,11 @@ class AccountControllerTest {
AccountCreationResult result =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "444444"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_OVER_PIN, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
.target(String.format("/v1/accounts/code/%s", "444444"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_OVER_PIN, "bar"))
.put(Entity.entity(new AccountAttributes(false, 3333, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
assertThat(result.getUuid()).isNotNull();
@@ -1208,13 +1153,13 @@ class AccountControllerTest {
when(senderTransfer.isTransferSupported()).thenReturn(true);
final Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "1234"))
.queryParam("transfer", true)
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_TRANSFER, "bar"))
.put(Entity.entity(new AccountAttributes(false, 2222, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE));
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "1234"))
.queryParam("transfer", true)
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_TRANSFER, "bar"))
.put(Entity.entity(new AccountAttributes(false, 2222, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(409);
}
@@ -1224,13 +1169,13 @@ class AccountControllerTest {
when(senderTransfer.isTransferSupported()).thenReturn(false);
final Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "1234"))
.queryParam("transfer", true)
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_TRANSFER, "bar"))
.put(Entity.entity(new AccountAttributes(false, 2222, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE));
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "1234"))
.queryParam("transfer", true)
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_TRANSFER, "bar"))
.put(Entity.entity(new AccountAttributes(false, 2222, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(200);
}
@@ -1240,41 +1185,14 @@ class AccountControllerTest {
when(senderTransfer.isTransferSupported()).thenReturn(true);
final Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "1234"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER_TRANSFER, "bar"))
.put(Entity.entity(new AccountAttributes(false, 2222, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
void testVerifyReregistration() throws InterruptedException {
final UUID existingUuid = UUID.randomUUID();
final Account existingAccount = mock(Account.class);
when(existingAccount.getUuid()).thenReturn(existingUuid);
when(accountsManager.get(SENDER)).thenReturn(Optional.of(existingAccount));
AccountCreationResult result =
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "1234"))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER, "bar"))
.header("Authorization", AuthHelper.getAuthHeader(SENDER_TRANSFER, "bar"))
.put(Entity.entity(new AccountAttributes(false, 2222, null, null, null, true, null),
MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class);
MediaType.APPLICATION_JSON_TYPE));
final ArgumentCaptor<Account> accountArgumentCaptor = ArgumentCaptor.forClass(Account.class);
verify(accountsManager, times(1)).create(accountArgumentCaptor.capture());
verify(directoryQueue, times(1)).refreshRegisteredUser(argThat(account -> SENDER.equals(account.getNumber())));
verify(storedMessages).clear(existingUuid);
verify(keys).delete(existingAccount);
assertThat(accountArgumentCaptor.getValue().isDiscoverableByPhoneNumber()).isTrue();
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
@@ -1388,7 +1306,6 @@ class AccountControllerTest {
verify(AuthHelper.DISABLED_DEVICE, times(1)).setGcmId(eq("c00lz0rz"));
verify(accountsManager, times(1)).updateDevice(eq(AuthHelper.DISABLED_ACCOUNT), anyLong(), any());
verify(directoryQueue, never()).refreshRegisteredUser(any(Account.class));
}
@Test
@@ -1404,7 +1321,6 @@ class AccountControllerTest {
verify(AuthHelper.DISABLED_DEVICE, times(1)).setGcmId(eq("z000"));
verify(accountsManager, times(1)).updateDevice(eq(AuthHelper.DISABLED_ACCOUNT), anyLong(), any());
verify(directoryQueue, never()).refreshRegisteredUser(any(Account.class));
}
@Test
@@ -1421,7 +1337,6 @@ class AccountControllerTest {
verify(AuthHelper.DISABLED_DEVICE, times(1)).setApnId(eq("first"));
verify(AuthHelper.DISABLED_DEVICE, times(1)).setVoipApnId(eq("second"));
verify(accountsManager, times(1)).updateDevice(eq(AuthHelper.DISABLED_ACCOUNT), anyLong(), any());
verify(directoryQueue, never()).refreshRegisteredUser(any(Account.class));
}
@Test
@@ -1438,7 +1353,6 @@ class AccountControllerTest {
verify(AuthHelper.DISABLED_DEVICE, times(1)).setApnId(eq("first"));
verify(AuthHelper.DISABLED_DEVICE, times(1)).setVoipApnId(null);
verify(accountsManager, times(1)).updateDevice(eq(AuthHelper.DISABLED_ACCOUNT), anyLong(), any());
verify(directoryQueue, never()).refreshRegisteredUser(any(Account.class));
}
@Test
@@ -1455,7 +1369,6 @@ class AccountControllerTest {
verify(AuthHelper.DISABLED_DEVICE, times(1)).setApnId(eq("third"));
verify(AuthHelper.DISABLED_DEVICE, times(1)).setVoipApnId(eq("fourth"));
verify(accountsManager, times(1)).updateDevice(eq(AuthHelper.DISABLED_ACCOUNT), anyLong(), any());
verify(directoryQueue, never()).refreshRegisteredUser(any(Account.class));
}
@ParameterizedTest
@@ -1566,7 +1479,6 @@ class AccountControllerTest {
.put(Entity.json(new AccountAttributes(false, 2222, null, null, null, true, null)));
assertThat(response.getStatus()).isEqualTo(204);
verify(directoryQueue, never()).refreshRegisteredUser(any());
}
@Test
@@ -1579,7 +1491,6 @@ class AccountControllerTest {
.put(Entity.json(new AccountAttributes(false, 2222, null, null, null, true, null)));
assertThat(response.getStatus()).isEqualTo(204);
verify(directoryQueue, times(1)).refreshRegisteredUser(eqUuid(AuthHelper.UNDISCOVERABLE_ACCOUNT));
}
@Test
@@ -1592,7 +1503,6 @@ class AccountControllerTest {
.put(Entity.json(new AccountAttributes(false, 2222, null, null, null, false, null)));
assertThat(response.getStatus()).isEqualTo(204);
verify(directoryQueue, times(1)).refreshRegisteredUser(eqUuid(AuthHelper.VALID_ACCOUNT));
}
@Test

View File

@@ -45,7 +45,6 @@ import org.whispersystems.textsecuregcm.entities.DeviceResponse;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
@@ -65,11 +64,10 @@ class DeviceControllerTest {
AccountsManager accounts,
MessagesManager messages,
KeysDynamoDb keys,
DirectoryQueue cdsSender,
RateLimiters rateLimiters,
Map<String, Integer> deviceConfiguration)
{
super(pendingDevices, accounts, messages, keys, cdsSender, rateLimiters, deviceConfiguration);
super(pendingDevices, accounts, messages, keys, rateLimiters, deviceConfiguration);
}
@Override
@@ -82,7 +80,6 @@ class DeviceControllerTest {
private static AccountsManager accountsManager = mock(AccountsManager.class );
private static MessagesManager messagesManager = mock(MessagesManager.class);
private static KeysDynamoDb keys = mock(KeysDynamoDb.class);
private static DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
private static RateLimiters rateLimiters = mock(RateLimiters.class );
private static RateLimiter rateLimiter = mock(RateLimiter.class );
private static Account account = mock(Account.class );
@@ -100,7 +97,6 @@ class DeviceControllerTest {
accountsManager,
messagesManager,
keys,
directoryQueue,
rateLimiters,
deviceConfiguration))
.build();
@@ -142,7 +138,6 @@ class DeviceControllerTest {
accountsManager,
messagesManager,
keys,
directoryQueue,
rateLimiters,
rateLimiter,
account,

View File

@@ -57,7 +57,6 @@ import org.whispersystems.textsecuregcm.limits.RateLimitChallengeManager;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.mappers.RateLimitChallengeExceptionMapper;
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
@@ -92,7 +91,6 @@ class KeysControllerTest {
private final static KeysDynamoDb keysDynamoDb = mock(KeysDynamoDb.class );
private final static AccountsManager accounts = mock(AccountsManager.class );
private final static DirectoryQueue directoryQueue = mock(DirectoryQueue.class );
private final static PreKeyRateLimiter preKeyRateLimiter = mock(PreKeyRateLimiter.class );
private final static RateLimitChallengeManager rateLimitChallengeManager = mock(RateLimitChallengeManager.class );
private final static Account existsAccount = mock(Account.class );
@@ -107,7 +105,7 @@ class KeysControllerTest {
.addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class)))
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new RateLimitChallengeExceptionMapper(rateLimitChallengeManager))
.addResource(new KeysController(rateLimiters, keysDynamoDb, accounts, directoryQueue, preKeyRateLimiter, dynamicConfigurationManager, rateLimitChallengeManager))
.addResource(new KeysController(rateLimiters, keysDynamoDb, accounts, preKeyRateLimiter, dynamicConfigurationManager, rateLimitChallengeManager))
.build();
@BeforeEach
@@ -184,7 +182,6 @@ class KeysControllerTest {
reset(
keysDynamoDb,
accounts,
directoryQueue,
preKeyRateLimiter,
existsAccount,
rateLimiters,

View File

@@ -19,8 +19,8 @@ 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.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import io.lettuce.core.RedisException;
@@ -30,17 +30,20 @@ import java.util.HashSet;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor;
import org.mockito.stubbing.Answer;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicAccountsDynamoDbMigrationConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
@@ -49,21 +52,28 @@ import org.whispersystems.textsecuregcm.storage.Accounts;
import org.whispersystems.textsecuregcm.storage.AccountsDynamoDb;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.ContestedOptimisticLockException;
import org.whispersystems.textsecuregcm.storage.DeletedAccounts;
import org.whispersystems.textsecuregcm.storage.DeletedAccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.KeysDynamoDb;
import org.whispersystems.textsecuregcm.storage.MessagesManager;
import org.whispersystems.textsecuregcm.storage.ProfilesManager;
import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager;
import org.whispersystems.textsecuregcm.storage.UsernamesManager;
import org.whispersystems.textsecuregcm.tests.util.JsonHelpers;
import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper;
class AccountsManagerTest {
private DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
private ExperimentEnrollmentManager experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class);
private Accounts accounts;
private AccountsDynamoDb accountsDynamoDb;
private DirectoryQueue directoryQueue;
private DynamicConfigurationManager dynamicConfigurationManager;
private ExperimentEnrollmentManager experimentEnrollmentManager;
private RedisAdvancedClusterCommands<String, String> commands;
private AccountsManager accountsManager;
private static final Answer<?> ACCOUNT_UPDATE_ANSWER = (answer) -> {
// it is implicit in the update() contract is that a successful call will
@@ -75,49 +85,57 @@ class AccountsManagerTest {
@BeforeEach
void setup() {
accounts = mock(Accounts.class);
accountsDynamoDb = mock(AccountsDynamoDb.class);
directoryQueue = mock(DirectoryQueue.class);
dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class);
DynamicConfiguration dynamicConfiguration = new DynamicConfiguration();
//noinspection unchecked
commands = mock(RedisAdvancedClusterCommands.class);
final DynamicConfiguration dynamicConfiguration = new DynamicConfiguration();
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
accountsManager = new AccountsManager(
accounts,
accountsDynamoDb,
RedisClusterHelper.buildMockRedisCluster(commands),
mock(DeletedAccountsManager.class),
directoryQueue,
mock(KeysDynamoDb.class),
mock(MessagesManager.class),
mock(UsernamesManager.class),
mock(ProfilesManager.class),
mock(StoredVerificationCodeManager.class),
mock(SecureStorageClient.class),
mock(SecureBackupClient.class),
experimentEnrollmentManager,
dynamicConfigurationManager);
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testGetAccountByNumberInCache(final boolean dynamoEnabled) {
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
Accounts accounts = mock(Accounts.class);
AccountsDynamoDb accountsDynamoDb = mock(AccountsDynamoDb.class);
DeletedAccountsManager deletedAccounts = mock(DeletedAccountsManager.class);
DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
KeysDynamoDb keysDynamoDb = mock(KeysDynamoDb.class);
MessagesManager messagesManager = mock(MessagesManager.class);
UsernamesManager usernamesManager = mock(UsernamesManager.class);
ProfilesManager profilesManager = mock(ProfilesManager.class);
SecureBackupClient secureBackupClient = mock(SecureBackupClient.class);
SecureStorageClient secureStorageClient = mock(SecureStorageClient.class);
UUID uuid = UUID.randomUUID();
enableDynamo(dynamoEnabled);
when(commands.get(eq("AccountMap::+14152222222"))).thenReturn(uuid.toString());
when(commands.get(eq("Account3::" + uuid.toString()))).thenReturn("{\"number\": \"+14152222222\", \"name\": \"test\"}");
when(commands.get(eq("Account3::" + uuid))).thenReturn("{\"number\": \"+14152222222\", \"name\": \"test\"}");
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccounts,
directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
Optional<Account> account = accountsManager.get("+14152222222");
Optional<Account> account = accountsManager.get("+14152222222");
assertTrue(account.isPresent());
assertEquals(account.get().getNumber(), "+14152222222");
assertEquals(account.get().getProfileName(), "test");
verify(commands, times(1)).get(eq("AccountMap::+14152222222"));
verify(commands, times(1)).get(eq("Account3::" + uuid.toString()));
verify(commands, times(1)).get(eq("Account3::" + uuid));
verifyNoMoreInteractions(commands);
verifyNoMoreInteractions(accounts);
verifyZeroInteractions(accountsDynamoDb);
verifyNoInteractions(accountsDynamoDb);
}
private void enableDynamo(boolean dynamoEnabled) {
@@ -136,75 +154,46 @@ class AccountsManagerTest {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testGetAccountByUuidInCache(boolean dynamoEnabled) {
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
Accounts accounts = mock(Accounts.class);
AccountsDynamoDb accountsDynamoDb = mock(AccountsDynamoDb.class);
DeletedAccountsManager deletedAccounts = mock(DeletedAccountsManager.class);
DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
KeysDynamoDb keysDynamoDb = mock(KeysDynamoDb.class);
MessagesManager messagesManager = mock(MessagesManager.class);
UsernamesManager usernamesManager = mock(UsernamesManager.class);
ProfilesManager profilesManager = mock(ProfilesManager.class);
SecureBackupClient secureBackupClient = mock(SecureBackupClient.class);
SecureStorageClient secureStorageClient = mock(SecureStorageClient.class);
UUID uuid = UUID.randomUUID();
enableDynamo(dynamoEnabled);
when(commands.get(eq("Account3::" + uuid.toString()))).thenReturn("{\"number\": \"+14152222222\", \"name\": \"test\"}");
when(commands.get(eq("Account3::" + uuid))).thenReturn("{\"number\": \"+14152222222\", \"name\": \"test\"}");
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccounts,
directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
Optional<Account> account = accountsManager.get(uuid);
Optional<Account> account = accountsManager.get(uuid);
assertTrue(account.isPresent());
assertEquals(account.get().getNumber(), "+14152222222");
assertEquals(account.get().getUuid(), uuid);
assertEquals(account.get().getProfileName(), "test");
verify(commands, times(1)).get(eq("Account3::" + uuid.toString()));
verify(commands, times(1)).get(eq("Account3::" + uuid));
verifyNoMoreInteractions(commands);
verifyNoMoreInteractions(accounts);
verifyZeroInteractions(accountsDynamoDb);
verifyNoInteractions(accountsDynamoDb);
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testGetAccountByNumberNotInCache(boolean dynamoEnabled) {
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
Accounts accounts = mock(Accounts.class);
AccountsDynamoDb accountsDynamoDb = mock(AccountsDynamoDb.class);
DeletedAccountsManager deletedAccounts = mock(DeletedAccountsManager.class);
DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
KeysDynamoDb keysDynamoDb = mock(KeysDynamoDb.class);
MessagesManager messagesManager = mock(MessagesManager.class);
UsernamesManager usernamesManager = mock(UsernamesManager.class);
ProfilesManager profilesManager = mock(ProfilesManager.class);
SecureBackupClient secureBackupClient = mock(SecureBackupClient.class);
SecureStorageClient secureStorageClient = mock(SecureStorageClient.class);
UUID uuid = UUID.randomUUID();
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
UUID uuid = UUID.randomUUID();
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
enableDynamo(dynamoEnabled);
when(commands.get(eq("AccountMap::+14152222222"))).thenReturn(null);
when(accounts.get(eq("+14152222222"))).thenReturn(Optional.of(account));
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccounts,
directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
Optional<Account> retrieved = accountsManager.get("+14152222222");
Optional<Account> retrieved = accountsManager.get("+14152222222");
assertTrue(retrieved.isPresent());
assertSame(retrieved.get(), account);
verify(commands, times(1)).get(eq("AccountMap::+14152222222"));
verify(commands, times(1)).set(eq("AccountMap::+14152222222"), eq(uuid.toString()));
verify(commands, times(1)).set(eq("Account3::" + uuid.toString()), anyString());
verify(commands, times(1)).set(eq("Account3::" + uuid), anyString());
verifyNoMoreInteractions(commands);
verify(accounts, times(1)).get(eq("+14152222222"));
@@ -218,36 +207,22 @@ class AccountsManagerTest {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testGetAccountByUuidNotInCache(boolean dynamoEnabled) {
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
Accounts accounts = mock(Accounts.class);
AccountsDynamoDb accountsDynamoDb = mock(AccountsDynamoDb.class);
DeletedAccountsManager deletedAccounts = mock(DeletedAccountsManager.class);
DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
KeysDynamoDb keysDynamoDb = mock(KeysDynamoDb.class);
MessagesManager messagesManager = mock(MessagesManager.class);
UsernamesManager usernamesManager = mock(UsernamesManager.class);
ProfilesManager profilesManager = mock(ProfilesManager.class);
SecureBackupClient secureBackupClient = mock(SecureBackupClient.class);
SecureStorageClient secureStorageClient = mock(SecureStorageClient.class);
UUID uuid = UUID.randomUUID();
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
UUID uuid = UUID.randomUUID();
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
enableDynamo(dynamoEnabled);
when(commands.get(eq("Account3::" + uuid))).thenReturn(null);
when(accounts.get(eq(uuid))).thenReturn(Optional.of(account));
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccounts,
directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
Optional<Account> retrieved = accountsManager.get(uuid);
Optional<Account> retrieved = accountsManager.get(uuid);
assertTrue(retrieved.isPresent());
assertSame(retrieved.get(), account);
verify(commands, times(1)).get(eq("Account3::" + uuid));
verify(commands, times(1)).set(eq("AccountMap::+14152222222"), eq(uuid.toString()));
verify(commands, times(1)).set(eq("Account3::" + uuid.toString()), anyString());
verify(commands, times(1)).set(eq("Account3::" + uuid), anyString());
verifyNoMoreInteractions(commands);
verify(accounts, times(1)).get(eq(uuid));
@@ -260,18 +235,6 @@ class AccountsManagerTest {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testGetAccountByNumberBrokenCache(boolean dynamoEnabled) {
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
Accounts accounts = mock(Accounts.class);
AccountsDynamoDb accountsDynamoDb = mock(AccountsDynamoDb.class);
DeletedAccountsManager deletedAccounts = mock(DeletedAccountsManager.class);
DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
KeysDynamoDb keysDynamoDb = mock(KeysDynamoDb.class);
MessagesManager messagesManager = mock(MessagesManager.class);
UsernamesManager usernamesManager = mock(UsernamesManager.class);
ProfilesManager profilesManager = mock(ProfilesManager.class);
SecureBackupClient secureBackupClient = mock(SecureBackupClient.class);
SecureStorageClient secureStorageClient = mock(SecureStorageClient.class);
UUID uuid = UUID.randomUUID();
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
@@ -280,8 +243,6 @@ class AccountsManagerTest {
when(commands.get(eq("AccountMap::+14152222222"))).thenThrow(new RedisException("Connection lost!"));
when(accounts.get(eq("+14152222222"))).thenReturn(Optional.of(account));
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccounts,
directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
Optional<Account> retrieved = accountsManager.get("+14152222222");
assertTrue(retrieved.isPresent());
@@ -289,7 +250,7 @@ class AccountsManagerTest {
verify(commands, times(1)).get(eq("AccountMap::+14152222222"));
verify(commands, times(1)).set(eq("AccountMap::+14152222222"), eq(uuid.toString()));
verify(commands, times(1)).set(eq("Account3::" + uuid.toString()), anyString());
verify(commands, times(1)).set(eq("Account3::" + uuid), anyString());
verifyNoMoreInteractions(commands);
verify(accounts, times(1)).get(eq("+14152222222"));
@@ -302,18 +263,6 @@ class AccountsManagerTest {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testGetAccountByUuidBrokenCache(boolean dynamoEnabled) {
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
Accounts accounts = mock(Accounts.class);
AccountsDynamoDb accountsDynamoDb = mock(AccountsDynamoDb.class);
DeletedAccountsManager deletedAccounts = mock(DeletedAccountsManager.class);
DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
KeysDynamoDb keysDynamoDb = mock(KeysDynamoDb.class);
MessagesManager messagesManager = mock(MessagesManager.class);
UsernamesManager usernamesManager = mock(UsernamesManager.class);
ProfilesManager profilesManager = mock(ProfilesManager.class);
SecureBackupClient secureBackupClient = mock(SecureBackupClient.class);
SecureStorageClient secureStorageClient = mock(SecureStorageClient.class);
UUID uuid = UUID.randomUUID();
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
@@ -322,8 +271,6 @@ class AccountsManagerTest {
when(commands.get(eq("Account3::" + uuid))).thenThrow(new RedisException("Connection lost!"));
when(accounts.get(eq(uuid))).thenReturn(Optional.of(account));
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccounts,
directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
Optional<Account> retrieved = accountsManager.get(uuid);
assertTrue(retrieved.isPresent());
@@ -331,7 +278,7 @@ class AccountsManagerTest {
verify(commands, times(1)).get(eq("Account3::" + uuid));
verify(commands, times(1)).set(eq("AccountMap::+14152222222"), eq(uuid.toString()));
verify(commands, times(1)).set(eq("Account3::" + uuid.toString()), anyString());
verify(commands, times(1)).set(eq("Account3::" + uuid), anyString());
verifyNoMoreInteractions(commands);
verify(accounts, times(1)).get(eq(uuid));
@@ -344,18 +291,6 @@ class AccountsManagerTest {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testUpdate_dynamoDbMigration(boolean dynamoEnabled) throws IOException {
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
Accounts accounts = mock(Accounts.class);
AccountsDynamoDb accountsDynamoDb = mock(AccountsDynamoDb.class);
DeletedAccountsManager deletedAccountsManager = mock(DeletedAccountsManager.class);
DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
KeysDynamoDb keysDynamoDb = mock(KeysDynamoDb.class);
MessagesManager messagesManager = mock(MessagesManager.class);
UsernamesManager usernamesManager = mock(UsernamesManager.class);
ProfilesManager profilesManager = mock(ProfilesManager.class);
SecureBackupClient secureBackupClient = mock(SecureBackupClient.class);
SecureStorageClient secureStorageClient = mock(SecureStorageClient.class);
UUID uuid = UUID.randomUUID();
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
@@ -367,9 +302,6 @@ class AccountsManagerTest {
when(accountsDynamoDb.get(uuid)).thenReturn(Optional.of(new Account("+14152222222", uuid, new HashSet<>(), new byte[16])));
doAnswer(ACCOUNT_UPDATE_ANSWER).when(accounts).update(any(Account.class));
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccountsManager,
directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
Account updatedAccount = accountsManager.update(account, a -> a.setProfileName("name"));
assertThrows(AssertionError.class, account::getProfileName, "Account passed to update() should be stale");
@@ -405,18 +337,6 @@ class AccountsManagerTest {
@Test
void testUpdate_dynamoMissing() {
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
Accounts accounts = mock(Accounts.class);
AccountsDynamoDb accountsDynamoDb = mock(AccountsDynamoDb.class);
DeletedAccountsManager deletedAccounts = mock(DeletedAccountsManager.class);
DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
KeysDynamoDb keysDynamoDb = mock(KeysDynamoDb.class);
MessagesManager messagesManager = mock(MessagesManager.class);
UsernamesManager usernamesManager = mock(UsernamesManager.class);
ProfilesManager profilesManager = mock(ProfilesManager.class);
SecureBackupClient secureBackupClient = mock(SecureBackupClient.class);
SecureStorageClient secureStorageClient = mock(SecureStorageClient.class);
UUID uuid = UUID.randomUUID();
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
@@ -427,9 +347,6 @@ class AccountsManagerTest {
doAnswer(ACCOUNT_UPDATE_ANSWER).when(accounts).update(any());
doAnswer(ACCOUNT_UPDATE_ANSWER).when(accountsDynamoDb).update(any());
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccounts,
directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
Account updatedAccount = accountsManager.update(account, a -> {});
verify(accounts, times(1)).update(account);
@@ -444,18 +361,6 @@ class AccountsManagerTest {
@Test
void testUpdate_optimisticLockingFailure() {
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
Accounts accounts = mock(Accounts.class);
AccountsDynamoDb accountsDynamoDb = mock(AccountsDynamoDb.class);
DeletedAccountsManager deletedAccounts = mock(DeletedAccountsManager.class);
DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
KeysDynamoDb keysDynamoDb = mock(KeysDynamoDb.class);
MessagesManager messagesManager = mock(MessagesManager.class);
UsernamesManager usernamesManager = mock(UsernamesManager.class);
ProfilesManager profilesManager = mock(ProfilesManager.class);
SecureBackupClient secureBackupClient = mock(SecureBackupClient.class);
SecureStorageClient secureStorageClient = mock(SecureStorageClient.class);
UUID uuid = UUID.randomUUID();
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
@@ -473,8 +378,6 @@ class AccountsManagerTest {
.doAnswer(ACCOUNT_UPDATE_ANSWER)
.when(accountsDynamoDb).update(any());
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccounts, directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
account = accountsManager.update(account, a -> a.setProfileName("name"));
assertEquals(1, account.getVersion());
@@ -492,18 +395,6 @@ class AccountsManagerTest {
@Test
void testUpdate_dynamoOptimisticLockingFailureDuringCreate() {
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
Accounts accounts = mock(Accounts.class);
AccountsDynamoDb accountsDynamoDb = mock(AccountsDynamoDb.class);
DeletedAccountsManager deletedAccountsManager = mock(DeletedAccountsManager.class);
DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
KeysDynamoDb keysDynamoDb = mock(KeysDynamoDb.class);
MessagesManager messagesManager = mock(MessagesManager.class);
UsernamesManager usernamesManager = mock(UsernamesManager.class);
ProfilesManager profilesManager = mock(ProfilesManager.class);
SecureBackupClient secureBackupClient = mock(SecureBackupClient.class);
SecureStorageClient secureStorageClient = mock(SecureStorageClient.class);
UUID uuid = UUID.randomUUID();
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
@@ -514,8 +405,6 @@ class AccountsManagerTest {
.thenReturn(Optional.of(account));
when(accountsDynamoDb.create(any())).thenThrow(ContestedOptimisticLockException.class);
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccountsManager, directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
accountsManager.update(account, a -> {});
verify(accounts, times(1)).update(account);
@@ -526,22 +415,7 @@ class AccountsManagerTest {
}
@Test
void testUpdateDevice() throws Exception {
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
Accounts accounts = mock(Accounts.class);
AccountsDynamoDb accountsDynamoDb = mock(AccountsDynamoDb.class);
DeletedAccountsManager deletedAccountsManager = mock(DeletedAccountsManager.class);
DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
KeysDynamoDb keysDynamoDb = mock(KeysDynamoDb.class);
MessagesManager messagesManager = mock(MessagesManager.class);
UsernamesManager usernamesManager = mock(UsernamesManager.class);
ProfilesManager profilesManager = mock(ProfilesManager.class);
SecureBackupClient secureBackupClient = mock(SecureBackupClient.class);
SecureStorageClient secureStorageClient = mock(SecureStorageClient.class);
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccountsManager, directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
void testUpdateDevice() {
assertEquals(Optional.empty(), accountsManager.compareAccounts(Optional.empty(), Optional.empty()));
final UUID uuid = UUID.randomUUID();
@@ -565,7 +439,7 @@ class AccountsManagerTest {
account = accountsManager.updateDevice(account, deviceId, deviceUpdater);
account = accountsManager.updateDevice(account, deviceId, d -> d.setName("deviceName"));
assertEquals("deviceName", account.getDevice(deviceId).get().getName());
assertEquals("deviceName", account.getDevice(deviceId).orElseThrow().getName());
verify(deviceUpdater, times(1)).accept(any(Device.class));
@@ -574,25 +448,8 @@ class AccountsManagerTest {
verify(unknownDeviceUpdater, never()).accept(any(Device.class));
}
@Test
void testCompareAccounts() throws Exception {
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
Accounts accounts = mock(Accounts.class);
AccountsDynamoDb accountsDynamoDb = mock(AccountsDynamoDb.class);
DeletedAccountsManager deletedAccounts = mock(DeletedAccountsManager.class);
DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
KeysDynamoDb keysDynamoDb = mock(KeysDynamoDb.class);
MessagesManager messagesManager = mock(MessagesManager.class);
UsernamesManager usernamesManager = mock(UsernamesManager.class);
ProfilesManager profilesManager = mock(ProfilesManager.class);
SecureBackupClient secureBackupClient = mock(SecureBackupClient.class);
SecureStorageClient secureStorageClient = mock(SecureStorageClient.class);
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, deletedAccounts,
directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
assertEquals(Optional.empty(), accountsManager.compareAccounts(Optional.empty(), Optional.empty()));
final UUID uuidA = UUID.randomUUID();
@@ -663,4 +520,50 @@ class AccountsManagerTest {
assertEquals(Optional.of("profileName"), accountsManager.compareAccounts(Optional.of(a1), Optional.of(a2)));
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testCreateWithDiscoverability(final boolean discoverable) {
final AccountAttributes attributes = new AccountAttributes(false, 0, null, null, null, discoverable, null);
final Account account = accountsManager.create("+18005550123", "password", null, attributes);
assertEquals(discoverable, account.isDiscoverableByPhoneNumber());
if (!discoverable) {
verify(directoryQueue).deleteAccount(account);
}
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testCreateWithStorageCapability(final boolean hasStorage) {
final AccountAttributes attributes = new AccountAttributes(false, 0, null, null, null, true,
new DeviceCapabilities(false, false, false, hasStorage, false, false, false, false));
final Account account = accountsManager.create("+18005550123", "password", null, attributes);
assertEquals(hasStorage, account.isStorageSupported());
}
@ParameterizedTest
@MethodSource
void testUpdateDirectoryQueue(final boolean discoverableBeforeUpdate, final boolean discoverableAfterUpdate, final boolean expectRefresh) {
final Account account = new Account("+14152222222", UUID.randomUUID(), new HashSet<>(), new byte[16]);
when(directoryQueue.isDiscoverable(any()))
.thenReturn(discoverableBeforeUpdate)
.thenReturn(discoverableAfterUpdate);
final Account updatedAccount = accountsManager.update(account, a -> a.setProfileName("Hello I am a unit test"));
verify(directoryQueue, times(expectRefresh ? 1 : 0)).refreshAccount(updatedAccount);
}
private static Stream<Arguments> testUpdateDirectoryQueue() {
return Stream.of(
Arguments.of(false, false, false),
Arguments.of(true, true, false),
Arguments.of(false, true, true),
Arguments.of(true, false, true));
}
}

View File

@@ -5,7 +5,6 @@
package org.whispersystems.textsecuregcm.tests.storage;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.eq;
@@ -13,7 +12,7 @@ import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.tests.util.AccountsHelper.eqUuid;
@@ -23,11 +22,8 @@ import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerRestartException;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
@@ -39,7 +35,6 @@ import org.whispersystems.textsecuregcm.util.Util;
class PushFeedbackProcessorTest {
private AccountsManager accountsManager = mock(AccountsManager.class);
private DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
private Account uninstalledAccount = mock(Account.class);
private Account mixedAccount = mock(Account.class);
@@ -105,16 +100,15 @@ class PushFeedbackProcessorTest {
@Test
void testEmpty() throws AccountDatabaseCrawlerRestartException {
PushFeedbackProcessor processor = new PushFeedbackProcessor(accountsManager, directoryQueue);
PushFeedbackProcessor processor = new PushFeedbackProcessor(accountsManager);
processor.timeAndProcessCrawlChunk(Optional.of(UUID.randomUUID()), Collections.emptyList());
verifyZeroInteractions(accountsManager);
verifyZeroInteractions(directoryQueue);
verifyNoInteractions(accountsManager);
}
@Test
void testUpdate() throws AccountDatabaseCrawlerRestartException {
PushFeedbackProcessor processor = new PushFeedbackProcessor(accountsManager, directoryQueue);
PushFeedbackProcessor processor = new PushFeedbackProcessor(accountsManager);
processor.timeAndProcessCrawlChunk(Optional.of(UUID.randomUUID()), List.of(uninstalledAccount, mixedAccount, stillActiveAccount, freshAccount, cleanAccount, undiscoverableAccount));
verify(uninstalledDevice).setApnId(isNull());
@@ -151,15 +145,6 @@ class PushFeedbackProcessorTest {
verify(stillActiveDevice, never()).setFetchesMessages(anyBoolean());
verify(accountsManager).update(eqUuid(stillActiveAccount), any());
final ArgumentCaptor<List<Account>> refreshedAccountArgumentCaptor = ArgumentCaptor.forClass(List.class);
verify(directoryQueue).refreshRegisteredUsers(refreshedAccountArgumentCaptor.capture());
final List<UUID> refreshedUuids = refreshedAccountArgumentCaptor.getValue().stream()
.map(Account::getUuid)
.collect(Collectors.toList());
assertTrue(refreshedUuids.containsAll(List.of(undiscoverableAccount.getUuid(), uninstalledAccount.getUuid())));
}
}