Create deleted-accounts records keyed by both e164 and PNI

This commit is contained in:
Jonathan Klabunde Tomer
2024-11-25 12:42:16 -08:00
committed by GitHub
parent 49d6a5e32d
commit ffed19d198
5 changed files with 362 additions and 5 deletions

View File

@@ -65,6 +65,7 @@ import org.whispersystems.textsecuregcm.util.SystemMapper;
import org.whispersystems.textsecuregcm.util.TestClock;
import org.whispersystems.textsecuregcm.util.TestRandomUtil;
import reactor.core.scheduler.Schedulers;
import reactor.util.function.Tuples;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
@@ -74,6 +75,7 @@ import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
import software.amazon.awssdk.services.dynamodb.model.Put;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
import software.amazon.awssdk.services.dynamodb.model.ReturnValuesOnConditionCheckFailure;
import software.amazon.awssdk.services.dynamodb.model.TransactWriteItem;
import software.amazon.awssdk.services.dynamodb.model.TransactWriteItemsRequest;
@@ -211,6 +213,7 @@ class AccountsTest {
accounts.delete(originalUuid, Collections.emptyList()).join();
assertThat(accounts.findRecentlyDeletedAccountIdentifier(account.getNumber())).hasValue(originalUuid);
assertThat(accounts.findRecentlyDeletedAccountIdentifier(account.getPhoneNumberIdentifier())).hasValue(originalUuid);
freshUser = createAccount(account);
assertThat(freshUser).isTrue();
@@ -220,6 +223,7 @@ class AccountsTest {
assertPhoneNumberIdentifierConstraintExists(account.getPhoneNumberIdentifier(), account.getUuid());
assertThat(accounts.findRecentlyDeletedAccountIdentifier(account.getNumber())).isEmpty();
assertThat(accounts.findRecentlyDeletedAccountIdentifier(account.getPhoneNumberIdentifier())).isEmpty();
}
@Test
@@ -742,6 +746,7 @@ class AccountsTest {
createAccount(retainedAccount);
assertThat(accounts.findRecentlyDeletedAccountIdentifier(deletedAccount.getNumber())).isEmpty();
assertThat(accounts.findRecentlyDeletedAccountIdentifier(deletedAccount.getPhoneNumberIdentifier())).isEmpty();
assertPhoneNumberConstraintExists("+14151112222", deletedAccount.getUuid());
assertPhoneNumberIdentifierConstraintExists(deletedAccount.getPhoneNumberIdentifier(), deletedAccount.getUuid());
@@ -755,6 +760,7 @@ class AccountsTest {
assertThat(accounts.getByAccountIdentifier(deletedAccount.getUuid())).isNotPresent();
assertThat(accounts.findRecentlyDeletedAccountIdentifier(deletedAccount.getNumber())).hasValue(deletedAccount.getUuid());
assertThat(accounts.findRecentlyDeletedAccountIdentifier(deletedAccount.getPhoneNumberIdentifier())).hasValue(deletedAccount.getUuid());
assertPhoneNumberConstraintDoesNotExist(deletedAccount.getNumber());
assertPhoneNumberIdentifierConstraintDoesNotExist(deletedAccount.getPhoneNumberIdentifier());
@@ -764,7 +770,7 @@ class AccountsTest {
{
final Account recreatedAccount = generateAccount(deletedAccount.getNumber(), UUID.randomUUID(),
UUID.randomUUID(), List.of(generateDevice(DEVICE_ID_1)));
deletedAccount.getPhoneNumberIdentifier(), List.of(generateDevice(DEVICE_ID_1)));
final boolean freshUser = createAccount(recreatedAccount);
@@ -893,6 +899,7 @@ class AccountsTest {
}
assertThat(accounts.findRecentlyDeletedAccountIdentifier(originalNumber)).isEqualTo(maybeDisplacedAccountIdentifier);
assertThat(accounts.findRecentlyDeletedAccountIdentifier(originalPni)).isEqualTo(maybeDisplacedAccountIdentifier);
}
private static Stream<Arguments> testChangeNumber() {
@@ -1697,7 +1704,142 @@ class AccountsTest {
assertInstanceOf(DeviceIdDeserializer.DeviceIdDeserializationException.class, cause);
}
@Test
public void getE164KeyedDeletedAccounts() {
final Account deletedAccount = generateAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID());
createAccount(deletedAccount);
accounts.delete(deletedAccount.getUuid(), List.of()).join();
assertEquals(
List.of(Tuples.of("+18005551234", deletedAccount.getUuid(), clock.instant().plus(Accounts.DELETED_ACCOUNTS_TIME_TO_LIVE).toEpochMilli() / 1000)),
accounts.getE164KeyedDeletedAccounts(1, Schedulers.immediate()).collectList().block());
}
@Test
public void insertPniDeletedAccount() throws Exception {
final String e164 = "+18005551234";
final UUID aci = UUID.randomUUID();
final UUID pni = UUID.randomUUID();
final Long expires = 1234567890L;
final ScanRequest scanRequest = ScanRequest.builder()
.tableName(Tables.DELETED_ACCOUNTS.tableName())
.build();
assertThat(
DYNAMO_DB_EXTENSION.getDynamoDbClient()
.scan(scanRequest)
.items())
.isEmpty();
DYNAMO_DB_EXTENSION.getDynamoDbClient().putItem(
PutItemRequest.builder()
.tableName(Tables.DELETED_ACCOUNTS.tableName())
.item(Map.of(
Accounts.DELETED_ACCOUNTS_KEY_ACCOUNT_E164, AttributeValues.fromString(e164),
Accounts.DELETED_ACCOUNTS_ATTR_ACCOUNT_UUID, AttributeValues.fromUUID(aci),
Accounts.DELETED_ACCOUNTS_ATTR_EXPIRES, AttributeValues.fromLong(expires)))
.build());
assertThat(accounts.insertPniDeletedAccount(e164, pni, aci, expires).get()).isTrue();
List<Map<String, AttributeValue>> items =
DYNAMO_DB_EXTENSION.getDynamoDbClient()
.scan(scanRequest)
.items();
assertThat(items).hasSize(2);
final Map<String, AttributeValue> item = items.stream().filter(i -> !i.get(Accounts.DELETED_ACCOUNTS_KEY_ACCOUNT_E164).s().equals(e164)).findFirst().get();
assertThat(item.get(Accounts.DELETED_ACCOUNTS_KEY_ACCOUNT_E164).s())
.isEqualTo(pni.toString());
assertThat(AttributeValues.getUUID(item, Accounts.DELETED_ACCOUNTS_ATTR_ACCOUNT_UUID, null))
.isEqualTo(aci);
assertThat(AttributeValues.getLong(item, Accounts.DELETED_ACCOUNTS_ATTR_EXPIRES, 0))
.isEqualTo(expires);
}
@Test
public void insertPniDeletedAccount_concurrentChange() throws Exception {
final String e164 = "+18005551234";
final UUID aci = UUID.randomUUID();
final UUID pni = UUID.randomUUID();
final Long expires = 1234567890L;
final ScanRequest scanRequest = ScanRequest.builder()
.tableName(Tables.DELETED_ACCOUNTS.tableName())
.build();
assertThat(
DYNAMO_DB_EXTENSION.getDynamoDbClient()
.scan(scanRequest)
.items())
.isEmpty();
DYNAMO_DB_EXTENSION.getDynamoDbClient().putItem(
PutItemRequest.builder()
.tableName(Tables.DELETED_ACCOUNTS.tableName())
.item(Map.of(
Accounts.DELETED_ACCOUNTS_KEY_ACCOUNT_E164, AttributeValues.fromString(e164),
Accounts.DELETED_ACCOUNTS_ATTR_ACCOUNT_UUID, AttributeValues.fromUUID(aci),
Accounts.DELETED_ACCOUNTS_ATTR_EXPIRES, AttributeValues.fromLong(expires + 1)))
.build());
assertThat(accounts.insertPniDeletedAccount(e164, pni, aci, expires).get()).isFalse();
List<Map<String, AttributeValue>> items =
DYNAMO_DB_EXTENSION.getDynamoDbClient()
.scan(scanRequest)
.items();
assertThat(items).hasSize(1);
}
@Test
public void insertPniDeletedAccount_concurrentDeletion() throws Exception {
final String e164 = "+18005551234";
final UUID aci = UUID.randomUUID();
final UUID pni = UUID.randomUUID();
final Long expires = 1234567890L;
final ScanRequest scanRequest = ScanRequest.builder()
.tableName(Tables.DELETED_ACCOUNTS.tableName())
.build();
assertThat(
DYNAMO_DB_EXTENSION.getDynamoDbClient()
.scan(scanRequest)
.items())
.isEmpty();
assertThat(accounts.insertPniDeletedAccount(e164, pni, aci, expires).get()).isFalse();
List<Map<String, AttributeValue>> items =
DYNAMO_DB_EXTENSION.getDynamoDbClient()
.scan(scanRequest)
.items();
assertThat(items).isEmpty();
}
@Test
public void insertPniDeletedAccount_alreadyMigrated() throws Exception {
final Account deletedAccount = generateAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID());
createAccount(deletedAccount);
accounts.delete(deletedAccount.getUuid(), List.of()).join();
final ScanRequest scanRequest = ScanRequest.builder()
.tableName(Tables.DELETED_ACCOUNTS.tableName())
.build();
assertThat(
DYNAMO_DB_EXTENSION.getDynamoDbClient()
.scan(scanRequest)
.items())
.hasSize(2);
assertThat(accounts.insertPniDeletedAccount(deletedAccount.getNumber(), deletedAccount.getPhoneNumberIdentifier(), deletedAccount.getUuid(), clock.instant().plus(Accounts.DELETED_ACCOUNTS_TIME_TO_LIVE).toEpochMilli() / 1000).get()).isFalse();
List<Map<String, AttributeValue>> items =
DYNAMO_DB_EXTENSION.getDynamoDbClient()
.scan(scanRequest)
.items();
assertThat(items).hasSize(2);
}
private static Device generateDevice(byte id) {
return DevicesHelper.createDevice(id);