Discard old Twilio machinery and rely entirely on the stand-alone registration service

This commit is contained in:
Jon Chambers
2022-10-07 17:11:37 -04:00
committed by Jon Chambers
parent 78f95e4859
commit 74d65b37a8
19 changed files with 159 additions and 2466 deletions

View File

@@ -209,32 +209,6 @@ class DynamicConfigurationTest {
}
}
@Test
void testParseTwilioConfiguration() throws JsonProcessingException {
{
final String emptyConfigYaml = REQUIRED_CONFIG.concat("test: true");
final DynamicConfiguration emptyConfig =
DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).orElseThrow();
assertTrue(emptyConfig.getTwilioConfiguration().getNumbers().isEmpty());
}
{
final String twilioConfigYaml = REQUIRED_CONFIG.concat("""
twilio:
numbers:
- 2135551212
- 2135551313
""");
final DynamicTwilioConfiguration config =
DynamicConfigurationManager.parseConfiguration(twilioConfigYaml, DynamicConfiguration.class).orElseThrow()
.getTwilioConfiguration();
assertEquals(List.of("2135551212", "2135551313"), config.getNumbers());
}
}
@Test
void testParsePaymentsConfiguration() throws JsonProcessingException {
{

View File

@@ -21,6 +21,7 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -86,7 +87,6 @@ import org.whispersystems.textsecuregcm.entities.ReserveUsernameResponse;
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
import org.whispersystems.textsecuregcm.entities.UsernameRequest;
import org.whispersystems.textsecuregcm.entities.UsernameResponse;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.mappers.ImpossiblePhoneNumberExceptionMapper;
@@ -99,8 +99,6 @@ import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
import org.whispersystems.textsecuregcm.registration.ClientType;
import org.whispersystems.textsecuregcm.registration.MessageTransport;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
import org.whispersystems.textsecuregcm.sms.SmsSender;
import org.whispersystems.textsecuregcm.sms.TwilioVerifyExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
@@ -144,7 +142,6 @@ class AccountControllerTest {
private static final String INVALID_CAPTCHA_TOKEN = "invalid_token";
private static final String TEST_NUMBER = "+14151111113";
private static final Integer TEST_VERIFICATION_CODE = 123456;
private static StoredVerificationCodeManager pendingAccountsManager = mock(StoredVerificationCodeManager.class);
private static AccountsManager accountsManager = mock(AccountsManager.class);
@@ -158,7 +155,6 @@ class AccountControllerTest {
private static RateLimiter usernameSetLimiter = mock(RateLimiter.class);
private static RateLimiter usernameReserveLimiter = mock(RateLimiter.class);
private static RateLimiter usernameLookupLimiter = mock(RateLimiter.class);
private static SmsSender smsSender = mock(SmsSender.class);
private static RegistrationServiceClient registrationServiceClient = mock(RegistrationServiceClient.class);
private static TurnTokenGenerator turnTokenGenerator = mock(TurnTokenGenerator.class);
private static Account senderPinAccount = mock(Account.class);
@@ -171,11 +167,6 @@ class AccountControllerTest {
private static DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
private static TwilioVerifyExperimentEnrollmentManager verifyExperimentEnrollmentManager = mock(
TwilioVerifyExperimentEnrollmentManager.class);
private static ExperimentEnrollmentManager experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class);
private byte[] registration_lock_key = new byte[32];
private static ExternalServiceCredentialGenerator storageCredentialGenerator = new ExternalServiceCredentialGenerator(new byte[32], new byte[32], false);
@@ -194,17 +185,14 @@ class AccountControllerTest {
accountsManager,
abusiveHostRules,
rateLimiters,
smsSender,
registrationServiceClient,
dynamicConfigurationManager,
turnTokenGenerator,
Map.of(TEST_NUMBER, TEST_VERIFICATION_CODE),
Map.of(TEST_NUMBER, 123456),
recaptchaClient,
pushNotificationManager,
verifyExperimentEnrollmentManager,
changeNumberManager,
storageCredentialGenerator,
experimentEnrollmentManager))
storageCredentialGenerator))
.build();
@@ -342,7 +330,6 @@ class AccountControllerTest {
usernameSetLimiter,
usernameReserveLimiter,
usernameLookupLimiter,
smsSender,
registrationServiceClient,
turnTokenGenerator,
senderPinAccount,
@@ -351,8 +338,6 @@ class AccountControllerTest {
senderTransfer,
recaptchaClient,
pushNotificationManager,
verifyExperimentEnrollmentManager,
experimentEnrollmentManager,
changeNumberManager);
clearInvocations(AuthHelper.DISABLED_DEVICE);
@@ -455,7 +440,7 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(400);
assertThat(response.readEntity(String.class)).isBlank();
verifyNoMoreInteractions(pushNotificationManager);
verifyNoInteractions(pushNotificationManager);
}
@Test
@@ -473,59 +458,17 @@ class AccountControllerTest {
assertThat(responseEntity.getOriginalNumber()).isEqualTo(number);
assertThat(responseEntity.getNormalizedNumber()).isEqualTo("+447700900111");
verifyNoMoreInteractions(pushNotificationManager);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendCode(final boolean enrolledInVerifyExperiment) {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverSmsVerificationWithTwilioVerify(anyString(), any(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
.queryParam("challenge", "1234-push")
.request()
.header("X-Forwarded-For", NICE_HOST)
.get();
assertThat(response.getStatus()).isEqualTo(200);
if (enrolledInVerifyExperiment) {
ArgumentCaptor<StoredVerificationCode> storedVerificationCodeArgumentCaptor = ArgumentCaptor
.forClass(StoredVerificationCode.class);
verify(smsSender).deliverSmsVerificationWithTwilioVerify(eq(SENDER), eq(Optional.empty()), anyString(), eq(Collections.emptyList()));
verify(pendingAccountsManager, times(2)).store(eq(SENDER), storedVerificationCodeArgumentCaptor.capture());
assertThat(storedVerificationCodeArgumentCaptor.getValue().twilioVerificationSid())
.isEqualTo("VerificationSid");
} else {
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.empty()), anyString());
}
verifyNoMoreInteractions(smsSender);
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
verifyNoInteractions(pushNotificationManager);
}
@Test
void testSendCodeViaRegistrationService() throws NumberParseException {
void testSendCode() throws NumberParseException {
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(sessionId));
when(experimentEnrollmentManager.isEnrolled(SENDER, AccountController.REGISTRATION_SERVICE_EXPERIMENT_NAME))
.thenReturn(true);
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
@@ -543,8 +486,6 @@ class AccountControllerTest {
verify(pendingAccountsManager).store(eq(SENDER), argThat(
storedVerificationCode -> Arrays.equals(storedVerificationCode.sessionId(), sessionId) &&
"1234-push".equals(storedVerificationCode.pushCode())));
verifyNoInteractions(smsSender);
}
@Test
@@ -560,7 +501,7 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(400);
assertThat(response.readEntity(String.class)).isBlank();
verify(smsSender, never()).deliverSmsVerification(any(), any(), any());
verify(registrationServiceClient, never()).sendRegistrationCode(any(), any(), any(), any(), any());
}
@Test
@@ -581,20 +522,14 @@ class AccountControllerTest {
assertThat(responseEntity.getOriginalNumber()).isEqualTo(number);
assertThat(responseEntity.getNormalizedNumber()).isEqualTo("+447700900111");
verify(smsSender, never()).deliverSmsVerification(any(), any(), any());
verify(registrationServiceClient, never()).sendRegistrationCode(any(), any(), any(), any(), any());
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
public void testSendCodeVoiceNoLocale(final boolean enrolledInVerifyExperiment) throws Exception {
@Test
public void testSendCodeVoiceNoLocale() throws NumberParseException {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverVoxVerificationWithTwilioVerify(anyString(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(new byte[16]));
Response response =
resources.getJerseyTest()
@@ -604,122 +539,18 @@ class AccountControllerTest {
.header("X-Forwarded-For", NICE_HOST)
.get();
assertThat(response.getStatus()).isEqualTo(200);
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER, null);
if (enrolledInVerifyExperiment) {
verify(smsSender).deliverVoxVerificationWithTwilioVerify(eq(SENDER), anyString(), eq(Collections.emptyList()));
} else {
verify(smsSender).deliverVoxVerification(eq(SENDER), anyString(), eq(Collections.emptyList()));
}
assertThat(response.getStatus()).isEqualTo(200);
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.VOICE, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
public void testSendCodeVoiceSingleLocale(final boolean enrolledInVerifyExperiment) throws Exception {
@Test
void testSendCodeWithValidPreauth() throws NumberParseException {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverVoxVerificationWithTwilioVerify(anyString(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/voice/code/%s", SENDER))
.queryParam("challenge", "1234-push")
.request()
.header("Accept-Language", "pt-BR")
.header("X-Forwarded-For", NICE_HOST)
.get();
assertThat(response.getStatus()).isEqualTo(200);
if (enrolledInVerifyExperiment) {
verify(smsSender)
.deliverVoxVerificationWithTwilioVerify(eq(SENDER), anyString(), eq(Locale.LanguageRange.parse("pt-BR")));
} else {
verify(smsSender).deliverVoxVerification(eq(SENDER), anyString(), eq(Locale.LanguageRange.parse("pt-BR")));
}
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
public void testSendCodeVoiceMultipleLocales(final boolean enrolledInVerifyExperiment) throws Exception {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverVoxVerificationWithTwilioVerify(anyString(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/voice/code/%s", SENDER))
.queryParam("challenge", "1234-push")
.request()
.header("Accept-Language", "en-US;q=1, ar-US;q=0.9, fa-US;q=0.8, zh-Hans-US;q=0.7, ru-RU;q=0.6, zh-Hant-US;q=0.5")
.header("X-Forwarded-For", NICE_HOST)
.get();
assertThat(response.getStatus()).isEqualTo(200);
if (enrolledInVerifyExperiment) {
verify(smsSender).deliverVoxVerificationWithTwilioVerify(eq(SENDER), anyString(), eq(Locale.LanguageRange
.parse("en-US;q=1, ar-US;q=0.9, fa-US;q=0.8, zh-Hans-US;q=0.7, ru-RU;q=0.6, zh-Hant-US;q=0.5")));
} else {
verify(smsSender).deliverVoxVerification(eq(SENDER), anyString(), eq(Locale.LanguageRange
.parse("en-US;q=1, ar-US;q=0.9, fa-US;q=0.8, zh-Hans-US;q=0.7, ru-RU;q=0.6, zh-Hant-US;q=0.5")));
}
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendCodeVoiceInvalidLocale(boolean enrolledInVerifyExperiment) throws Exception {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverVoxVerificationWithTwilioVerify(anyString(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/voice/code/%s", SENDER))
.queryParam("challenge", "1234-push")
.request()
.header("Accept-Language", "This is not a reasonable Accept-Language value")
.header("X-Forwarded-For", NICE_HOST)
.get();
// Should still send a code, just with no accept language
assertThat(response.getStatus()).isEqualTo(200);
if (enrolledInVerifyExperiment) {
verify(smsSender).deliverVoxVerificationWithTwilioVerify(eq(SENDER), anyString(), eq(Collections.emptyList()));
} else {
verify(smsSender).deliverVoxVerification(eq(SENDER), anyString(), eq(Collections.emptyList()));
}
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendCodeWithValidPreauth(final boolean enrolledInVerifyExperiment) throws Exception {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverSmsVerificationWithTwilioVerify(anyString(), any(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(new byte[16]));
Response response =
resources.getJerseyTest()
@@ -731,17 +562,14 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(200);
if (enrolledInVerifyExperiment) {
verify(smsSender).deliverSmsVerificationWithTwilioVerify(eq(SENDER_PREAUTH), eq(Optional.empty()), anyString(),
eq(Collections.emptyList()));
} else {
verify(smsSender).deliverSmsVerification(eq(SENDER_PREAUTH), eq(Optional.empty()), anyString());
}
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER_PREAUTH, null);
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
}
@Test
void testSendCodeWithInvalidPreauth() throws Exception {
void testSendCodeWithInvalidPreauth() {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER_PREAUTH))
@@ -752,12 +580,12 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(403);
verifyNoMoreInteractions(smsSender);
verifyNoMoreInteractions(abusiveHostRules);
verifyNoInteractions(registrationServiceClient);
verifyNoInteractions(abusiveHostRules);
}
@Test
void testSendCodeWithNoPreauth() throws Exception {
void testSendCodeWithNoPreauth() {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER_PREAUTH))
@@ -767,22 +595,14 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(402);
verify(smsSender, never()).deliverSmsVerification(eq(SENDER_PREAUTH), eq(Optional.empty()), anyString());
verify(smsSender, never()).deliverSmsVerificationWithTwilioVerify(eq(SENDER_PREAUTH), eq(Optional.empty()), anyString(), anyList());
verifyNoInteractions(registrationServiceClient);
}
@Test
void testSendiOSCode() throws NumberParseException {
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendiOSCode(final boolean enrolledInVerifyExperiment) throws Exception {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverSmsVerificationWithTwilioVerify(anyString(), any(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(new byte[16]));
Response response =
resources.getJerseyTest()
@@ -795,25 +615,15 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(200);
if (enrolledInVerifyExperiment) {
verify(smsSender).deliverSmsVerificationWithTwilioVerify(eq(SENDER), eq(Optional.of("ios")), anyString(),
eq(Collections.emptyList()));
} else {
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.of("ios")), anyString());
}
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER, null);
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.IOS, null, AccountController.REGISTRATION_RPC_TIMEOUT);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendAndroidNgCode(final boolean enrolledInVerifyExperiment) throws Exception {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverSmsVerificationWithTwilioVerify(anyString(), any(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
@Test
void testSendAndroidNgCode() throws NumberParseException {
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(new byte[16]));
Response response =
resources.getJerseyTest()
@@ -826,25 +636,13 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(200);
if (enrolledInVerifyExperiment) {
verify(smsSender).deliverSmsVerificationWithTwilioVerify(eq(SENDER), eq(Optional.of("android-ng")), anyString(),
eq(Collections.emptyList()));
} else {
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.of("android-ng")), anyString());
}
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER, null);
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.ANDROID_WITHOUT_FCM, null, AccountController.REGISTRATION_RPC_TIMEOUT);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendAbusiveHost(final boolean enrolledInVerifyExperiment) {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverSmsVerificationWithTwilioVerify(anyString(), any(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
@Test
void testSendAbusiveHost() {
Response response =
resources.getJerseyTest()
@@ -857,20 +655,14 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules).isBlocked(eq(ABUSIVE_HOST));
verifyNoMoreInteractions(smsSender);
verifyNoInteractions(registrationServiceClient);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendAbusiveHostWithValidCaptcha(final boolean enrolledInVerifyExperiment) throws IOException {
@Test
void testSendAbusiveHostWithValidCaptcha() throws NumberParseException {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverSmsVerificationWithTwilioVerify(anyString(), any(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(new byte[16]));
Response response =
resources.getJerseyTest()
@@ -882,27 +674,16 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(200);
verifyNoMoreInteractions(abusiveHostRules);
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER, null);
verifyNoInteractions(abusiveHostRules);
verify(recaptchaClient).verify(eq(VALID_CAPTCHA_TOKEN), eq(ABUSIVE_HOST));
if (enrolledInVerifyExperiment) {
verify(smsSender).deliverSmsVerificationWithTwilioVerify(eq(SENDER), eq(Optional.empty()), anyString(),
eq(Collections.emptyList()));
} else {
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.empty()), anyString());
}
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendAbusiveHostWithInvalidCaptcha(final boolean enrolledInVerifyExperiment) {
@Test
void testSendAbusiveHostWithInvalidCaptcha() {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverSmsVerificationWithTwilioVerify(anyString(), any(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
@@ -913,23 +694,13 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(402);
verifyNoMoreInteractions(abusiveHostRules);
verifyNoInteractions(abusiveHostRules);
verify(recaptchaClient).verify(eq(INVALID_CAPTCHA_TOKEN), eq(ABUSIVE_HOST));
verifyNoMoreInteractions(smsSender);
verifyNoInteractions(registrationServiceClient);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendRateLimitedHostAutoBlock(final boolean enrolledInVerifyExperiment) {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverSmsVerificationWithTwilioVerify(anyString(), any(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
@Test
void testSendRateLimitedHostAutoBlock() {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
@@ -944,21 +715,12 @@ class AccountControllerTest {
verify(abusiveHostRules).setBlockedHost(eq(RATE_LIMITED_IP_HOST));
verifyNoMoreInteractions(abusiveHostRules);
verifyNoMoreInteractions(recaptchaClient);
verifyNoMoreInteractions(smsSender);
verifyNoInteractions(recaptchaClient);
verifyNoInteractions(registrationServiceClient);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendRateLimitedPrefixAutoBlock(final boolean enrolledInVerifyExperiment) {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverSmsVerificationWithTwilioVerify(anyString(), any(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
@Test
void testSendRateLimitedPrefixAutoBlock() {
Response response =
resources.getJerseyTest()
@@ -974,21 +736,12 @@ class AccountControllerTest {
verify(abusiveHostRules).setBlockedHost(eq(RATE_LIMITED_PREFIX_HOST));
verifyNoMoreInteractions(abusiveHostRules);
verifyNoMoreInteractions(recaptchaClient);
verifyNoMoreInteractions(smsSender);
verifyNoInteractions(recaptchaClient);
verifyNoInteractions(registrationServiceClient);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendRateLimitedHostNoAutoBlock(final boolean enrolledInVerifyExperiment) {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverSmsVerificationWithTwilioVerify(anyString(), any(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
@Test
void testSendRateLimitedHostNoAutoBlock() {
Response response =
resources.getJerseyTest()
@@ -1003,17 +756,13 @@ class AccountControllerTest {
verify(abusiveHostRules).isBlocked(eq(RATE_LIMITED_HOST2));
verifyNoMoreInteractions(abusiveHostRules);
verifyNoMoreInteractions(recaptchaClient);
verifyNoMoreInteractions(smsSender);
verifyNoInteractions(recaptchaClient);
verifyNoInteractions(registrationServiceClient);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendMultipleHost(final boolean enrolledInVerifyExperiment) {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
@Test
void testSendMultipleHost() {
Response response =
resources.getJerseyTest()
@@ -1028,16 +777,12 @@ class AccountControllerTest {
verify(abusiveHostRules, times(1)).isBlocked(eq(ABUSIVE_HOST));
verifyNoMoreInteractions(abusiveHostRules);
verifyNoMoreInteractions(smsSender);
verifyNoInteractions(registrationServiceClient);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendRestrictedHostOut(final boolean enrolledInVerifyExperiment) {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
@Test
void testSendRestrictedHostOut() {
final String challenge = "challenge";
when(pendingAccountsManager.getCodeForNumber(RESTRICTED_NUMBER))
@@ -1054,17 +799,15 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
verifyNoMoreInteractions(smsSender);
verifyNoInteractions(registrationServiceClient);
}
@ParameterizedTest
@CsvSource({
"+12025550123, true, true",
"+12025550123, false, true",
"+12505550199, true, false",
"+12505550199, false, false",
"+12025550123, true",
"+12505550199, false",
})
void testRestrictedRegion(final String number, final boolean enrolledInVerifyExperiment, final boolean expectSendCode) {
void testRestrictedRegion(final String number, final boolean expectSendCode) throws NumberParseException {
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
@@ -1073,15 +816,12 @@ class AccountControllerTest {
when(dynamicConfiguration.getCaptchaConfiguration()).thenReturn(signupCaptchaConfig);
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
final String challenge = "challenge";
when(pendingAccountsManager.getCodeForNumber(number))
.thenReturn(Optional.of(new StoredVerificationCode("123456", System.currentTimeMillis(), challenge, null, null)));
when(smsSender.deliverSmsVerificationWithTwilioVerify(any(), any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(Optional.empty()));
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(new byte[16]));
Response response =
resources.getJerseyTest()
@@ -1094,31 +834,22 @@ class AccountControllerTest {
if (expectSendCode) {
assertThat(response.getStatus()).isEqualTo(200);
if (enrolledInVerifyExperiment) {
verify(smsSender).deliverSmsVerificationWithTwilioVerify(eq(number), any(), any(), any());
} else {
verify(smsSender).deliverSmsVerification(eq(number), any(), any());
}
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(number, null);
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
} else {
assertThat(response.getStatus()).isEqualTo(402);
verifyNoMoreInteractions(smsSender);
verifyNoInteractions(registrationServiceClient);
}
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSendRestrictedIn(final boolean enrolledInVerifyExperiment) throws Exception {
@Test
void testSendRestrictedIn() throws NumberParseException {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
if (enrolledInVerifyExperiment) {
when(smsSender.deliverSmsVerificationWithTwilioVerify(anyString(), any(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
final String challenge = "challenge";
when(pendingAccountsManager.getCodeForNumber(SENDER)).thenReturn(Optional.of(new StoredVerificationCode("123456", System.currentTimeMillis(), challenge, null, null)));
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(new byte[16]));
Response response =
resources.getJerseyTest()
@@ -1130,40 +861,38 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(200);
if (enrolledInVerifyExperiment) {
verify(smsSender).deliverSmsVerificationWithTwilioVerify(eq(SENDER), eq(Optional.empty()), anyString(),
eq(Collections.emptyList()));
} else {
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.empty()), anyString());
}
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER, null);
verifyNoMoreInteractions(smsSender);
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
}
@Test
void testSendCodeTestDeviceNumber() throws Exception {
// no push code and a blocked host, but should evade captchas and skip smsSender
void testSendCodeTestDeviceNumber() throws NumberParseException {
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(sessionId));
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", TEST_NUMBER))
.request()
.header("X-Forwarded-For", ABUSIVE_HOST)
.get();
ArgumentCaptor<StoredVerificationCode> captor = ArgumentCaptor.forClass(StoredVerificationCode.class);
final ArgumentCaptor<StoredVerificationCode> captor = ArgumentCaptor.forClass(StoredVerificationCode.class);
verify(pendingAccountsManager).store(eq(TEST_NUMBER), captor.capture());
assertThat(captor.getValue().code()).isEqualTo(Integer.toString(TEST_VERIFICATION_CODE));
assertThat(captor.getValue().code()).isNull();
assertThat(captor.getValue().sessionId()).isEqualTo(sessionId);
assertThat(response.getStatus()).isEqualTo(200);
verifyNoInteractions(smsSender);
// Even though no actual SMS will be sent, we leave that decision to the registration service
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(TEST_NUMBER, null);
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testVerifyCode(final boolean enrolledInVerifyExperiment) throws Exception {
if (enrolledInVerifyExperiment) {
when(pendingAccountsManager.getCodeForNumber(SENDER)).thenReturn(
Optional.of(new StoredVerificationCode("1234", System.currentTimeMillis(), "1234-push", "VerificationSid", null)));
}
@Test
void testVerifyCode() throws Exception {
resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", "1234"))
.request()
@@ -1171,11 +900,6 @@ class AccountControllerTest {
.put(Entity.entity(new AccountAttributes(), MediaType.APPLICATION_JSON_TYPE), AccountIdentityResponse.class);
verify(accountsManager).create(eq(SENDER), eq("bar"), any(), any(), anyList());
if (enrolledInVerifyExperiment) {
verify(smsSender).reportVerificationSucceeded(eq("VerificationSid"), any(), eq("registration"));
}
verifyNoInteractions(registrationServiceClient);
}
@@ -1199,7 +923,6 @@ class AccountControllerTest {
verify(accountsManager).create(eq(SENDER), eq("bar"), any(), any(), anyList());
verify(registrationServiceClient).checkVerificationCode(sessionId, "1234", AccountController.REGISTRATION_RPC_TIMEOUT);
verifyNoInteractions(smsSender);
}
@Test
@@ -1225,7 +948,7 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(403);
verifyNoMoreInteractions(accountsManager);
verifyNoInteractions(accountsManager);
}
@Test
@@ -1240,7 +963,7 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(403);
verifyNoMoreInteractions(accountsManager);
verifyNoInteractions(accountsManager);
}
@Test
@@ -1266,7 +989,6 @@ class AccountControllerTest {
verify(registrationServiceClient).checkVerificationCode(sessionId, "1111", AccountController.REGISTRATION_RPC_TIMEOUT);
verifyNoInteractions(accountsManager);
verifyNoInteractions(smsSender);
}
@Test
@@ -1320,7 +1042,7 @@ class AccountControllerTest {
assertThat(result.getUuid()).isNotNull();
verifyNoMoreInteractions(pinLimiter);
verifyNoInteractions(pinLimiter);
} finally {
when(senderRegLockAccount.getRegistrationLock()).thenReturn(lock);
}
@@ -1361,7 +1083,7 @@ class AccountControllerTest {
assertThat(failure.getBackupCredentials().getPassword().startsWith(SENDER_REG_LOCK_UUID.toString())).isTrue();
assertThat(failure.getTimeRemaining()).isGreaterThan(0);
verifyNoMoreInteractions(pinLimiter);
verifyNoInteractions(pinLimiter);
}
@Test
@@ -1411,55 +1133,14 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
void testVerifyTestDeviceNumber() throws Exception {
when(pendingAccountsManager.getCodeForNumber(TEST_NUMBER)).thenReturn(Optional.of(
new StoredVerificationCode(Integer.toString(TEST_VERIFICATION_CODE), System.currentTimeMillis(), "push", null, null)));
final Response response = resources.getJerseyTest()
.target(String.format("/v1/accounts/code/%s", TEST_VERIFICATION_CODE))
.request()
.header("Authorization", AuthHelper.getProvisioningAuthHeader(TEST_NUMBER, "bar"))
.put(Entity.entity(new AccountAttributes(), MediaType.APPLICATION_JSON_TYPE));
verify(accountsManager).create(eq(TEST_NUMBER), eq("bar"), any(), any(), anyList());
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
void testChangePhoneNumber() throws Exception {
final String number = "+18005559876";
final String code = "987654";
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(
new StoredVerificationCode(code, System.currentTimeMillis(), "push", null, null)));
final AccountIdentityResponse accountIdentityResponse =
resources.getJerseyTest()
.target("/v1/accounts/number")
.request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.put(Entity.entity(new ChangePhoneNumberRequest(number, code, null, null, null, null, null),
MediaType.APPLICATION_JSON_TYPE), AccountIdentityResponse.class);
verify(changeNumberManager).changeNumber(eq(AuthHelper.VALID_ACCOUNT), eq(number), any(), any(), any(), any());
assertThat(accountIdentityResponse.getUuid()).isEqualTo(AuthHelper.VALID_UUID);
assertThat(accountIdentityResponse.getNumber()).isEqualTo(number);
assertThat(accountIdentityResponse.getPni()).isNotEqualTo(AuthHelper.VALID_PNI);
}
@Test
void testChangePhoneNumberWithRegistrationService() throws Exception {
final String number = "+18005559876";
final String code = "987654";
final byte[] sessionId = "session".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(
new StoredVerificationCode(code, System.currentTimeMillis(), "push", null, sessionId)));
new StoredVerificationCode(null, System.currentTimeMillis(), "push", null, sessionId)));
when(registrationServiceClient.checkVerificationCode(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));
@@ -1557,26 +1238,6 @@ class AccountControllerTest {
void testChangePhoneNumberIncorrectCode() throws Exception {
final String number = "+18005559876";
final String code = "987654";
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(
new StoredVerificationCode(code, System.currentTimeMillis(), "push", null, null)));
final Response response =
resources.getJerseyTest()
.target("/v1/accounts/number")
.request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.put(Entity.entity(new ChangePhoneNumberRequest(number, code + "-incorrect", null, null, null, null, null),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(403);
verify(changeNumberManager, never()).changeNumber(any(), any(), any(), any(), any(), any());
}
@Test
void testChangePhoneNumberIncorrectCodeWithRegistrationService() throws Exception {
final String number = "+18005559876";
final String code = "987654";
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(
@@ -1603,9 +1264,13 @@ class AccountControllerTest {
void testChangePhoneNumberExistingAccountReglockNotRequired() throws Exception {
final String number = "+18005559876";
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", null, null)));
new StoredVerificationCode(code, System.currentTimeMillis(), "push", null, sessionId)));
when(registrationServiceClient.checkVerificationCode(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));
final StoredRegistrationLock existingRegistrationLock = mock(StoredRegistrationLock.class);
when(existingRegistrationLock.requiresClientRegistrationLock()).thenReturn(false);
@@ -1633,9 +1298,13 @@ class AccountControllerTest {
void testChangePhoneNumberExistingAccountReglockRequiredNotProvided() throws Exception {
final String number = "+18005559876";
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", null, null)));
new StoredVerificationCode(code, System.currentTimeMillis(), "push", null, sessionId)));
when(registrationServiceClient.checkVerificationCode(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));
final StoredRegistrationLock existingRegistrationLock = mock(StoredRegistrationLock.class);
when(existingRegistrationLock.requiresClientRegistrationLock()).thenReturn(true);
@@ -1664,9 +1333,13 @@ class AccountControllerTest {
final String number = "+18005559876";
final String code = "987654";
final String reglock = "setec-astronomy";
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(
new StoredVerificationCode(code, System.currentTimeMillis(), "push", null, null)));
new StoredVerificationCode(null, System.currentTimeMillis(), "push", null, sessionId)));
when(registrationServiceClient.checkVerificationCode(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));
final StoredRegistrationLock existingRegistrationLock = mock(StoredRegistrationLock.class);
when(existingRegistrationLock.requiresClientRegistrationLock()).thenReturn(true);
@@ -1696,9 +1369,13 @@ class AccountControllerTest {
final String number = "+18005559876";
final String code = "987654";
final String reglock = "setec-astronomy";
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(
new StoredVerificationCode(code, System.currentTimeMillis(), "push", null, null)));
new StoredVerificationCode(null, System.currentTimeMillis(), "push", null, sessionId)));
when(registrationServiceClient.checkVerificationCode(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));
final StoredRegistrationLock existingRegistrationLock = mock(StoredRegistrationLock.class);
when(existingRegistrationLock.requiresClientRegistrationLock()).thenReturn(true);
@@ -1728,6 +1405,7 @@ class AccountControllerTest {
final String number = "+18005559876";
final String code = "987654";
final String pniIdentityKey = "changed-pni-identity-key";
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
Device device2 = mock(Device.class);
when(device2.getId()).thenReturn(2L);
@@ -1744,7 +1422,10 @@ class AccountControllerTest {
when(AuthHelper.VALID_ACCOUNT.getDevice(3L)).thenReturn(Optional.of(device3));
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(
new StoredVerificationCode(code, System.currentTimeMillis(), "push", null, null)));
new StoredVerificationCode(null, System.currentTimeMillis(), "push", null, sessionId)));
when(registrationServiceClient.checkVerificationCode(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(true));
var deviceMessages = List.of(
new IncomingMessage(1, 2, 2, "content2"),
@@ -2104,7 +1785,8 @@ class AccountControllerTest {
@ParameterizedTest
@MethodSource
void testSignupCaptcha(final String message, final boolean enforced, final Set<String> countryCodes, final int expectedResponseStatusCode) {
void testSignupCaptcha(final String message, final boolean enforced, final Set<String> countryCodes, final int expectedResponseStatusCode)
throws NumberParseException {
DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
when(dynamicConfigurationManager.getConfiguration())
.thenReturn(dynamicConfiguration);
@@ -2114,6 +1796,9 @@ class AccountControllerTest {
when(dynamicConfiguration.getCaptchaConfiguration())
.thenReturn(signupCaptchaConfig);
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(new byte[16]));
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
@@ -2124,8 +1809,10 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(expectedResponseStatusCode);
verify(smsSender, 200 == expectedResponseStatusCode ? times(1) : never())
.deliverSmsVerification(eq(SENDER), eq(Optional.empty()), anyString());
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER, null);
verify(registrationServiceClient, 200 == expectedResponseStatusCode ? times(1) : never())
.sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
}
static Stream<Arguments> testSignupCaptcha() {

View File

@@ -1,89 +0,0 @@
package org.whispersystems.textsecuregcm.sms;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import java.util.Locale.LanguageRange;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.textsecuregcm.configuration.VoiceVerificationConfiguration;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
class TwilioVerifyExperimentEnrollmentManagerTest {
private final ExperimentEnrollmentManager experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class);
private final VoiceVerificationConfiguration voiceVerificationConfiguration = mock(VoiceVerificationConfiguration.class);
private TwilioVerifyExperimentEnrollmentManager manager;
private static final String NUMBER = "+15055551212";
private static final Optional<String> INELIGIBLE_CLIENT = Optional.of("android-2020-01");
private static final Optional<String> ELIGIBLE_CLIENT = Optional.of("anything");
private static final List<LanguageRange> LANGUAGE_ONLY_SUPPORTED_BY_SIGNAL = LanguageRange.parse("am");
private static final List<LanguageRange> LANGUAGE_NOT_SUPPORTED_BY_SIGNAL_OR_TWILIO = LanguageRange.parse("xx");
private static final List<LanguageRange> LANGUAGE_SUPPORTED_BY_TWILIO = LanguageRange.parse("fr-CA");
@BeforeEach
void setup() {
when(voiceVerificationConfiguration.getLocales())
.thenReturn(Set.of("am", "en-US", "fr-CA"));
manager = new TwilioVerifyExperimentEnrollmentManager(
voiceVerificationConfiguration,
experimentEnrollmentManager);
}
@ParameterizedTest
@MethodSource
void testIsEnrolled(String message, boolean expected, Optional<String> clientType, String number,
List<LanguageRange> languageRanges, String transport, boolean managerResponse) {
when(experimentEnrollmentManager.isEnrolled(number, TwilioVerifyExperimentEnrollmentManager.EXPERIMENT_NAME))
.thenReturn(managerResponse);
assertEquals(expected, manager.isEnrolled(clientType, number, languageRanges, transport), message);
}
static Stream<Arguments> testIsEnrolled() {
return Stream.of(
Arguments.of("ineligible client", false, INELIGIBLE_CLIENT, NUMBER, Collections.emptyList(), "sms", true),
Arguments
.of("ineligible client", false, Optional.of("android-ng"), NUMBER, Collections.emptyList(), "sms", true),
Arguments
.of("client, language, and manager all agree on enrollment", true, ELIGIBLE_CLIENT, NUMBER,
LANGUAGE_SUPPORTED_BY_TWILIO,
"sms", true),
Arguments
.of("enrolled: ineligible language doesnt matter with sms", true, ELIGIBLE_CLIENT, NUMBER,
LANGUAGE_ONLY_SUPPORTED_BY_SIGNAL, "sms",
true),
Arguments
.of("not enrolled: language only supported by Signal is preferred", false, ELIGIBLE_CLIENT, NUMBER, List.of(
LANGUAGE_ONLY_SUPPORTED_BY_SIGNAL.get(0), LANGUAGE_SUPPORTED_BY_TWILIO.get(0)), "voice", true),
Arguments.of("enrolled: preferred language is supported", true, ELIGIBLE_CLIENT, NUMBER, List.of(
LANGUAGE_SUPPORTED_BY_TWILIO.get(0), LANGUAGE_ONLY_SUPPORTED_BY_SIGNAL
.get(0)), "voice", true),
Arguments
.of("enrolled: preferred (and only) language is not supported by Signal or Twilio", true, ELIGIBLE_CLIENT,
NUMBER, LANGUAGE_NOT_SUPPORTED_BY_SIGNAL_OR_TWILIO, "voice", true),
Arguments.of("not enrolled: preferred language (and only) is only supported by Siganl", false, ELIGIBLE_CLIENT,
NUMBER, LANGUAGE_ONLY_SUPPORTED_BY_SIGNAL, "voice", true)
);
}
}

View File

@@ -1,248 +0,0 @@
/*
* Copyright 2021-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.sms;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import java.net.http.HttpClient;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Locale.LanguageRange;
import java.util.Optional;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient;
import org.whispersystems.textsecuregcm.util.ExecutorUtils;
@SuppressWarnings("OptionalGetWithoutIsPresent")
class TwilioVerifySenderTest {
private static final String ACCOUNT_ID = "test_account_id";
private static final String ACCOUNT_TOKEN = "test_account_token";
private static final String MESSAGING_SERVICE_SID = "test_messaging_services_id";
private static final String NANPA_MESSAGING_SERVICE_SID = "nanpa_test_messaging_service_id";
private static final String VERIFY_SERVICE_SID = "verify_service_sid";
private static final String LOCAL_DOMAIN = "test.com";
private static final String ANDROID_APP_HASH = "someHash";
private static final String SERVICE_FRIENDLY_NAME = "SignalTest";
private static final String VERIFICATION_SID = "verification";
@RegisterExtension
private final WireMockExtension wireMock = WireMockExtension.newInstance()
.options(wireMockConfig().dynamicPort().dynamicHttpsPort())
.build();
private TwilioVerifySender sender;
@BeforeEach
void setup() {
final TwilioConfiguration twilioConfiguration = createTwilioConfiguration();
final FaultTolerantHttpClient httpClient = FaultTolerantHttpClient.newBuilder()
.withCircuitBreaker(twilioConfiguration.getCircuitBreaker())
.withRetry(twilioConfiguration.getRetry())
.withVersion(HttpClient.Version.HTTP_2)
.withConnectTimeout(Duration.ofSeconds(10))
.withRedirect(HttpClient.Redirect.NEVER)
.withExecutor(ExecutorUtils.newFixedThreadBoundedQueueExecutor(10, 100))
.withName("twilio")
.build();
sender = new TwilioVerifySender("http://localhost:" + wireMock.getPort(), httpClient, twilioConfiguration);
}
private TwilioConfiguration createTwilioConfiguration() {
TwilioConfiguration configuration = new TwilioConfiguration();
configuration.setAccountId(ACCOUNT_ID);
configuration.setAccountToken(ACCOUNT_TOKEN);
configuration.setMessagingServiceSid(MESSAGING_SERVICE_SID);
configuration.setNanpaMessagingServiceSid(NANPA_MESSAGING_SERVICE_SID);
configuration.setVerifyServiceSid(VERIFY_SERVICE_SID);
configuration.setLocalDomain(LOCAL_DOMAIN);
configuration.setAndroidAppHash(ANDROID_APP_HASH);
configuration.setVerifyServiceFriendlyName(SERVICE_FRIENDLY_NAME);
return configuration;
}
private void setupSuccessStubForVerify() {
wireMock.stubFor(post(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications"))
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody("{\"sid\": \"" + VERIFICATION_SID + "\", \"status\": \"pending\"}")));
}
@ParameterizedTest
@MethodSource
void deliverSmsVerificationWithVerify(@Nullable final String client, @Nullable final String languageRange,
final boolean expectAppHash, @Nullable final String expectedLocale) throws Exception {
setupSuccessStubForVerify();
List<LanguageRange> languageRanges = Optional.ofNullable(languageRange)
.map(LanguageRange::parse)
.orElse(Collections.emptyList());
final Optional<String> verificationSid = sender
.deliverSmsVerificationWithVerify("+14153333333", Optional.ofNullable(client), "123456",
languageRanges).get();
assertEquals(VERIFICATION_SID, verificationSid.get());
wireMock.verify(1, postRequestedFor(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo(
(expectedLocale == null ? "" : "Locale=" + expectedLocale + "&")
+ "Channel=sms&To=%2B14153333333&CustomFriendlyName=" + SERVICE_FRIENDLY_NAME
+ "&CustomCode=123456" + (expectAppHash ? "&AppHash=" + ANDROID_APP_HASH : "")
)));
}
@SuppressWarnings("unused")
private static Stream<Arguments> deliverSmsVerificationWithVerify() {
return Stream.of(
// client, languageRange, expectAppHash, expectedLocale
Arguments.of("ios", "fr-CA, en", false, "fr"),
Arguments.of("android-2021-03", "zh-HK, it", true, "zh-HK"),
Arguments.of(null, null, false, null)
);
}
@ParameterizedTest
@MethodSource
void deliverVoxVerificationWithVerify(@Nullable final String languageRange,
@Nullable final String expectedLocale) throws Exception {
setupSuccessStubForVerify();
final List<LanguageRange> languageRanges = Optional.ofNullable(languageRange)
.map(LanguageRange::parse)
.orElse(Collections.emptyList());
final Optional<String> verificationSid = sender
.deliverVoxVerificationWithVerify("+14153333333", "123456", languageRanges).get();
assertEquals(VERIFICATION_SID, verificationSid.get());
wireMock.verify(1, postRequestedFor(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo(
(expectedLocale == null ? "" : "Locale=" + expectedLocale + "&")
+ "Channel=call&To=%2B14153333333&CustomFriendlyName=" + SERVICE_FRIENDLY_NAME
+ "&CustomCode=123456")));
}
@SuppressWarnings("unused")
private static Stream<Arguments> deliverVoxVerificationWithVerify() {
return Stream.of(
// languageRange, expectedLocale
Arguments.of("fr-CA, en", "fr"),
Arguments.of("zh-HK, it", "zh-HK"),
Arguments.of("en-CAA, en", "en"),
Arguments.of(null, null)
);
}
@Test
void testSmsFiveHundred() throws Exception {
wireMock.stubFor(post(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications"))
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
.willReturn(aResponse()
.withStatus(500)
.withHeader("Content-Type", "application/json")
.withBody("{\"message\": \"Server error!\"}")));
final Optional<String> verificationSid = sender
.deliverSmsVerificationWithVerify("+14153333333", Optional.empty(), "123456", Collections.emptyList()).get();
assertThat(verificationSid).isEmpty();
wireMock.verify(3, postRequestedFor(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("Channel=sms&To=%2B14153333333&CustomFriendlyName=" + SERVICE_FRIENDLY_NAME
+ "&CustomCode=123456")));
}
@Test
void testVoxFiveHundred() throws Exception {
wireMock.stubFor(post(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications"))
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
.willReturn(aResponse()
.withStatus(500)
.withHeader("Content-Type", "application/json")
.withBody("{\"message\": \"Server error!\"}")));
final Optional<String> verificationSid = sender
.deliverVoxVerificationWithVerify("+14153333333", "123456", Collections.emptyList()).get();
assertThat(verificationSid).isEmpty();
wireMock.verify(3, postRequestedFor(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("Channel=call&To=%2B14153333333&CustomFriendlyName=" + SERVICE_FRIENDLY_NAME
+ "&CustomCode=123456")));
}
@Test
void reportVerificationSucceeded() throws Exception {
wireMock.stubFor(post(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications/" + VERIFICATION_SID))
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("{\"status\": \"approved\", \"sid\": \"" + VERIFICATION_SID + "\"}")));
final Boolean success = sender.reportVerificationSucceeded(VERIFICATION_SID, null, "test").get();
assertThat(success).isTrue();
wireMock.verify(1,
postRequestedFor(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications/" + VERIFICATION_SID))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("Status=approved")));
}
@Test
void reportVerificationFailed() throws Exception {
wireMock.stubFor(post(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications/" + VERIFICATION_SID))
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
.willReturn(aResponse()
.withStatus(404)
.withHeader("Content-Type", "application/json")
.withBody("{\"status\": 404, \"code\": 20404}")));
final Boolean success = sender.reportVerificationSucceeded(VERIFICATION_SID, null, "test").get();
assertThat(success).isFalse();
wireMock.verify(1,
postRequestedFor(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications/" + VERIFICATION_SID))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("Status=approved")));
}
}

View File

@@ -1,42 +0,0 @@
package org.whispersystems.textsecuregcm.tests.sms;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.whispersystems.textsecuregcm.sms.SmsSender;
import org.whispersystems.textsecuregcm.sms.TwilioSmsSender;
class SmsSenderTest {
private static final String NON_MEXICO_NUMBER = "+12345678901";
private static final String MEXICO_NON_MOBILE_NUMBER = "+52234567890";
private static final String MEXICO_MOBILE_NUMBER = "+52123456789";
private final TwilioSmsSender twilioSmsSender = mock(TwilioSmsSender.class);
private final SmsSender smsSender = new SmsSender(twilioSmsSender);
@Test
void testDeliverSmsVerificationNonMexico() {
smsSender.deliverSmsVerification(NON_MEXICO_NUMBER, Optional.empty(), "");
verify(twilioSmsSender, times(1))
.deliverSmsVerification(NON_MEXICO_NUMBER, Optional.empty(), "");
}
@Test
void testDeliverSmsVerificationMexicoNonMobile() {
smsSender.deliverSmsVerification(MEXICO_NON_MOBILE_NUMBER, Optional.empty(), "");
verify(twilioSmsSender, times(1))
.deliverSmsVerification("+521" + MEXICO_NON_MOBILE_NUMBER.substring("+52".length()), Optional.empty(), "");
}
@Test
void testDeliverSmsVerificationMexicoMobile() {
smsSender.deliverSmsVerification(MEXICO_MOBILE_NUMBER, Optional.empty(), "");
verify(twilioSmsSender, times(1))
.deliverSmsVerification(MEXICO_MOBILE_NUMBER, Optional.empty(), "");
}
}

View File

@@ -1,291 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.tests.sms;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import java.util.List;
import java.util.Locale.LanguageRange;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nonnull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
import org.whispersystems.textsecuregcm.configuration.TwilioVerificationTextConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicTwilioConfiguration;
import org.whispersystems.textsecuregcm.sms.TwilioSmsSender;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
class TwilioSmsSenderTest {
private static final String ACCOUNT_ID = "test_account_id";
private static final String ACCOUNT_TOKEN = "test_account_token";
private static final String MESSAGING_SERVICE_SID = "test_messaging_services_id";
private static final String NANPA_MESSAGING_SERVICE_SID = "nanpa_test_messaging_service_id";
private static final String VERIFY_SERVICE_SID = "verify_service_sid";
private static final String LOCAL_DOMAIN = "test.com";
@RegisterExtension
private final WireMockExtension wireMock = WireMockExtension.newInstance()
.options(wireMockConfig().dynamicPort().dynamicHttpsPort())
.build();
private DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
private TwilioSmsSender sender;
@BeforeEach
void setup() {
dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
DynamicConfiguration dynamicConfiguration = new DynamicConfiguration();
DynamicTwilioConfiguration dynamicTwilioConfiguration = new DynamicTwilioConfiguration();
dynamicConfiguration.setTwilioConfiguration(dynamicTwilioConfiguration);
dynamicTwilioConfiguration.setNumbers(List.of("+14151111111", "+14152222222"));
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
TwilioConfiguration configuration = createTwilioConfiguration();
sender = new TwilioSmsSender("http://localhost:" + wireMock.getPort(), "http://localhost:11111", configuration, dynamicConfigurationManager);
}
@Nonnull
private TwilioConfiguration createTwilioConfiguration() {
TwilioConfiguration configuration = new TwilioConfiguration();
configuration.setAccountId(ACCOUNT_ID);
configuration.setAccountToken(ACCOUNT_TOKEN);
configuration.setMessagingServiceSid(MESSAGING_SERVICE_SID);
configuration.setNanpaMessagingServiceSid(NANPA_MESSAGING_SERVICE_SID);
configuration.setVerifyServiceSid(VERIFY_SERVICE_SID);
configuration.setLocalDomain(LOCAL_DOMAIN);
configuration.setDefaultClientVerificationTexts(createTwlilioVerificationText(""));
configuration.setRegionalClientVerificationTexts(
Map.of("33", createTwlilioVerificationText("[33] "))
);
configuration.setAndroidAppHash("someHash");
return configuration;
}
private TwilioVerificationTextConfiguration createTwlilioVerificationText(final String prefix) {
TwilioVerificationTextConfiguration verificationTextConfiguration = new TwilioVerificationTextConfiguration();
verificationTextConfiguration.setIosText(prefix + "Verify on iOS: %1$s\n\nsomelink://verify/%1$s");
verificationTextConfiguration.setAndroidNgText(prefix + "<#> Verify on AndroidNg: %1$s\n\ncharacters");
verificationTextConfiguration.setAndroid202001Text(prefix + "Verify on Android202001: %1$s\n\nsomelink://verify/%1$s\n\ncharacters");
verificationTextConfiguration.setAndroid202103Text(prefix + "Verify on Android202103: %1$s\n\ncharacters");
verificationTextConfiguration.setGenericText(prefix + "Verify on whatever: %1$s");
return verificationTextConfiguration;
}
private void setupSuccessStubForSms() {
wireMock.stubFor(post(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody("{\"price\": -0.00750, \"status\": \"sent\"}")));
}
@Test
void testSendSms() {
setupSuccessStubForSms();
boolean success = sender.deliverSmsVerification("+14153333333", Optional.of("android-ng"), "123-456").join();
assertThat(success).isTrue();
wireMock.verify(1, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("MessagingServiceSid=nanpa_test_messaging_service_id&To=%2B14153333333&Body=%3C%23%3E+Verify+on+AndroidNg%3A+123-456%0A%0Acharacters")));
}
@Test
void testSendSmsAndroid202001() {
setupSuccessStubForSms();
boolean success = sender.deliverSmsVerification("+14153333333", Optional.of("android-2020-01"), "123-456").join();
assertThat(success).isTrue();
wireMock.verify(1, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("MessagingServiceSid=nanpa_test_messaging_service_id&To=%2B14153333333&Body=Verify+on+Android202001%3A+123-456%0A%0Asomelink%3A%2F%2Fverify%2F123-456%0A%0Acharacters")));
}
@Test
void testSendSmsAndroid202103() {
setupSuccessStubForSms();
boolean success = sender.deliverSmsVerification("+14153333333", Optional.of("android-2021-03"), "123456").join();
assertThat(success).isTrue();
wireMock.verify(1, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("MessagingServiceSid=nanpa_test_messaging_service_id&To=%2B14153333333&Body=Verify+on+Android202103%3A+123456%0A%0Acharacters")));
}
@Test
void testSendSmsNanpaMessagingService() {
setupSuccessStubForSms();
TwilioConfiguration configuration = createTwilioConfiguration();
configuration.setNanpaMessagingServiceSid(NANPA_MESSAGING_SERVICE_SID);
TwilioSmsSender sender = new TwilioSmsSender("http://localhost:" + wireMock.getPort(),
"http://localhost:11111", configuration, dynamicConfigurationManager);
assertThat(sender.deliverSmsVerification("+14153333333", Optional.of("ios"), "654-321").join()).isTrue();
wireMock.verify(1, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("MessagingServiceSid=nanpa_test_messaging_service_id&To=%2B14153333333&Body=Verify+on+iOS%3A+654-321%0A%0Asomelink%3A%2F%2Fverify%2F654-321")));
wireMock.resetRequests();
assertThat(sender.deliverSmsVerification("+447911123456", Optional.of("ios"), "654-321").join()).isTrue();
wireMock.verify(1, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("MessagingServiceSid=test_messaging_services_id&To=%2B447911123456&Body=Verify+on+iOS%3A+654-321%0A%0Asomelink%3A%2F%2Fverify%2F654-321")));
}
@Test
void testSendVox() {
wireMock.stubFor(post(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Calls.json"))
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody("{\"price\": -0.00750, \"status\": \"completed\"}")));
boolean success = sender.deliverVoxVerification("+14153333333", "123-456", LanguageRange.parse("en-US")).join();
assertThat(success).isTrue();
wireMock.verify(1, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Calls.json"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(matching("To=%2B14153333333&From=%2B1415(1111111|2222222)&Url=https%3A%2F%2Ftest.com%2Fv1%2Fvoice%2Fdescription%2F123-456%3Fl%3Den-US")));
}
@Test
void testSendVoxMultipleLocales() {
wireMock.stubFor(post(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Calls.json"))
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody("{\"price\": -0.00750, \"status\": \"completed\"}")));
boolean success = sender.deliverVoxVerification("+14153333333", "123-456", LanguageRange.parse("en-US;q=1, ar-US;q=0.9, fa-US;q=0.8, zh-Hans-US;q=0.7, ru-RU;q=0.6, zh-Hant-US;q=0.5")).join();
assertThat(success).isTrue();
wireMock.verify(1, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Calls.json"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(matching("To=%2B14153333333&From=%2B1415(1111111|2222222)&Url=https%3A%2F%2Ftest.com%2Fv1%2Fvoice%2Fdescription%2F123-456%3Fl%3Den-US%26l%3Dar-US%26l%3Dfa-US%26l%3Dzh-US%26l%3Dru-RU%26l%3Dzh-US")));
}
@Test
void testSendSmsFiveHundred() {
wireMock.stubFor(post(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
.willReturn(aResponse()
.withStatus(500)
.withHeader("Content-Type", "application/json")
.withBody("{\"message\": \"Server error!\"}")));
boolean success = sender.deliverSmsVerification("+14153333333", Optional.of("android-ng"), "123-456").join();
assertThat(success).isFalse();
wireMock.verify(3, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("MessagingServiceSid=nanpa_test_messaging_service_id&To=%2B14153333333&Body=%3C%23%3E+Verify+on+AndroidNg%3A+123-456%0A%0Acharacters")));
}
@Test
void testSendVoxFiveHundred() {
wireMock.stubFor(post(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Calls.json"))
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
.willReturn(aResponse()
.withStatus(500)
.withHeader("Content-Type", "application/json")
.withBody("{\"message\": \"Server error!\"}")));
boolean success = sender.deliverVoxVerification("+14153333333", "123-456", LanguageRange.parse("en-US")).join();
assertThat(success).isFalse();
wireMock.verify(3, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Calls.json"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(matching("To=%2B14153333333&From=%2B1415(1111111|2222222)&Url=https%3A%2F%2Ftest.com%2Fv1%2Fvoice%2Fdescription%2F123-456%3Fl%3Den-US")));
}
@Test
void testSendSmsNetworkFailure() {
TwilioConfiguration configuration = createTwilioConfiguration();
TwilioSmsSender sender = new TwilioSmsSender("http://localhost:" + 39873, "http://localhost:" + 39873, configuration, dynamicConfigurationManager);
boolean success = sender.deliverSmsVerification("+14153333333", Optional.of("android-ng"), "123-456").join();
assertThat(success).isFalse();
}
@Test
void testRetrySmsOnUnreachableErrorCodeIsTriedOnlyOnceWithoutSenderId() {
wireMock.stubFor(post(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
.willReturn(aResponse()
.withStatus(400)
.withHeader("Content-Type", "application/json")
.withBody("{\"status\": 400, \"message\": \"is not currently reachable\", \"code\": 21612}")));
boolean success = sender.deliverSmsVerification("+14153333333", Optional.of("android-ng"), "123-456").join();
assertThat(success).isFalse();
wireMock.verify(1, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("MessagingServiceSid=nanpa_test_messaging_service_id&To=%2B14153333333&Body=%3C%23%3E+Verify+on+AndroidNg%3A+123-456%0A%0Acharacters")));
}
@Test
void testSendSmsChina() {
setupSuccessStubForSms();
boolean success = sender.deliverSmsVerification("+861065529988", Optional.of("android-ng"), "123-456").join();
assertThat(success).isTrue();
wireMock.verify(1, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("MessagingServiceSid=test_messaging_services_id&To=%2B861065529988&Body=%3C%23%3E+Verify+on+AndroidNg%3A+123-456%0A%0Acharacters%E2%80%88")));
}
@Test
void testSendSmsRegionalVerificationText() {
setupSuccessStubForSms();
boolean success = sender.deliverSmsVerification("+33655512673", Optional.of("android-ng"), "123-456").join();
assertThat(success).isTrue();
wireMock.verify(1, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("MessagingServiceSid=test_messaging_services_id&To=%2B33655512673&Body=%5B33%5D+%3C%23%3E+Verify+on+AndroidNg%3A+123-456%0A%0Acharacters")));
}
}