Add /v1/verification

This commit is contained in:
Chris Eager
2023-02-22 14:27:05 -06:00
committed by GitHub
parent e1ea3795bb
commit 35286f838e
37 changed files with 3255 additions and 177 deletions

View File

@@ -22,8 +22,7 @@ class StoredVerificationCodeTest {
private static Stream<Arguments> isValid() {
return Stream.of(
Arguments.of(
new StoredVerificationCode("code", System.currentTimeMillis(), null, null), "code", true),
Arguments.of(new StoredVerificationCode("code", System.currentTimeMillis(), null, null), "code", true),
Arguments.of(new StoredVerificationCode("code", System.currentTimeMillis(), null, null), "incorrect", false),
Arguments.of(new StoredVerificationCode("", System.currentTimeMillis(), null, null), "", false)
);

View File

@@ -75,6 +75,7 @@ import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
import org.whispersystems.textsecuregcm.captcha.AssessmentResult;
import org.whispersystems.textsecuregcm.captcha.CaptchaChecker;
import org.whispersystems.textsecuregcm.captcha.RegistrationCaptchaManager;
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicCaptchaConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
@@ -197,6 +198,8 @@ class AccountControllerTest {
private static final RegistrationLockVerificationManager registrationLockVerificationManager = new RegistrationLockVerificationManager(
accountsManager, clientPresenceManager, backupCredentialsGenerator, rateLimiters);
private static final RegistrationCaptchaManager registrationCaptchaManager = new RegistrationCaptchaManager(
captchaChecker, rateLimiters, Map.of(TEST_NUMBER, 123456), dynamicConfigurationManager);
private static final ResourceExtension resources = ResourceExtension.builder()
.addProvider(AuthHelper.getAuthFilter())
@@ -217,8 +220,7 @@ class AccountControllerTest {
registrationServiceClient,
dynamicConfigurationManager,
turnTokenGenerator,
Map.of(TEST_NUMBER, 123456),
captchaChecker,
registrationCaptchaManager,
pushNotificationManager,
changeNumberManager,
registrationLockVerificationManager,
@@ -250,30 +252,43 @@ class AccountControllerTest {
when(rateLimiters.getUsernameLookupLimiter()).thenReturn(usernameLookupLimiter);
when(senderPinAccount.getLastSeen()).thenReturn(System.currentTimeMillis());
when(senderPinAccount.getRegistrationLock()).thenReturn(new StoredRegistrationLock(Optional.empty(), Optional.empty(), System.currentTimeMillis()));
when(senderPinAccount.getRegistrationLock()).thenReturn(
new StoredRegistrationLock(Optional.empty(), Optional.empty(), System.currentTimeMillis()));
when(senderHasStorage.getUuid()).thenReturn(UUID.randomUUID());
when(senderHasStorage.isStorageSupported()).thenReturn(true);
when(senderHasStorage.getRegistrationLock()).thenReturn(new StoredRegistrationLock(Optional.empty(), Optional.empty(), System.currentTimeMillis()));
when(senderHasStorage.getRegistrationLock()).thenReturn(
new StoredRegistrationLock(Optional.empty(), Optional.empty(), System.currentTimeMillis()));
when(senderRegLockAccount.getRegistrationLock()).thenReturn(new StoredRegistrationLock(Optional.of(registrationLockCredentials.hash()), Optional.of(registrationLockCredentials.salt()), System.currentTimeMillis()));
when(senderRegLockAccount.getRegistrationLock()).thenReturn(
new StoredRegistrationLock(Optional.of(registrationLockCredentials.hash()),
Optional.of(registrationLockCredentials.salt()), System.currentTimeMillis()));
when(senderRegLockAccount.getLastSeen()).thenReturn(System.currentTimeMillis());
when(senderRegLockAccount.getUuid()).thenReturn(SENDER_REG_LOCK_UUID);
when(senderRegLockAccount.getNumber()).thenReturn(SENDER_REG_LOCK);
when(senderTransfer.getRegistrationLock()).thenReturn(new StoredRegistrationLock(Optional.empty(), Optional.empty(), System.currentTimeMillis()));
when(senderTransfer.getRegistrationLock()).thenReturn(
new StoredRegistrationLock(Optional.empty(), Optional.empty(), System.currentTimeMillis()));
when(senderTransfer.getUuid()).thenReturn(SENDER_TRANSFER_UUID);
when(senderTransfer.getNumber()).thenReturn(SENDER_TRANSFER);
when(pendingAccountsManager.getCodeForNumber(SENDER)).thenReturn(Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "1234-push", null)));
when(pendingAccountsManager.getCodeForNumber(SENDER)).thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "1234-push", null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_OLD)).thenReturn(Optional.empty());
when(pendingAccountsManager.getCodeForNumber(SENDER_PIN)).thenReturn(Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), null, null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_REG_LOCK)).thenReturn(Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), null, null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_OVER_PIN)).thenReturn(Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), null, null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_OVER_PREFIX)).thenReturn(Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "1234-push", null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_PREAUTH)).thenReturn(Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "validchallenge", null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_HAS_STORAGE)).thenReturn(Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), null, null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_TRANSFER)).thenReturn(Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), null, null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_PIN)).thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), null, null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_REG_LOCK)).thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), null, null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_OVER_PIN)).thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), null, null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_OVER_PREFIX)).thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "1234-push", null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_PREAUTH)).thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "validchallenge", null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_HAS_STORAGE)).thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), null, null)));
when(pendingAccountsManager.getCodeForNumber(SENDER_TRANSFER)).thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), null, null)));
when(accountsManager.getByE164(eq(SENDER_PIN))).thenReturn(Optional.of(senderPinAccount));
when(accountsManager.getByE164(eq(SENDER_REG_LOCK))).thenReturn(Optional.of(senderRegLockAccount));
@@ -953,7 +968,8 @@ class AccountControllerTest {
final String challenge = "challenge";
final byte[] sessionId = "session".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(SENDER)).thenReturn(Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), challenge, null)));
when(pendingAccountsManager.getCodeForNumber(SENDER)).thenReturn(Optional.of(
new StoredVerificationCode(null, System.currentTimeMillis(), challenge, null)));
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(sessionId));
@@ -1103,8 +1119,8 @@ class AccountControllerTest {
final byte[] sessionId = "session".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(SENDER_REG_LOCK))
.thenReturn(Optional.of(
new StoredVerificationCode(null, System.currentTimeMillis(), "666666-push", sessionId)));
.thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "666666-push", sessionId)));
when(registrationServiceClient.checkVerificationCode(sessionId, "666666", AccountController.REGISTRATION_RPC_TIMEOUT))
.thenReturn(CompletableFuture.completedFuture(true));
@@ -1137,8 +1153,8 @@ class AccountControllerTest {
final byte[] sessionId = "session".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(SENDER_REG_LOCK))
.thenReturn(Optional.of(
new StoredVerificationCode(null, System.currentTimeMillis(), "666666-push", sessionId)));
.thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "666666-push", sessionId)));
when(registrationServiceClient.checkVerificationCode(sessionId, "666666", AccountController.REGISTRATION_RPC_TIMEOUT))
.thenReturn(CompletableFuture.completedFuture(true));
@@ -1164,8 +1180,8 @@ class AccountControllerTest {
final byte[] sessionId = "session".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(SENDER_REG_LOCK))
.thenReturn(Optional.of(
new StoredVerificationCode(null, System.currentTimeMillis(), "666666-push", sessionId)));
.thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "666666-push", sessionId)));
when(registrationServiceClient.checkVerificationCode(sessionId, "666666", AccountController.REGISTRATION_RPC_TIMEOUT))
.thenReturn(CompletableFuture.completedFuture(true));
@@ -1191,8 +1207,8 @@ class AccountControllerTest {
final byte[] sessionId = "session".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(SENDER_REG_LOCK))
.thenReturn(Optional.of(
new StoredVerificationCode(null, System.currentTimeMillis(), "666666-push", sessionId)));
.thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "666666-push", sessionId)));
when(registrationServiceClient.checkVerificationCode(sessionId, "666666", AccountController.REGISTRATION_RPC_TIMEOUT))
.thenReturn(CompletableFuture.completedFuture(true));
@@ -1224,8 +1240,7 @@ class AccountControllerTest {
final byte[] sessionId = "session".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(SENDER_TRANSFER))
.thenReturn(Optional.of(
new StoredVerificationCode(null, System.currentTimeMillis(), "1234-push", sessionId)));
.thenReturn(Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "1234-push", sessionId)));
when(registrationServiceClient.checkVerificationCode(sessionId, "1234", AccountController.REGISTRATION_RPC_TIMEOUT))
.thenReturn(CompletableFuture.completedFuture(true));
@@ -1249,8 +1264,7 @@ class AccountControllerTest {
final byte[] sessionId = "session".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(SENDER_TRANSFER))
.thenReturn(Optional.of(
new StoredVerificationCode(null, System.currentTimeMillis(), "1234-push", sessionId)));
.thenReturn(Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "1234-push", sessionId)));
when(registrationServiceClient.checkVerificationCode(sessionId, "1234", AccountController.REGISTRATION_RPC_TIMEOUT))
.thenReturn(CompletableFuture.completedFuture(true));
@@ -1274,8 +1288,7 @@ class AccountControllerTest {
final byte[] sessionId = "session".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(SENDER_TRANSFER))
.thenReturn(Optional.of(
new StoredVerificationCode(null, System.currentTimeMillis(), "1234-push", sessionId)));
.thenReturn(Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "1234-push", sessionId)));
when(registrationServiceClient.checkVerificationCode(sessionId, "1234", AccountController.REGISTRATION_RPC_TIMEOUT))
.thenReturn(CompletableFuture.completedFuture(true));
@@ -1299,8 +1312,8 @@ class AccountControllerTest {
final String code = "987654";
final byte[] sessionId = "session".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(
new StoredVerificationCode(null, System.currentTimeMillis(), "push", sessionId)));
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "push", sessionId)));
when(registrationServiceClient.checkVerificationCode(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));
@@ -1400,8 +1413,8 @@ class AccountControllerTest {
final String code = "987654";
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(
new StoredVerificationCode(code, System.currentTimeMillis(), "push", sessionId)));
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(
Optional.of(new StoredVerificationCode(code, System.currentTimeMillis(), "push", sessionId)));
when(registrationServiceClient.checkVerificationCode(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(false));
@@ -1426,8 +1439,8 @@ class AccountControllerTest {
final String code = "987654";
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(
new StoredVerificationCode(code, System.currentTimeMillis(), "push", sessionId)));
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(
Optional.of(new StoredVerificationCode(code, System.currentTimeMillis(), "push", sessionId)));
when(registrationServiceClient.checkVerificationCode(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));
@@ -1460,8 +1473,8 @@ class AccountControllerTest {
final String code = "987654";
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(
new StoredVerificationCode(code, System.currentTimeMillis(), "push", sessionId)));
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(
Optional.of(new StoredVerificationCode(code, System.currentTimeMillis(), "push", sessionId)));
when(registrationServiceClient.checkVerificationCode(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));
@@ -1539,8 +1552,8 @@ class AccountControllerTest {
final String reglock = "setec-astronomy";
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(
new StoredVerificationCode(null, System.currentTimeMillis(), "push", sessionId)));
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "push", sessionId)));
when(registrationServiceClient.checkVerificationCode(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));
@@ -1591,15 +1604,15 @@ class AccountControllerTest {
when(AuthHelper.VALID_ACCOUNT.getDevice(2L)).thenReturn(Optional.of(device2));
when(AuthHelper.VALID_ACCOUNT.getDevice(3L)).thenReturn(Optional.of(device3));
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(
new StoredVerificationCode(null, System.currentTimeMillis(), "push", sessionId)));
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "push", sessionId)));
when(registrationServiceClient.checkVerificationCode(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));
var deviceMessages = List.of(
new IncomingMessage(1, 2, 2, "content2"),
new IncomingMessage(1, 3, 3, "content3"));
new IncomingMessage(1, 2, 2, "content2"),
new IncomingMessage(1, 3, 3, "content3"));
var deviceKeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey(), 3L, new SignedPreKey());
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89);
@@ -2231,8 +2244,10 @@ class AccountControllerTest {
Arguments.of("123456", null, false),
Arguments.of(null, new StoredVerificationCode(null, 0, null, null), false),
Arguments.of(null, new StoredVerificationCode(null, 0, "123456", null), false),
Arguments.of("654321", new StoredVerificationCode(null, 0, "123456", null), false),
Arguments.of("123456", new StoredVerificationCode(null, 0, "123456", null), true)
Arguments.of("654321", new StoredVerificationCode(null, 0, "123456", null),
false),
Arguments.of("123456", new StoredVerificationCode(null, 0, "123456", null),
true)
);
}
}

View File

@@ -24,6 +24,7 @@ import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
import io.dropwizard.testing.junit5.ResourceExtension;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
@@ -59,7 +60,7 @@ import org.whispersystems.textsecuregcm.auth.RegistrationLockVerificationManager
import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse;
import org.whispersystems.textsecuregcm.entities.ChangeNumberRequest;
import org.whispersystems.textsecuregcm.entities.PhoneNumberDiscoverabilityRequest;
import org.whispersystems.textsecuregcm.entities.RegistrationSession;
import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.mappers.ImpossiblePhoneNumberExceptionMapper;
@@ -78,7 +79,9 @@ import org.whispersystems.textsecuregcm.util.SystemMapper;
@ExtendWith(DropwizardExtensionsSupport.class)
class AccountControllerV2Test {
public static final String NEW_NUMBER = PhoneNumberUtil.getInstance().format(
private static final long SESSION_EXPIRATION_SECONDS = Duration.ofMinutes(10).toSeconds();
private static final String NEW_NUMBER = PhoneNumberUtil.getInstance().format(
PhoneNumberUtil.getInstance().getExampleNumber("US"),
PhoneNumberUtil.PhoneNumberFormat.E164);
@@ -146,7 +149,9 @@ class AccountControllerV2Test {
void changeNumberSuccess() throws Exception {
when(registrationServiceClient.getSession(any(), any()))
.thenReturn(CompletableFuture.completedFuture(Optional.of(new RegistrationSession(NEW_NUMBER, true))));
.thenReturn(CompletableFuture.completedFuture(
Optional.of(new RegistrationServiceSession(new byte[16], NEW_NUMBER, true, null, null, null,
SESSION_EXPIRATION_SECONDS))));
final AccountIdentityResponse accountIdentityResponse =
resources.getJerseyTest()
@@ -245,7 +250,7 @@ class AccountControllerV2Test {
@ParameterizedTest
@MethodSource
void registrationServiceSessionCheck(@Nullable final RegistrationSession session, final int expectedStatus,
void registrationServiceSessionCheck(@Nullable final RegistrationServiceSession session, final int expectedStatus,
final String message) {
when(registrationServiceClient.getSession(any(), any()))
.thenReturn(CompletableFuture.completedFuture(Optional.ofNullable(session)));
@@ -263,8 +268,14 @@ class AccountControllerV2Test {
static Stream<Arguments> registrationServiceSessionCheck() {
return Stream.of(
Arguments.of(null, 401, "session not found"),
Arguments.of(new RegistrationSession("+18005551234", false), 400, "session number mismatch"),
Arguments.of(new RegistrationSession(NEW_NUMBER, false), 401, "session not verified")
Arguments.of(new RegistrationServiceSession(new byte[16], "+18005551234", false, null, null, null,
SESSION_EXPIRATION_SECONDS), 400,
"session number mismatch"),
Arguments.of(
new RegistrationServiceSession(new byte[16], NEW_NUMBER, false, null, null, null,
SESSION_EXPIRATION_SECONDS),
401,
"session not verified")
);
}
@@ -273,7 +284,9 @@ class AccountControllerV2Test {
void registrationLock(final RegistrationLockError error) throws Exception {
when(registrationServiceClient.getSession(any(), any()))
.thenReturn(
CompletableFuture.completedFuture(Optional.of(new RegistrationSession(NEW_NUMBER, true))));
CompletableFuture.completedFuture(
Optional.of(new RegistrationServiceSession(new byte[16], NEW_NUMBER, true, null, null, null,
SESSION_EXPIRATION_SECONDS))));
when(accountsManager.getByE164(any())).thenReturn(Optional.of(mock(Account.class)));

View File

@@ -17,6 +17,7 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil;
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
import io.dropwizard.testing.junit5.ResourceExtension;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@@ -43,7 +44,7 @@ import org.whispersystems.textsecuregcm.auth.RegistrationLockError;
import org.whispersystems.textsecuregcm.auth.RegistrationLockVerificationManager;
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.entities.RegistrationRequest;
import org.whispersystems.textsecuregcm.entities.RegistrationSession;
import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.mappers.ImpossiblePhoneNumberExceptionMapper;
@@ -59,11 +60,12 @@ import org.whispersystems.textsecuregcm.util.SystemMapper;
@ExtendWith(DropwizardExtensionsSupport.class)
class RegistrationControllerTest {
private static final long SESSION_EXPIRATION_SECONDS = Duration.ofMinutes(10).toSeconds();
private static final String NUMBER = PhoneNumberUtil.getInstance().format(
PhoneNumberUtil.getInstance().getExampleNumber("US"),
PhoneNumberUtil.PhoneNumberFormat.E164);
public static final String PASSWORD = "password";
private static final String PASSWORD = "password";
private final AccountsManager accountsManager = mock(AccountsManager.class);
private final RegistrationServiceClient registrationServiceClient = mock(RegistrationServiceClient.class);
@@ -187,7 +189,7 @@ class RegistrationControllerTest {
@ParameterizedTest
@MethodSource
void registrationServiceSessionCheck(@Nullable final RegistrationSession session, final int expectedStatus,
void registrationServiceSessionCheck(@Nullable final RegistrationServiceSession session, final int expectedStatus,
final String message) {
when(registrationServiceClient.getSession(any(), any()))
.thenReturn(CompletableFuture.completedFuture(Optional.ofNullable(session)));
@@ -204,8 +206,15 @@ class RegistrationControllerTest {
static Stream<Arguments> registrationServiceSessionCheck() {
return Stream.of(
Arguments.of(null, 401, "session not found"),
Arguments.of(new RegistrationSession("+18005551234", false), 400, "session number mismatch"),
Arguments.of(new RegistrationSession(NUMBER, false), 401, "session not verified")
Arguments.of(
new RegistrationServiceSession(new byte[16], "+18005551234", false, null, null, null,
SESSION_EXPIRATION_SECONDS),
400,
"session number mismatch"),
Arguments.of(
new RegistrationServiceSession(new byte[16], NUMBER, false, null, null, null, SESSION_EXPIRATION_SECONDS),
401,
"session not verified")
);
}
@@ -244,7 +253,10 @@ class RegistrationControllerTest {
@EnumSource(RegistrationLockError.class)
void registrationLock(final RegistrationLockError error) throws Exception {
when(registrationServiceClient.getSession(any(), any()))
.thenReturn(CompletableFuture.completedFuture(Optional.of(new RegistrationSession(NUMBER, true))));
.thenReturn(
CompletableFuture.completedFuture(
Optional.of(new RegistrationServiceSession(new byte[16], NUMBER, true, null, null, null,
SESSION_EXPIRATION_SECONDS))));
when(accountsManager.getByE164(any())).thenReturn(Optional.of(mock(Account.class)));
@@ -275,7 +287,10 @@ class RegistrationControllerTest {
void deviceTransferAvailable(final boolean existingAccount, final boolean transferSupported,
final boolean skipDeviceTransfer, final int expectedStatus) throws Exception {
when(registrationServiceClient.getSession(any(), any()))
.thenReturn(CompletableFuture.completedFuture(Optional.of(new RegistrationSession(NUMBER, true))));
.thenReturn(
CompletableFuture.completedFuture(
Optional.of(new RegistrationServiceSession(new byte[16], NUMBER, true, null, null, null,
SESSION_EXPIRATION_SECONDS))));
final Optional<Account> maybeAccount;
if (existingAccount) {
@@ -301,7 +316,10 @@ class RegistrationControllerTest {
@Test
void registrationSuccess() throws Exception {
when(registrationServiceClient.getSession(any(), any()))
.thenReturn(CompletableFuture.completedFuture(Optional.of(new RegistrationSession(NUMBER, true))));
.thenReturn(
CompletableFuture.completedFuture(
Optional.of(new RegistrationServiceSession(new byte[16], NUMBER, true, null, null, null,
SESSION_EXPIRATION_SECONDS))));
when(accountsManager.create(any(), any(), any(), any(), any()))
.thenReturn(mock(Account.class));

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.storage;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
class SerializedExpireableJsonDynamoStoreTest {
static abstract class Tests<T> {
private static final String TABLE_NAME = "test";
private static final String KEY = "foo";
static final Clock clock = Clock.systemUTC();
interface Value {
String v();
}
@RegisterExtension
static final DynamoDbExtension DYNAMO_DB_EXTENSION = DynamoDbExtension.builder()
.tableName(TABLE_NAME)
.hashKey(SerializedExpireableJsonDynamoStore.KEY_KEY)
.attributeDefinition(AttributeDefinition.builder()
.attributeName(SerializedExpireableJsonDynamoStore.KEY_KEY)
.attributeType(ScalarAttributeType.S)
.build())
.build();
private SerializedExpireableJsonDynamoStore<T> store;
abstract SerializedExpireableJsonDynamoStore<T> getStore(final DynamoDbAsyncClient dynamoDbClient,
final String tableName);
abstract T testValue(final String v);
abstract T maybeExpiredTestValue(final String v);
@BeforeEach
void setUp() {
store = getStore(DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), TABLE_NAME);
}
@Test
void testStoreAndFind() throws Exception {
assertEquals(Optional.empty(), store.findForKey(KEY).get(1, TimeUnit.SECONDS));
final T original = testValue("1234");
final T second = testValue("5678");
store.insert(KEY, original).get(1, TimeUnit.SECONDS);
{
final Optional<T> maybeValue = store.findForKey(KEY).get(1, TimeUnit.SECONDS);
assertTrue(maybeValue.isPresent());
assertEquals(original, maybeValue.get());
}
assertThrows(Exception.class, () -> store.insert(KEY, second).get(1, TimeUnit.SECONDS));
assertDoesNotThrow(() -> store.update(KEY, second).get(1, TimeUnit.SECONDS));
{
final Optional<T> maybeValue = store.findForKey(KEY).get(1, TimeUnit.SECONDS);
assertTrue(maybeValue.isPresent());
assertEquals(second, maybeValue.get());
}
}
@Test
void testRemove() throws Exception {
assertEquals(Optional.empty(), store.findForKey(KEY).get(1, TimeUnit.SECONDS));
store.insert(KEY, testValue("1234")).get(1, TimeUnit.SECONDS);
assertTrue(store.findForKey(KEY).get(1, TimeUnit.SECONDS).isPresent());
store.remove(KEY).get(1, TimeUnit.SECONDS);
assertFalse(store.findForKey(KEY).get(1, TimeUnit.SECONDS).isPresent());
final T v = maybeExpiredTestValue("1234");
store.insert(KEY, v).get(1, TimeUnit.SECONDS);
assertEquals(v instanceof SerializedExpireableJsonDynamoStore.Expireable,
store.findForKey(KEY).get(1, TimeUnit.SECONDS).isEmpty());
}
}
record Expires(String v, long timestamp) implements SerializedExpireableJsonDynamoStore.Expireable, Tests.Value {
static final Duration EXPIRATION = Duration.ofSeconds(30);
@Override
public long getExpirationEpochSeconds() {
return Instant.ofEpochMilli(timestamp()).plus(EXPIRATION).getEpochSecond();
}
}
@Nested
class Expireable extends Tests<Expires> {
class ExpiresStore extends SerializedExpireableJsonDynamoStore<Expires> {
public ExpiresStore(final DynamoDbAsyncClient dynamoDbClient, final String tableName) {
super(dynamoDbClient, tableName, clock);
}
}
private static final long VALID_TIMESTAMP = Instant.now().toEpochMilli();
private static final long EXPIRED_TIMESTAMP = Instant.now().minus(Expires.EXPIRATION).minus(
Duration.ofHours(1)).toEpochMilli();
@Override
SerializedExpireableJsonDynamoStore<Expires> getStore(final DynamoDbAsyncClient dynamoDbClient,
final String tableName) {
return new ExpiresStore(dynamoDbClient, tableName);
}
@Override
Expires testValue(final String v) {
return new Expires(v, VALID_TIMESTAMP);
}
@Override
Expires maybeExpiredTestValue(final String v) {
return new Expires(v, EXPIRED_TIMESTAMP);
}
}
record DoesNotExpire(String v) implements Tests.Value {
}
@Nested
class NotExpireable extends Tests<DoesNotExpire> {
class DoesNotExpireStore extends SerializedExpireableJsonDynamoStore<DoesNotExpire> {
public DoesNotExpireStore(final DynamoDbAsyncClient dynamoDbClient, final String tableName) {
super(dynamoDbClient, tableName, clock);
}
}
@Override
SerializedExpireableJsonDynamoStore<DoesNotExpire> getStore(final DynamoDbAsyncClient dynamoDbClient,
final String tableName) {
return new DoesNotExpireStore(dynamoDbClient, tableName);
}
@Override
DoesNotExpire testValue(final String v) {
return new DoesNotExpire(v);
}
@Override
DoesNotExpire maybeExpiredTestValue(final String v) {
return new DoesNotExpire(v);
}
}
}

View File

@@ -53,8 +53,10 @@ class VerificationCodeStoreTest {
void testStoreAndFind() {
assertEquals(Optional.empty(), verificationCodeStore.findForNumber(PHONE_NUMBER));
final StoredVerificationCode originalCode = new StoredVerificationCode("1234", VALID_TIMESTAMP, "abcd", "session".getBytes(StandardCharsets.UTF_8));
final StoredVerificationCode secondCode = new StoredVerificationCode("5678", VALID_TIMESTAMP, "efgh", "changed-session".getBytes(StandardCharsets.UTF_8));
final StoredVerificationCode originalCode = new StoredVerificationCode("1234", VALID_TIMESTAMP, "abcd",
"session".getBytes(StandardCharsets.UTF_8));
final StoredVerificationCode secondCode = new StoredVerificationCode("5678", VALID_TIMESTAMP, "efgh",
"changed-session".getBytes(StandardCharsets.UTF_8));
verificationCodeStore.insert(PHONE_NUMBER, originalCode);
{
@@ -77,13 +79,15 @@ class VerificationCodeStoreTest {
void testRemove() {
assertEquals(Optional.empty(), verificationCodeStore.findForNumber(PHONE_NUMBER));
verificationCodeStore.insert(PHONE_NUMBER, new StoredVerificationCode("1234", VALID_TIMESTAMP, "abcd", "session".getBytes(StandardCharsets.UTF_8)));
verificationCodeStore.insert(PHONE_NUMBER,
new StoredVerificationCode("1234", VALID_TIMESTAMP, "abcd", "session".getBytes(StandardCharsets.UTF_8)));
assertTrue(verificationCodeStore.findForNumber(PHONE_NUMBER).isPresent());
verificationCodeStore.remove(PHONE_NUMBER);
assertFalse(verificationCodeStore.findForNumber(PHONE_NUMBER).isPresent());
verificationCodeStore.insert(PHONE_NUMBER, new StoredVerificationCode("1234", EXPIRED_TIMESTAMP, "abcd", "session".getBytes(StandardCharsets.UTF_8)));
verificationCodeStore.insert(PHONE_NUMBER,
new StoredVerificationCode("1234", EXPIRED_TIMESTAMP, "abcd", "session".getBytes(StandardCharsets.UTF_8)));
assertFalse(verificationCodeStore.findForNumber(PHONE_NUMBER).isPresent());
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.storage;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletionException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.registration.VerificationSession;
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
class VerificationSessionsTest {
private static final String TABLE_NAME = "verification_sessions_test";
private static final Clock clock = Clock.systemUTC();
@RegisterExtension
static final DynamoDbExtension DYNAMO_DB_EXTENSION = DynamoDbExtension.builder()
.tableName(TABLE_NAME)
.hashKey(VerificationSessions.KEY_KEY)
.attributeDefinition(AttributeDefinition.builder()
.attributeName(VerificationSessions.KEY_KEY)
.attributeType(ScalarAttributeType.S)
.build())
.build();
private VerificationSessions verificationSessions;
@BeforeEach
void setUp() {
verificationSessions = new VerificationSessions(DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), TABLE_NAME, clock);
}
@Test
void testExpiration() {
final Instant created = Instant.now().minusSeconds(60);
final Instant updates = Instant.now();
final Duration remoteExpiration = Duration.ofMinutes(2);
final VerificationSession verificationSession = new VerificationSession(null,
List.of(VerificationSession.Information.PUSH_CHALLENGE), Collections.emptyList(), true,
created.toEpochMilli(), updates.toEpochMilli(), remoteExpiration.toSeconds());
assertEquals(updates.plus(remoteExpiration).getEpochSecond(), verificationSession.getExpirationEpochSeconds());
}
@Test
void testStore() {
assertTimeoutPreemptively(Duration.ofSeconds(5), () -> {
final String sessionId = "sessionId";
final Optional<VerificationSession> absentSession = verificationSessions.findForKey(sessionId).join();
assertTrue(absentSession.isEmpty());
final VerificationSession session = new VerificationSession(null,
List.of(VerificationSession.Information.PUSH_CHALLENGE), Collections.emptyList(), true,
clock.millis(), clock.millis(), Duration.ofMinutes(1).toSeconds());
verificationSessions.insert(sessionId, session).join();
assertEquals(session, verificationSessions.findForKey(sessionId).join().orElseThrow());
final CompletionException ce = assertThrows(CompletionException.class,
() -> verificationSessions.insert(sessionId, session).join());
final Throwable t = ExceptionUtils.unwrap(ce);
assertTrue(t instanceof ConditionalCheckFailedException,
"inserting with the same key should fail conditional checks");
final VerificationSession updatedSession = new VerificationSession(null, Collections.emptyList(),
List.of(VerificationSession.Information.PUSH_CHALLENGE), true, clock.millis(), clock.millis(),
Duration.ofMinutes(2).toSeconds());
verificationSessions.update(sessionId, updatedSession).join();
assertEquals(updatedSession, verificationSessions.findForKey(sessionId).join().orElseThrow());
});
}
}

View File

@@ -25,7 +25,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
@@ -36,8 +35,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
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.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;