parent
b31a88043e
commit
0bc494245d
|
@ -19,7 +19,6 @@ package org.whispersystems.textsecuregcm;
|
|||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.whispersystems.textsecuregcm.configuration.FederationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.GraphiteConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.NexmoConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.PushConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RedPhoneConfiguration;
|
||||
|
@ -31,7 +30,6 @@ import org.whispersystems.textsecuregcm.configuration.WebsocketConfiguration;
|
|||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -48,9 +46,6 @@ public class WhisperServerConfiguration extends Configuration {
|
|||
@JsonProperty
|
||||
private TwilioConfiguration twilio;
|
||||
|
||||
@JsonProperty
|
||||
private NexmoConfiguration nexmo;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
|
@ -120,10 +115,6 @@ public class WhisperServerConfiguration extends Configuration {
|
|||
return twilio;
|
||||
}
|
||||
|
||||
public NexmoConfiguration getNexmoConfiguration() {
|
||||
return nexmo;
|
||||
}
|
||||
|
||||
public PushConfiguration getPushConfiguration() {
|
||||
return push;
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider;
|
|||
import org.whispersystems.dropwizard.simpleauth.BasicCredentialAuthFilter;
|
||||
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
|
||||
import org.whispersystems.textsecuregcm.auth.FederatedPeerAuthenticator;
|
||||
import org.whispersystems.textsecuregcm.configuration.NexmoConfiguration;
|
||||
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
||||
import org.whispersystems.textsecuregcm.controllers.AttachmentController;
|
||||
import org.whispersystems.textsecuregcm.controllers.DeviceController;
|
||||
|
@ -68,7 +67,6 @@ import org.whispersystems.textsecuregcm.push.PushSender;
|
|||
import org.whispersystems.textsecuregcm.push.PushServiceClient;
|
||||
import org.whispersystems.textsecuregcm.push.ReceiptSender;
|
||||
import org.whispersystems.textsecuregcm.push.WebsocketSender;
|
||||
import org.whispersystems.textsecuregcm.sms.NexmoSmsSender;
|
||||
import org.whispersystems.textsecuregcm.sms.SmsSender;
|
||||
import org.whispersystems.textsecuregcm.sms.TwilioSmsSender;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
|
@ -185,8 +183,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||
|
||||
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushServiceClient, pubSubManager);
|
||||
TwilioSmsSender twilioSmsSender = new TwilioSmsSender(config.getTwilioConfiguration());
|
||||
Optional<NexmoSmsSender> nexmoSmsSender = initializeNexmoSmsSender(config.getNexmoConfiguration());
|
||||
SmsSender smsSender = new SmsSender(twilioSmsSender, nexmoSmsSender, config.getTwilioConfiguration().isInternational());
|
||||
SmsSender smsSender = new SmsSender(twilioSmsSender);
|
||||
UrlSigner urlSigner = new UrlSigner(config.getS3Configuration());
|
||||
PushSender pushSender = new PushSender(apnFallbackManager, pushServiceClient, websocketSender);
|
||||
ReceiptSender receiptSender = new ReceiptSender(accountsManager, pushSender, federatedClientManager);
|
||||
|
@ -282,14 +279,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||
}
|
||||
}
|
||||
|
||||
private Optional<NexmoSmsSender> initializeNexmoSmsSender(NexmoConfiguration configuration) {
|
||||
if (configuration == null) {
|
||||
return Optional.absent();
|
||||
} else {
|
||||
return Optional.of(new NexmoSmsSender(configuration));
|
||||
}
|
||||
}
|
||||
|
||||
private Client initializeHttpClient(Environment environment, WhisperServerConfiguration config) {
|
||||
Client httpClient = new JerseyClientBuilder(environment).using(config.getJerseyClientConfiguration())
|
||||
.build(getName());
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class NexmoConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
private String apiKey;
|
||||
|
||||
@JsonProperty
|
||||
private String apiSecret;
|
||||
|
||||
@JsonProperty
|
||||
private String number;
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public String getApiSecret() {
|
||||
return apiSecret;
|
||||
}
|
||||
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
}
|
|
@ -40,9 +40,6 @@ public class TwilioConfiguration {
|
|||
@JsonProperty
|
||||
private String localDomain;
|
||||
|
||||
@JsonProperty
|
||||
private boolean international;
|
||||
|
||||
public String getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
@ -58,8 +55,4 @@ public class TwilioConfiguration {
|
|||
public String getLocalDomain() {
|
||||
return localDomain;
|
||||
}
|
||||
|
||||
public boolean isInternational() {
|
||||
return international;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ public class AccountController {
|
|||
@Path("/{transport}/code/{number}")
|
||||
public Response createAccount(@PathParam("transport") String transport,
|
||||
@PathParam("number") String number,
|
||||
@QueryParam("client") String client)
|
||||
@QueryParam("client") Optional<String> client)
|
||||
throws IOException, RateLimitExceededException
|
||||
{
|
||||
if (!Util.isValidNumber(number)) {
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.sms;
|
||||
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.SharedMetricRegistries;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.configuration.NexmoConfiguration;
|
||||
import org.whispersystems.textsecuregcm.util.Constants;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
|
||||
public class NexmoSmsSender {
|
||||
|
||||
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
||||
private final Meter smsMeter = metricRegistry.meter(name(getClass(), "sms", "delivered"));
|
||||
private final Meter voxMeter = metricRegistry.meter(name(getClass(), "vox", "delivered"));
|
||||
private final Logger logger = LoggerFactory.getLogger(NexmoSmsSender.class);
|
||||
|
||||
private static final String NEXMO_SMS_URL =
|
||||
"https://rest.nexmo.com/sms/json?api_key=%s&api_secret=%s&from=%s&to=%s&text=%s";
|
||||
|
||||
private static final String NEXMO_VOX_URL =
|
||||
"https://rest.nexmo.com/tts/json?api_key=%s&api_secret=%s&to=%s&text=%s";
|
||||
|
||||
private final String apiKey;
|
||||
private final String apiSecret;
|
||||
private final String number;
|
||||
|
||||
public NexmoSmsSender(NexmoConfiguration config) {
|
||||
this.apiKey = config.getApiKey();
|
||||
this.apiSecret = config.getApiSecret();
|
||||
this.number = config.getNumber();
|
||||
}
|
||||
|
||||
public void deliverSmsVerification(String destination, String clientType, String verificationCode) throws IOException {
|
||||
String verificationMsg;
|
||||
|
||||
if ("ios".equals(clientType)) {
|
||||
verificationMsg = String.format(SmsSender.SMS_IOS_VERIFICATION_TEXT, verificationCode, verificationCode);
|
||||
} else {
|
||||
verificationMsg = String.format(SmsSender.SMS_VERIFICATION_TEXT, verificationCode);
|
||||
}
|
||||
|
||||
URL url = new URL(String.format(NEXMO_SMS_URL, apiKey, apiSecret, number, destination,
|
||||
URLEncoder.encode(verificationMsg, "UTF-8")));
|
||||
|
||||
URLConnection connection = url.openConnection();
|
||||
connection.setDoInput(true);
|
||||
connection.connect();
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
while (reader.readLine() != null) {}
|
||||
reader.close();
|
||||
smsMeter.mark();
|
||||
}
|
||||
|
||||
public void deliverVoxVerification(String destination, String message) throws IOException {
|
||||
URL url = new URL(String.format(NEXMO_VOX_URL, apiKey, apiSecret, destination,
|
||||
URLEncoder.encode(SmsSender.VOX_VERIFICATION_TEXT + message, "UTF-8")));
|
||||
|
||||
URLConnection connection = url.openConnection();
|
||||
connection.setDoInput(true);
|
||||
connection.connect();
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
logger.debug(line);
|
||||
}
|
||||
reader.close();
|
||||
voxMeter.mark();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -25,26 +25,21 @@ import org.slf4j.LoggerFactory;
|
|||
import java.io.IOException;
|
||||
|
||||
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_VERIFICATION_TEXT = "Your TextSecure verification code: ";
|
||||
static final String VOX_VERIFICATION_TEXT = "Your Signal verification code is: ";
|
||||
static final String SMS_VERIFICATION_TEXT = "Your TextSecure verification code: %s";
|
||||
static final String VOX_VERIFICATION_TEXT = "Your Signal verification code is: ";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SmsSender.class);
|
||||
|
||||
private final TwilioSmsSender twilioSender;
|
||||
private final Optional<NexmoSmsSender> nexmoSender;
|
||||
private final boolean isTwilioInternational;
|
||||
private final TwilioSmsSender twilioSender;
|
||||
|
||||
public SmsSender(TwilioSmsSender twilioSender,
|
||||
Optional<NexmoSmsSender> nexmoSender,
|
||||
boolean isTwilioInternational)
|
||||
public SmsSender(TwilioSmsSender twilioSender)
|
||||
{
|
||||
this.isTwilioInternational = isTwilioInternational;
|
||||
this.twilioSender = twilioSender;
|
||||
this.nexmoSender = nexmoSender;
|
||||
this.twilioSender = twilioSender;
|
||||
}
|
||||
|
||||
public void deliverSmsVerification(String destination, String clientType, String verificationCode)
|
||||
public void deliverSmsVerification(String destination, Optional<String> clientType, String verificationCode)
|
||||
throws IOException
|
||||
{
|
||||
// Fix up mexico numbers to 'mobile' format just for SMS delivery.
|
||||
|
@ -52,38 +47,20 @@ public class SmsSender {
|
|||
destination = "+421" + destination.substring(3);
|
||||
}
|
||||
|
||||
if (!isTwilioDestination(destination) && nexmoSender.isPresent()) {
|
||||
nexmoSender.get().deliverSmsVerification(destination, clientType, verificationCode);
|
||||
} else {
|
||||
try {
|
||||
twilioSender.deliverSmsVerification(destination, clientType, verificationCode);
|
||||
} catch (TwilioRestException e) {
|
||||
logger.info("Twilio SMS Failed: " + e.getErrorMessage());
|
||||
if (nexmoSender.isPresent()) {
|
||||
nexmoSender.get().deliverSmsVerification(destination, clientType, verificationCode);
|
||||
}
|
||||
}
|
||||
try {
|
||||
twilioSender.deliverSmsVerification(destination, clientType, verificationCode);
|
||||
} catch (TwilioRestException e) {
|
||||
logger.info("Twilio SMS Failed: " + e.getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void deliverVoxVerification(String destination, String verificationCode)
|
||||
throws IOException
|
||||
{
|
||||
if (!isTwilioDestination(destination) && nexmoSender.isPresent()) {
|
||||
nexmoSender.get().deliverVoxVerification(destination, verificationCode);
|
||||
} else {
|
||||
try {
|
||||
twilioSender.deliverVoxVerification(destination, verificationCode);
|
||||
} catch (TwilioRestException e) {
|
||||
logger.info("Twilio Vox Failed: " + e.getErrorMessage());
|
||||
if (nexmoSender.isPresent()) {
|
||||
nexmoSender.get().deliverVoxVerification(destination, verificationCode);
|
||||
}
|
||||
}
|
||||
try {
|
||||
twilioSender.deliverVoxVerification(destination, verificationCode);
|
||||
} catch (TwilioRestException e) {
|
||||
logger.info("Twilio Vox Failed: " + e.getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTwilioDestination(String number) {
|
||||
return isTwilioInternational || number.length() == 12 && number.startsWith("+1");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.whispersystems.textsecuregcm.sms;
|
|||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.SharedMetricRegistries;
|
||||
import com.google.common.base.Optional;
|
||||
import com.twilio.sdk.TwilioRestClient;
|
||||
import com.twilio.sdk.TwilioRestException;
|
||||
import com.twilio.sdk.resource.factory.CallFactory;
|
||||
|
@ -63,7 +64,7 @@ public class TwilioSmsSender {
|
|||
this.random = new Random(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public void deliverSmsVerification(String destination, String clientType, String verificationCode)
|
||||
public void deliverSmsVerification(String destination, Optional<String> clientType, String verificationCode)
|
||||
throws IOException, TwilioRestException
|
||||
{
|
||||
TwilioRestClient client = new TwilioRestClient(accountId, accountToken);
|
||||
|
@ -72,7 +73,7 @@ public class TwilioSmsSender {
|
|||
messageParams.add(new BasicNameValuePair("To", destination));
|
||||
messageParams.add(new BasicNameValuePair("From", getRandom(random, numbers)));
|
||||
|
||||
if ("ios".equals(clientType)) {
|
||||
if ("ios".equals(clientType.orNull())) {
|
||||
messageParams.add(new BasicNameValuePair("Body", String.format(SmsSender.SMS_IOS_VERIFICATION_TEXT, verificationCode, verificationCode)));
|
||||
} else {
|
||||
messageParams.add(new BasicNameValuePair("Body", String.format(SmsSender.SMS_VERIFICATION_TEXT, verificationCode)));
|
||||
|
|
|
@ -82,7 +82,7 @@ public class AccountControllerTest {
|
|||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
|
||||
verify(smsSender).deliverSmsVerification(eq(SENDER), isNull(String.class), anyString());
|
||||
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.<String>absent()), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -96,7 +96,7 @@ public class AccountControllerTest {
|
|||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
|
||||
verify(smsSender).deliverSmsVerification(eq(SENDER), eq("ios"), anyString());
|
||||
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.of("ios")), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
package org.whispersystems.textsecuregcm.tests.sms;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.twilio.sdk.TwilioRestException;
|
||||
import junit.framework.TestCase;
|
||||
import org.whispersystems.textsecuregcm.sms.NexmoSmsSender;
|
||||
import org.whispersystems.textsecuregcm.sms.SmsSender;
|
||||
import org.whispersystems.textsecuregcm.sms.TwilioSmsSender;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
public class DeliveryPreferenceTest extends TestCase {
|
||||
|
||||
private TwilioSmsSender twilioSender = mock(TwilioSmsSender.class);
|
||||
private NexmoSmsSender nexmoSender = mock(NexmoSmsSender.class);
|
||||
|
||||
public void testInternationalPreferenceOff() throws IOException, TwilioRestException {
|
||||
SmsSender smsSender = new SmsSender(twilioSender, Optional.of(nexmoSender), false);
|
||||
|
||||
smsSender.deliverSmsVerification("+441112223333", null, "123-456");
|
||||
verify(nexmoSender).deliverSmsVerification("+441112223333", null, "123-456");
|
||||
verifyNoMoreInteractions(twilioSender);
|
||||
}
|
||||
|
||||
public void testInternationalPreferenceOn() throws IOException, TwilioRestException {
|
||||
SmsSender smsSender = new SmsSender(twilioSender, Optional.of(nexmoSender), true);
|
||||
|
||||
smsSender.deliverSmsVerification("+441112223333", null, "123-456");
|
||||
verify(twilioSender).deliverSmsVerification("+441112223333", null, "123-456");
|
||||
verifyNoMoreInteractions(nexmoSender);
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package org.whispersystems.textsecuregcm.tests.sms;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.twilio.sdk.TwilioRestException;
|
||||
import junit.framework.TestCase;
|
||||
import org.whispersystems.textsecuregcm.sms.NexmoSmsSender;
|
||||
import org.whispersystems.textsecuregcm.sms.SmsSender;
|
||||
import org.whispersystems.textsecuregcm.sms.TwilioSmsSender;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class TwilioFallbackTest extends TestCase {
|
||||
|
||||
private NexmoSmsSender nexmoSender = mock(NexmoSmsSender.class );
|
||||
private TwilioSmsSender twilioSender = mock(TwilioSmsSender.class);
|
||||
|
||||
@Override
|
||||
protected void setUp() throws IOException, TwilioRestException {
|
||||
doThrow(new TwilioRestException("foo", 404)).when(twilioSender).deliverSmsVerification(anyString(), anyString(), anyString());
|
||||
doThrow(new TwilioRestException("bar", 405)).when(twilioSender).deliverVoxVerification(anyString(), anyString());
|
||||
}
|
||||
|
||||
public void testNexmoSmsFallback() throws IOException, TwilioRestException {
|
||||
SmsSender smsSender = new SmsSender(twilioSender, Optional.of(nexmoSender), true);
|
||||
smsSender.deliverSmsVerification("+442223334444", null, "123-456");
|
||||
|
||||
verify(nexmoSender).deliverSmsVerification("+442223334444", null, "123-456");
|
||||
verify(twilioSender).deliverSmsVerification("+442223334444", null, "123-456");
|
||||
}
|
||||
|
||||
public void testNexmoVoxFallback() throws IOException, TwilioRestException {
|
||||
SmsSender smsSender = new SmsSender(twilioSender, Optional.of(nexmoSender), true);
|
||||
smsSender.deliverVoxVerification("+442223334444", "123-456");
|
||||
|
||||
verify(nexmoSender).deliverVoxVerification("+442223334444", "123-456");
|
||||
verify(twilioSender).deliverVoxVerification("+442223334444", "123-456");
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue