diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java index 16a41c208..8f5e10c71 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java @@ -17,7 +17,33 @@ package org.whispersystems.textsecuregcm; import com.fasterxml.jackson.annotation.JsonProperty; -import org.whispersystems.textsecuregcm.configuration.*; +import io.dropwizard.Configuration; +import io.dropwizard.client.JerseyClientConfiguration; +import org.whispersystems.textsecuregcm.configuration.AccountDatabaseCrawlerConfiguration; +import org.whispersystems.textsecuregcm.configuration.ApnConfiguration; +import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguration; +import org.whispersystems.textsecuregcm.configuration.CdnConfiguration; +import org.whispersystems.textsecuregcm.configuration.DatabaseConfiguration; +import org.whispersystems.textsecuregcm.configuration.DirectoryConfiguration; +import org.whispersystems.textsecuregcm.configuration.GcmConfiguration; +import org.whispersystems.textsecuregcm.configuration.GcpAttachmentsConfiguration; +import org.whispersystems.textsecuregcm.configuration.MaxDeviceConfiguration; +import org.whispersystems.textsecuregcm.configuration.MessageCacheConfiguration; +import org.whispersystems.textsecuregcm.configuration.MicrometerConfiguration; +import org.whispersystems.textsecuregcm.configuration.PushConfiguration; +import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration; +import org.whispersystems.textsecuregcm.configuration.RecaptchaConfiguration; +import org.whispersystems.textsecuregcm.configuration.RedisClusterConfiguration; +import org.whispersystems.textsecuregcm.configuration.RedisConfiguration; +import org.whispersystems.textsecuregcm.configuration.RemoteConfigConfiguration; +import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration; +import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; +import org.whispersystems.textsecuregcm.configuration.TestDeviceConfiguration; +import org.whispersystems.textsecuregcm.configuration.TurnConfiguration; +import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration; +import org.whispersystems.textsecuregcm.configuration.UnidentifiedDeliveryConfiguration; +import org.whispersystems.textsecuregcm.configuration.VoiceVerificationConfiguration; +import org.whispersystems.textsecuregcm.configuration.ZkConfig; import org.whispersystems.websocket.configuration.WebSocketConfiguration; import javax.validation.Valid; @@ -27,9 +53,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import io.dropwizard.Configuration; -import io.dropwizard.client.JerseyClientConfiguration; - /** @noinspection MismatchedQueryAndUpdateOfCollection, WeakerAccess */ public class WhisperServerConfiguration extends Configuration { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/TwilioConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/TwilioConfiguration.java index daa8fbf07..2c34f7417 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/TwilioConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/TwilioConfiguration.java @@ -51,6 +51,18 @@ public class TwilioConfiguration { @Valid private TwilioSenderIdConfiguration senderId = new TwilioSenderIdConfiguration(); + @NotEmpty + private String iosVerificationText; + + @NotEmpty + private String androidNgVerificationText; + + @NotEmpty + private String android202001VerificationText; + + @NotEmpty + private String genericVerificationText; + public String getAccountId() { return accountId; } @@ -122,4 +134,40 @@ public class TwilioConfiguration { public void setSenderId(TwilioSenderIdConfiguration senderId) { this.senderId = senderId; } + + public String getIosVerificationText() { + return iosVerificationText; + } + + @VisibleForTesting + public void setIosVerificationText(String iosVerificationText) { + this.iosVerificationText = iosVerificationText; + } + + public String getAndroidNgVerificationText() { + return androidNgVerificationText; + } + + @VisibleForTesting + public void setAndroidNgVerificationText(String androidNgVerificationText) { + this.androidNgVerificationText = androidNgVerificationText; + } + + public String getAndroid202001VerificationText() { + return android202001VerificationText; + } + + @VisibleForTesting + public void setAndroid202001VerificationText(String android202001VerificationText) { + this.android202001VerificationText = android202001VerificationText; + } + + public String getGenericVerificationText() { + return genericVerificationText; + } + + @VisibleForTesting + public void setGenericVerificationText(String genericVerificationText) { + this.genericVerificationText = genericVerificationText; + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/sms/SmsSender.java b/service/src/main/java/org/whispersystems/textsecuregcm/sms/SmsSender.java index dfe6ec25a..4c0a119d9 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/sms/SmsSender.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/sms/SmsSender.java @@ -17,22 +17,13 @@ package org.whispersystems.textsecuregcm.sms; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.Optional; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class SmsSender { - - static final String SMS_IOS_VERIFICATION_TEXT = "Your Signal verification code: %s\n\nOr tap: sgnl://verify/%s"; - static final String SMS_ANDROID_NG_VERIFICATION_TEXT = "<#> Your Signal verification code: %s\n\ndoDiFGKPO1r"; - static final String SMS_VERIFICATION_TEXT = "Your Signal verification code: %s"; - private final TwilioSmsSender twilioSender; - public SmsSender(TwilioSmsSender twilioSender) - { + public SmsSender(TwilioSmsSender twilioSender) { this.twilioSender = twilioSender; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java b/service/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java index c78a2041a..f4a86367c 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java @@ -33,6 +33,7 @@ import org.whispersystems.textsecuregcm.util.ExecutorUtils; import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.Util; +import javax.annotation.Nullable; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; @@ -41,6 +42,7 @@ import java.net.http.HttpResponse; import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Random; @@ -67,6 +69,10 @@ public class TwilioSmsSender { private final String localDomain; private final SenderIdSupplier senderIdSupplier; private final Random random; + private final String androidNgVerificationText; + private final String android202001VerificationText; + private final String iosVerificationText; + private final String genericVerificationText; private final FaultTolerantHttpClient httpClient; private final URI smsUri; @@ -76,24 +82,28 @@ public class TwilioSmsSender { public TwilioSmsSender(String baseUri, TwilioConfiguration twilioConfiguration) { Executor executor = ExecutorUtils.newFixedThreadBoundedQueueExecutor(10, 100); - this.accountId = twilioConfiguration.getAccountId(); - this.accountToken = twilioConfiguration.getAccountToken(); - this.numbers = new ArrayList<>(twilioConfiguration.getNumbers()); - this.localDomain = twilioConfiguration.getLocalDomain(); - this.messagingServicesId = twilioConfiguration.getMessagingServicesId(); - this.senderIdSupplier = new SenderIdSupplier(twilioConfiguration.getSenderId()); - this.random = new Random(System.currentTimeMillis()); - this.smsUri = URI.create(baseUri + "/2010-04-01/Accounts/" + accountId + "/Messages.json"); - this.voxUri = URI.create(baseUri + "/2010-04-01/Accounts/" + accountId + "/Calls.json" ); - this.httpClient = FaultTolerantHttpClient.newBuilder() - .withCircuitBreaker(twilioConfiguration.getCircuitBreaker()) - .withRetry(twilioConfiguration.getRetry()) - .withVersion(HttpClient.Version.HTTP_2) - .withConnectTimeout(Duration.ofSeconds(10)) - .withRedirect(HttpClient.Redirect.NEVER) - .withExecutor(executor) - .withName("twilio") - .build(); + this.accountId = twilioConfiguration.getAccountId(); + this.accountToken = twilioConfiguration.getAccountToken(); + this.numbers = new ArrayList<>(twilioConfiguration.getNumbers()); + this.localDomain = twilioConfiguration.getLocalDomain(); + this.messagingServicesId = twilioConfiguration.getMessagingServicesId(); + this.senderIdSupplier = new SenderIdSupplier(twilioConfiguration.getSenderId()); + this.random = new Random(System.currentTimeMillis()); + this.androidNgVerificationText = twilioConfiguration.getAndroidNgVerificationText(); + this.android202001VerificationText = twilioConfiguration.getAndroid202001VerificationText(); + this.iosVerificationText = twilioConfiguration.getIosVerificationText(); + this.genericVerificationText = twilioConfiguration.getGenericVerificationText(); + this.smsUri = URI.create(baseUri + "/2010-04-01/Accounts/" + accountId + "/Messages.json"); + this.voxUri = URI.create(baseUri + "/2010-04-01/Accounts/" + accountId + "/Calls.json" ); + this.httpClient = FaultTolerantHttpClient.newBuilder() + .withCircuitBreaker(twilioConfiguration.getCircuitBreaker()) + .withRetry(twilioConfiguration.getRetry()) + .withVersion(HttpClient.Version.HTTP_2) + .withConnectTimeout(Duration.ofSeconds(10)) + .withRedirect(HttpClient.Redirect.NEVER) + .withExecutor(executor) + .withName("twilio") + .build(); } public TwilioSmsSender(TwilioConfiguration twilioConfiguration) { @@ -125,13 +135,7 @@ public class TwilioSmsSender { requestParameters.put("To", destination); setOriginationRequestParameter(destination, requestParameters); - if ("ios".equals(clientType.orElse(null))) { - requestParameters.put("Body", String.format(SmsSender.SMS_IOS_VERIFICATION_TEXT, verificationCode, verificationCode)); - } else if ("android-ng".equals(clientType.orElse(null))) { - requestParameters.put("Body", String.format(SmsSender.SMS_ANDROID_NG_VERIFICATION_TEXT, verificationCode)); - } else { - requestParameters.put("Body", String.format(SmsSender.SMS_VERIFICATION_TEXT, verificationCode)); - } + requestParameters.put("Body", String.format(Locale.US, getBodyFormatString(clientType.orElse(null)), verificationCode)); HttpRequest request = HttpRequest.newBuilder() .uri(smsUri) @@ -147,6 +151,18 @@ public class TwilioSmsSender { .handle(this::processResponse); } + private String getBodyFormatString(@Nullable String clientType) { + if ("ios".equals(clientType)) { + return iosVerificationText; + } else if ("android-ng".equals(clientType)) { + return androidNgVerificationText; + } else if ("android-2020-01".equals(clientType)) { + return android202001VerificationText; + } else { + return genericVerificationText; + } + } + public CompletableFuture deliverVoxVerification(String destination, String verificationCode, Optional locale) { String url = "https://" + localDomain + "/v1/voice/description/" + verificationCode; diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/sms/TwilioSmsSenderTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/sms/TwilioSmsSenderTest.java index 84fcc6fbc..42366b6ca 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/sms/TwilioSmsSenderTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/sms/TwilioSmsSenderTest.java @@ -45,6 +45,10 @@ public class TwilioSmsSenderTest { configuration.setNumbers(NUMBERS); configuration.setMessagingServicesId(MESSAGING_SERVICES_ID); configuration.setLocalDomain(LOCAL_DOMAIN); + configuration.setIosVerificationText("Verify on iOS: %1$s\n\nsomelink://verify/%1$s"); + configuration.setAndroidNgVerificationText("<#> Verify on AndroidNg: %1$s\n\ncharacters"); + configuration.setAndroid202001VerificationText("Verify on Android202001: %1$s\n\nsomelink://verify/%1$s\n\ncharacters"); + configuration.setGenericVerificationText("Verify on whatever: %1$s"); return configuration; } @@ -58,7 +62,6 @@ public class TwilioSmsSenderTest { TwilioConfiguration configuration = createTwilioConfiguration(); - TwilioSmsSender sender = new TwilioSmsSender("http://localhost:" + wireMockRule.port(), configuration); boolean success = sender.deliverSmsVerification("+14153333333", Optional.of("android-ng"), "123-456").join(); @@ -66,7 +69,26 @@ public class TwilioSmsSenderTest { 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=%2B14153333333&Body=%3C%23%3E+Your+Signal+verification+code%3A+123-456%0A%0AdoDiFGKPO1r"))); + .withRequestBody(equalTo("MessagingServiceSid=test_messaging_services_id&To=%2B14153333333&Body=%3C%23%3E+Verify+on+AndroidNg%3A+123-456%0A%0Acharacters"))); + } + + @Test + public void testSendSmsAndroid202001() { + wireMockRule.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\"}"))); + + TwilioConfiguration configuration = createTwilioConfiguration(); + TwilioSmsSender sender = new TwilioSmsSender("http://localhost:" + wireMockRule.port(), configuration); + boolean success = sender.deliverSmsVerification("+14153333333", Optional.of("android-2020-01"), "123-456").join(); + + assertThat(success).isTrue(); + + 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=%2B14153333333&Body=Verify+on+Android202001%3A+123-456%0A%0Asomelink%3A%2F%2Fverify%2F123-456%0A%0Acharacters"))); } @Test @@ -91,7 +113,7 @@ public class TwilioSmsSenderTest { } @Test - public void testSendSmsFiveHundered() { + public void testSendSmsFiveHundred() { wireMockRule.stubFor(post(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json")) .withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN) .willReturn(aResponse() @@ -109,7 +131,7 @@ public class TwilioSmsSenderTest { verify(3, 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=%2B14153333333&Body=%3C%23%3E+Your+Signal+verification+code%3A+123-456%0A%0AdoDiFGKPO1r"))); + .withRequestBody(equalTo("MessagingServiceSid=test_messaging_services_id&To=%2B14153333333&Body=%3C%23%3E+Verify+on+AndroidNg%3A+123-456%0A%0Acharacters"))); } @Test @@ -162,7 +184,7 @@ public class TwilioSmsSenderTest { assertThat(success).isTrue(); final String requestBodyToParam = "To=" + URLEncoder.encode(destination, StandardCharsets.UTF_8); - final String requestBodySuffix = "&Body=%3C%23%3E+Your+Signal+verification+code%3A+987-654%0A%0AdoDiFGKPO1r"; + final String requestBodySuffix = "&Body=%3C%23%3E+Verify+on+AndroidNg%3A+987-654%0A%0Acharacters"; final String expectedRequestBody; if (expectedSenderId != null) { expectedRequestBody = requestBodyToParam + "&From=" + expectedSenderId + requestBodySuffix;