Read registration recovery passwords exclusively by PNI

This commit is contained in:
Jon Chambers
2024-11-25 17:05:20 -05:00
committed by Jon Chambers
parent 6967e4e54b
commit 5b9f8177f2
19 changed files with 129 additions and 118 deletions

View File

@@ -31,11 +31,11 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.entities.PhoneVerificationRequest;
import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.WebSocketConnectionEventManager;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
@@ -53,7 +53,7 @@ class RegistrationLockVerificationManagerTest {
ExternalServiceCredentialsGenerator.class);
private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager = mock(
RegistrationRecoveryPasswordsManager.class);
private static PushNotificationManager pushNotificationManager = mock(PushNotificationManager.class);
private final PushNotificationManager pushNotificationManager = mock(PushNotificationManager.class);
private final RateLimiters rateLimiters = mock(RateLimiters.class);
private final RegistrationLockVerificationManager registrationLockVerificationManager = new RegistrationLockVerificationManager(
accountsManager, disconnectionRequestManager, svr2CredentialsGenerator,
@@ -105,12 +105,13 @@ class RegistrationLockVerificationManagerTest {
if (!verificationType.equals(PhoneVerificationRequest.VerificationType.RECOVERY_PASSWORD) || clientRegistrationLock != null) {
verify(registrationRecoveryPasswordsManager).removeForNumber(account.getNumber());
} else {
verify(registrationRecoveryPasswordsManager, never()).removeForNumber(account.getNumber());
verify(registrationRecoveryPasswordsManager, never()).removeForNumber(any());
}
verify(disconnectionRequestManager).requestDisconnection(account.getUuid(), List.of(Device.PRIMARY_ID));
try {
verify(pushNotificationManager).sendAttemptLoginNotification(any(), eq("failedRegistrationLock"));
} catch (NotPushRegisteredException npre) {}
} catch (final NotPushRegisteredException ignored) {
}
if (alreadyLocked) {
verify(account, never()).lockAuthTokenHash();
} else {
@@ -126,10 +127,13 @@ class RegistrationLockVerificationManagerTest {
doThrow(RateLimitExceededException.class).when(pinLimiter).validate(anyString());
yield new Pair<>(RateLimitExceededException.class, ignored -> {
verify(account, never()).lockAuthTokenHash();
try {
verify(pushNotificationManager, never()).sendAttemptLoginNotification(any(), eq("failedRegistrationLock"));
} catch (NotPushRegisteredException npre) {}
verify(registrationRecoveryPasswordsManager, never()).removeForNumber(account.getNumber());
} catch (final NotPushRegisteredException ignored2) {
}
verify(registrationRecoveryPasswordsManager, never()).removeForNumber(any());
verify(disconnectionRequestManager, never()).requestDisconnection(any(), any());
});
}
@@ -167,7 +171,7 @@ class RegistrationLockVerificationManagerTest {
PhoneVerificationRequest.VerificationType.SESSION));
verify(account, never()).lockAuthTokenHash();
verify(registrationRecoveryPasswordsManager, never()).removeForNumber(account.getNumber());
verify(registrationRecoveryPasswordsManager, never()).removeForNumber(any());
verify(disconnectionRequestManager, never()).requestDisconnection(any(), any());
}

View File

@@ -86,6 +86,7 @@ import org.whispersystems.textsecuregcm.storage.AccountBadge;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.ChangeNumberManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
import org.whispersystems.textsecuregcm.tests.util.AccountsHelper;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
@@ -105,6 +106,7 @@ class AccountControllerV2Test {
private final AccountsManager accountsManager = mock(AccountsManager.class);
private final ChangeNumberManager changeNumberManager = mock(ChangeNumberManager.class);
private final PhoneNumberIdentifiers phoneNumberIdentifiers = mock(PhoneNumberIdentifiers.class);
private final RegistrationServiceClient registrationServiceClient = mock(RegistrationServiceClient.class);
private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager = mock(
RegistrationRecoveryPasswordsManager.class);
@@ -125,8 +127,8 @@ class AccountControllerV2Test {
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(
new AccountControllerV2(accountsManager, changeNumberManager,
new PhoneVerificationTokenManager(registrationServiceClient, registrationRecoveryPasswordsManager,
registrationRecoveryChecker),
new PhoneVerificationTokenManager(phoneNumberIdentifiers, registrationServiceClient,
registrationRecoveryPasswordsManager, registrationRecoveryChecker),
registrationLockVerificationManager, rateLimiters))
.build();
@@ -401,6 +403,8 @@ class AccountControllerV2Test {
@Test
void recoveryPasswordManagerVerificationTrue() throws Exception {
when(phoneNumberIdentifiers.getPhoneNumberIdentifier(any()))
.thenReturn(CompletableFuture.completedFuture(UUID.randomUUID()));
when(registrationRecoveryPasswordsManager.verify(any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));
when(registrationRecoveryChecker.checkRegistrationRecoveryAttempt(any(), any()))
@@ -443,6 +447,8 @@ class AccountControllerV2Test {
@Test
void registrationRecoveryCheckerAllowsAttempt() {
when(phoneNumberIdentifiers.getPhoneNumberIdentifier(any()))
.thenReturn(CompletableFuture.completedFuture(UUID.randomUUID()));
when(registrationRecoveryChecker.checkRegistrationRecoveryAttempt(any(), any())).thenReturn(true);
when(registrationRecoveryPasswordsManager.verify(any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));

View File

@@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.controllers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
@@ -77,6 +78,7 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.DeviceCapability;
import org.whispersystems.textsecuregcm.storage.DeviceSpec;
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.tests.util.KeysHelper;
@@ -94,6 +96,7 @@ class RegistrationControllerTest {
private static final String PASSWORD = "password";
private final AccountsManager accountsManager = mock(AccountsManager.class);
private final PhoneNumberIdentifiers phoneNumberIdentifiers = mock(PhoneNumberIdentifiers.class);
private final RegistrationServiceClient registrationServiceClient = mock(RegistrationServiceClient.class);
private final RegistrationLockVerificationManager registrationLockVerificationManager = mock(
RegistrationLockVerificationManager.class);
@@ -113,8 +116,8 @@ class RegistrationControllerTest {
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(
new RegistrationController(accountsManager,
new PhoneVerificationTokenManager(registrationServiceClient, registrationRecoveryPasswordsManager,
registrationRecoveryChecker),
new PhoneVerificationTokenManager(phoneNumberIdentifiers, registrationServiceClient,
registrationRecoveryPasswordsManager, registrationRecoveryChecker),
registrationLockVerificationManager, rateLimiters))
.build();
@@ -243,6 +246,8 @@ class RegistrationControllerTest {
@Test
void recoveryPasswordManagerVerificationFailureOrTimeout() {
when(phoneNumberIdentifiers.getPhoneNumberIdentifier(any()))
.thenReturn(CompletableFuture.completedFuture(UUID.randomUUID()));
when(registrationRecoveryChecker.checkRegistrationRecoveryAttempt(any(), any())).thenReturn(true);
when(registrationRecoveryPasswordsManager.verify(any(), any()))
.thenReturn(CompletableFuture.failedFuture(new RuntimeException()));
@@ -289,6 +294,8 @@ class RegistrationControllerTest {
@Test
void recoveryPasswordManagerVerificationTrue() throws InterruptedException {
when(phoneNumberIdentifiers.getPhoneNumberIdentifier(any()))
.thenReturn(CompletableFuture.completedFuture(UUID.randomUUID()));
when(registrationRecoveryChecker.checkRegistrationRecoveryAttempt(any(), any())).thenReturn(true);
when(registrationRecoveryPasswordsManager.verify(any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));
@@ -325,6 +332,8 @@ class RegistrationControllerTest {
@Test
void registrationRecoveryCheckerAllowsAttempt() throws InterruptedException {
when(phoneNumberIdentifiers.getPhoneNumberIdentifier(any()))
.thenReturn(CompletableFuture.completedFuture(UUID.randomUUID()));
when(registrationRecoveryChecker.checkRegistrationRecoveryAttempt(any(), any())).thenReturn(true);
when(registrationRecoveryPasswordsManager.verify(any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));

View File

@@ -39,6 +39,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.stream.Stream;
@@ -77,6 +78,7 @@ import org.whispersystems.textsecuregcm.spam.RegistrationFraudChecker;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
import org.whispersystems.textsecuregcm.util.SystemMapper;
@@ -87,7 +89,11 @@ class VerificationControllerTest {
private static final long SESSION_EXPIRATION_SECONDS = Duration.ofMinutes(10).toSeconds();
private static final byte[] SESSION_ID = "session".getBytes(StandardCharsets.UTF_8);
private static final String NUMBER = "+18005551212";
private static final String NUMBER = PhoneNumberUtil.getInstance().format(
PhoneNumberUtil.getInstance().getExampleNumber("US"),
PhoneNumberUtil.PhoneNumberFormat.E164);
private static final UUID PNI = UUID.randomUUID();
private final RegistrationServiceClient registrationServiceClient = mock(RegistrationServiceClient.class);
private final VerificationSessionManager verificationSessionManager = mock(VerificationSessionManager.class);
@@ -95,6 +101,7 @@ class VerificationControllerTest {
private final RegistrationCaptchaManager registrationCaptchaManager = mock(RegistrationCaptchaManager.class);
private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager = mock(
RegistrationRecoveryPasswordsManager.class);
private final PhoneNumberIdentifiers phoneNumberIdentifiers = mock(PhoneNumberIdentifiers.class);
private final RateLimiters rateLimiters = mock(RateLimiters.class);
private final AccountsManager accountsManager = mock(AccountsManager.class);
private final Clock clock = Clock.systemUTC();
@@ -115,7 +122,7 @@ class VerificationControllerTest {
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(
new VerificationController(registrationServiceClient, verificationSessionManager, pushNotificationManager,
registrationCaptchaManager, registrationRecoveryPasswordsManager, rateLimiters, accountsManager,
registrationCaptchaManager, registrationRecoveryPasswordsManager, phoneNumberIdentifiers, rateLimiters, accountsManager,
RegistrationFraudChecker.noop(), dynamicConfigurationManager, clock))
.build();
@@ -131,6 +138,8 @@ class VerificationControllerTest {
.thenReturn(new DynamicRegistrationConfiguration(false));
when(dynamicConfigurationManager.getConfiguration())
.thenReturn(dynamicConfiguration);
when(phoneNumberIdentifiers.getPhoneNumberIdentifier(NUMBER))
.thenReturn(CompletableFuture.completedFuture(PNI));
}
@ParameterizedTest

View File

@@ -65,6 +65,7 @@ import org.whispersystems.textsecuregcm.controllers.AccountController;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.entities.EncryptedUsername;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
@@ -113,7 +114,7 @@ class AccountsGrpcServiceTest extends SimpleBaseGrpcTest<AccountsGrpcService, Ac
when(rateLimiter.validateReactive(any(UUID.class))).thenReturn(Mono.empty());
when(rateLimiter.validateReactive(anyString())).thenReturn(Mono.empty());
when(registrationRecoveryPasswordsManager.storeForCurrentNumber(anyString(), any()))
when(registrationRecoveryPasswordsManager.storeForCurrentNumber(any(), any()))
.thenReturn(CompletableFuture.completedFuture(null));
return new AccountsGrpcService(accountsManager,
@@ -261,7 +262,7 @@ class AccountsGrpcServiceTest extends SimpleBaseGrpcTest<AccountsGrpcService, Ac
final List<byte[]> usernameHashes = invocation.getArgument(1);
return CompletableFuture.completedFuture(
new AccountsManager.UsernameReservation(invocation.getArgument(0), usernameHashes.get(0)));
new AccountsManager.UsernameReservation(invocation.getArgument(0), usernameHashes.getFirst()));
});
final ReserveUsernameHashResponse expectedResponse = ReserveUsernameHashResponse.newBuilder()
@@ -684,12 +685,10 @@ class AccountsGrpcServiceTest extends SimpleBaseGrpcTest<AccountsGrpcService, Ac
@Test
void setRegistrationRecoveryPassword() {
final String phoneNumber =
PhoneNumberUtil.getInstance().format(PhoneNumberUtil.getInstance().getExampleNumber("US"),
PhoneNumberUtil.PhoneNumberFormat.E164);
final UUID phoneNumberIdentifier = UUID.randomUUID();
final Account account = mock(Account.class);
when(account.getNumber()).thenReturn(phoneNumber);
when(account.getIdentifier(IdentityType.PNI)).thenReturn(phoneNumberIdentifier);
when(accountsManager.getByAccountIdentifierAsync(AUTHENTICATED_ACI))
.thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
@@ -701,7 +700,7 @@ class AccountsGrpcServiceTest extends SimpleBaseGrpcTest<AccountsGrpcService, Ac
.setRegistrationRecoveryPassword(ByteString.copyFrom(registrationRecoveryPassword))
.build()));
verify(registrationRecoveryPasswordsManager).storeForCurrentNumber(phoneNumber, registrationRecoveryPassword);
verify(registrationRecoveryPasswordsManager).storeForCurrentNumber(account.getNumber(), registrationRecoveryPassword);
}
@Test

View File

@@ -224,7 +224,7 @@ class AccountsManagerTest {
final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager =
mock(RegistrationRecoveryPasswordsManager.class);
when(registrationRecoveryPasswordsManager.removeForNumber(anyString())).thenReturn(CompletableFuture.completedFuture(null));
when(registrationRecoveryPasswordsManager.removeForNumber(any())).thenReturn(CompletableFuture.completedFuture(null));
when(keysManager.deleteSingleUsePreKeys(any())).thenReturn(CompletableFuture.completedFuture(null));
when(messagesManager.clear(any())).thenReturn(CompletableFuture.completedFuture(null));

View File

@@ -288,10 +288,10 @@ public final class DynamoDbExtensionSchema {
List.of(), List.of()),
REGISTRATION_RECOVERY_PASSWORDS("registration_recovery_passwords_test",
RegistrationRecoveryPasswords.KEY_E164,
RegistrationRecoveryPasswords.KEY_PNI,
null,
List.of(AttributeDefinition.builder()
.attributeName(RegistrationRecoveryPasswords.KEY_E164)
.attributeName(RegistrationRecoveryPasswords.KEY_PNI)
.attributeType(ScalarAttributeType.S)
.build()),
List.of(), List.of()),

View File

@@ -32,20 +32,17 @@ import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
public class RegistrationRecoveryTest {
private static final MutableClock CLOCK = MockUtils.mutableClock(0);
private static final Duration EXPIRATION = Duration.ofSeconds(1000);
private static final String NUMBER = "+18005555555";
private static final UUID PNI = UUID.randomUUID();
private static final SaltedTokenHash ORIGINAL_HASH = SaltedTokenHash.generateFor("pass1");
private static final SaltedTokenHash ANOTHER_HASH = SaltedTokenHash.generateFor("pass2");
@RegisterExtension
private static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(
Tables.PNI,
Tables.REGISTRATION_RECOVERY_PASSWORDS);
private static final DynamoDbExtension DYNAMO_DB_EXTENSION =
new DynamoDbExtension(Tables.PNI, Tables.REGISTRATION_RECOVERY_PASSWORDS);
private UUID pni;
private RegistrationRecoveryPasswords registrationRecoveryPasswords;
@@ -60,89 +57,63 @@ public class RegistrationRecoveryTest {
DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
CLOCK
);
final PhoneNumberIdentifiers phoneNumberIdentifiers =
new PhoneNumberIdentifiers(DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), Tables.PNI.tableName());
manager = new RegistrationRecoveryPasswordsManager(registrationRecoveryPasswords, phoneNumberIdentifiers);
pni = phoneNumberIdentifiers.getPhoneNumberIdentifier(NUMBER).join();
}
@Test
public void testLookupAfterWrite() throws Exception {
registrationRecoveryPasswords.addOrReplace(NUMBER, PNI, ORIGINAL_HASH).get();
registrationRecoveryPasswords.addOrReplace(NUMBER, pni, ORIGINAL_HASH).get();
final long initialExp = fetchTimestamp(NUMBER);
final long expectedExpiration = CLOCK.instant().getEpochSecond() + EXPIRATION.getSeconds();
assertEquals(expectedExpiration, initialExp);
{
final Optional<SaltedTokenHash> saltedTokenHashByNumber = registrationRecoveryPasswords.lookup(NUMBER).get();
assertTrue(saltedTokenHashByNumber.isPresent());
assertEquals(ORIGINAL_HASH.salt(), saltedTokenHashByNumber.get().salt());
assertEquals(ORIGINAL_HASH.hash(), saltedTokenHashByNumber.get().hash());
}
{
final Optional<SaltedTokenHash> saltedTokenHashByPni = registrationRecoveryPasswords.lookup(PNI).get();
assertTrue(saltedTokenHashByPni.isPresent());
assertEquals(ORIGINAL_HASH.salt(), saltedTokenHashByPni.get().salt());
assertEquals(ORIGINAL_HASH.hash(), saltedTokenHashByPni.get().hash());
}
final Optional<SaltedTokenHash> saltedTokenHashByPni = registrationRecoveryPasswords.lookup(pni).get();
assertTrue(saltedTokenHashByPni.isPresent());
assertEquals(ORIGINAL_HASH.salt(), saltedTokenHashByPni.get().salt());
assertEquals(ORIGINAL_HASH.hash(), saltedTokenHashByPni.get().hash());
}
@Test
public void testLookupAfterRefresh() throws Exception {
registrationRecoveryPasswords.addOrReplace(NUMBER, PNI, ORIGINAL_HASH).get();
registrationRecoveryPasswords.addOrReplace(NUMBER, pni, ORIGINAL_HASH).get();
CLOCK.increment(50, TimeUnit.SECONDS);
registrationRecoveryPasswords.addOrReplace(NUMBER, PNI, ORIGINAL_HASH).get();
registrationRecoveryPasswords.addOrReplace(NUMBER, pni, ORIGINAL_HASH).get();
final long updatedExp = fetchTimestamp(NUMBER);
final long expectedExp = CLOCK.instant().getEpochSecond() + EXPIRATION.getSeconds();
assertEquals(expectedExp, updatedExp);
{
final Optional<SaltedTokenHash> saltedTokenHashByNumber = registrationRecoveryPasswords.lookup(NUMBER).get();
assertTrue(saltedTokenHashByNumber.isPresent());
assertEquals(ORIGINAL_HASH.salt(), saltedTokenHashByNumber.get().salt());
assertEquals(ORIGINAL_HASH.hash(), saltedTokenHashByNumber.get().hash());
}
{
final Optional<SaltedTokenHash> saltedTokenHashByPni = registrationRecoveryPasswords.lookup(PNI).get();
assertTrue(saltedTokenHashByPni.isPresent());
assertEquals(ORIGINAL_HASH.salt(), saltedTokenHashByPni.get().salt());
assertEquals(ORIGINAL_HASH.hash(), saltedTokenHashByPni.get().hash());
}
final Optional<SaltedTokenHash> saltedTokenHashByPni = registrationRecoveryPasswords.lookup(pni).get();
assertTrue(saltedTokenHashByPni.isPresent());
assertEquals(ORIGINAL_HASH.salt(), saltedTokenHashByPni.get().salt());
assertEquals(ORIGINAL_HASH.hash(), saltedTokenHashByPni.get().hash());
}
@Test
public void testReplace() throws Exception {
registrationRecoveryPasswords.addOrReplace(NUMBER, PNI, ORIGINAL_HASH).get();
registrationRecoveryPasswords.addOrReplace(NUMBER, PNI, ANOTHER_HASH).get();
registrationRecoveryPasswords.addOrReplace(NUMBER, pni, ORIGINAL_HASH).get();
registrationRecoveryPasswords.addOrReplace(NUMBER, pni, ANOTHER_HASH).get();
{
final Optional<SaltedTokenHash> saltedTokenHashByNumber = registrationRecoveryPasswords.lookup(NUMBER).get();
assertTrue(saltedTokenHashByNumber.isPresent());
assertEquals(ANOTHER_HASH.salt(), saltedTokenHashByNumber.get().salt());
assertEquals(ANOTHER_HASH.hash(), saltedTokenHashByNumber.get().hash());
}
{
final Optional<SaltedTokenHash> saltedTokenHashByPni = registrationRecoveryPasswords.lookup(PNI).get();
assertTrue(saltedTokenHashByPni.isPresent());
assertEquals(ANOTHER_HASH.salt(), saltedTokenHashByPni.get().salt());
assertEquals(ANOTHER_HASH.hash(), saltedTokenHashByPni.get().hash());
}
final Optional<SaltedTokenHash> saltedTokenHashByPni = registrationRecoveryPasswords.lookup(pni).get();
assertTrue(saltedTokenHashByPni.isPresent());
assertEquals(ANOTHER_HASH.salt(), saltedTokenHashByPni.get().salt());
assertEquals(ANOTHER_HASH.hash(), saltedTokenHashByPni.get().hash());
}
@Test
public void testRemove() throws Exception {
assertDoesNotThrow(() -> registrationRecoveryPasswords.removeEntry(NUMBER, PNI).join());
assertDoesNotThrow(() -> registrationRecoveryPasswords.removeEntry(NUMBER, pni).join());
registrationRecoveryPasswords.addOrReplace(NUMBER, PNI, ORIGINAL_HASH).get();
assertTrue(registrationRecoveryPasswords.lookup(NUMBER).get().isPresent());
registrationRecoveryPasswords.addOrReplace(NUMBER, pni, ORIGINAL_HASH).get();
assertTrue(registrationRecoveryPasswords.lookup(pni).get().isPresent());
registrationRecoveryPasswords.removeEntry(NUMBER, PNI).get();
assertTrue(registrationRecoveryPasswords.lookup(NUMBER).get().isEmpty());
registrationRecoveryPasswords.removeEntry(NUMBER, pni).get();
assertTrue(registrationRecoveryPasswords.lookup(pni).get().isEmpty());
}
@Test
@@ -153,30 +124,30 @@ public class RegistrationRecoveryTest {
// initial store
manager.storeForCurrentNumber(NUMBER, password).get();
assertTrue(manager.verify(NUMBER, password).get());
assertFalse(manager.verify(NUMBER, wrongPassword).get());
assertTrue(manager.verify(pni, password).get());
assertFalse(manager.verify(pni, wrongPassword).get());
// update
manager.storeForCurrentNumber(NUMBER, password).get();
assertTrue(manager.verify(NUMBER, password).get());
assertFalse(manager.verify(NUMBER, wrongPassword).get());
assertTrue(manager.verify(pni, password).get());
assertFalse(manager.verify(pni, wrongPassword).get());
// replace
manager.storeForCurrentNumber(NUMBER, updatedPassword).get();
assertTrue(manager.verify(NUMBER, updatedPassword).get());
assertFalse(manager.verify(NUMBER, password).get());
assertFalse(manager.verify(NUMBER, wrongPassword).get());
assertTrue(manager.verify(pni, updatedPassword).get());
assertFalse(manager.verify(pni, password).get());
assertFalse(manager.verify(pni, wrongPassword).get());
manager.removeForNumber(NUMBER).get();
assertFalse(manager.verify(NUMBER, updatedPassword).get());
assertFalse(manager.verify(NUMBER, password).get());
assertFalse(manager.verify(NUMBER, wrongPassword).get());
assertFalse(manager.verify(pni, updatedPassword).get());
assertFalse(manager.verify(pni, password).get());
assertFalse(manager.verify(pni, wrongPassword).get());
}
private static long fetchTimestamp(final String number) throws ExecutionException, InterruptedException {
return DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient().getItem(GetItemRequest.builder()
.tableName(Tables.REGISTRATION_RECOVERY_PASSWORDS.tableName())
.key(Map.of(RegistrationRecoveryPasswords.KEY_E164, AttributeValues.fromString(number)))
.key(Map.of(RegistrationRecoveryPasswords.KEY_PNI, AttributeValues.fromString(number)))
.build())
.thenApply(getItemResponse -> {
final Map<String, AttributeValue> item = getItemResponse.item();

View File

@@ -85,6 +85,7 @@ public class AuthHelper {
public static final String UNDISCOVERABLE_NUMBER = "+18005551234";
public static final UUID UNDISCOVERABLE_UUID = UUID.randomUUID();
public static final UUID UNDISCOVERABLE_PNI = UUID.randomUUID();
public static final String UNDISCOVERABLE_PASSWORD = "IT'S A SECRET TO EVERYBODY.";
public static final ECKeyPair VALID_IDENTITY_KEY_PAIR = Curve.generateKeyPair();
@@ -169,7 +170,9 @@ public class AuthHelper {
when(VALID_ACCOUNT_TWO.getPhoneNumberIdentifier()).thenReturn(VALID_PNI_TWO);
when(UNDISCOVERABLE_ACCOUNT.getNumber()).thenReturn(UNDISCOVERABLE_NUMBER);
when(UNDISCOVERABLE_ACCOUNT.getUuid()).thenReturn(UNDISCOVERABLE_UUID);
when(UNDISCOVERABLE_ACCOUNT.getPhoneNumberIdentifier()).thenReturn(UNDISCOVERABLE_PNI);
when(UNDISCOVERABLE_ACCOUNT.getIdentifier(IdentityType.ACI)).thenReturn(UNDISCOVERABLE_UUID);
when(UNDISCOVERABLE_ACCOUNT.getIdentifier(IdentityType.PNI)).thenReturn(UNDISCOVERABLE_PNI);
when(VALID_ACCOUNT_3.getNumber()).thenReturn(VALID_NUMBER_3);
when(VALID_ACCOUNT_3.getUuid()).thenReturn(VALID_UUID_3);
when(VALID_ACCOUNT_3.getPhoneNumberIdentifier()).thenReturn(VALID_PNI_3);