Add support for Twilio voice verification.

This commit is contained in:
Moxie Marlinspike
2013-12-09 17:50:25 -08:00
parent 4ad0dad3d9
commit c194ce153d
7 changed files with 78 additions and 40 deletions

View File

@@ -49,9 +49,9 @@ public class SenderFactory {
public VoxSender getVoxSender(String number) {
if (nexmoSender.isPresent()) {
return nexmoSender.get();
} else {
return twilioSender;
}
throw new AssertionError("FIX ME!");
}
private boolean isTwilioDestination(String number) {

View File

@@ -16,66 +16,78 @@
*/
package org.whispersystems.textsecuregcm.sms;
import com.sun.jersey.core.util.Base64;
import com.twilio.sdk.TwilioRestClient;
import com.twilio.sdk.TwilioRestException;
import com.twilio.sdk.resource.factory.CallFactory;
import com.twilio.sdk.resource.factory.MessageFactory;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Meter;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
import org.whispersystems.textsecuregcm.util.Util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class TwilioSmsSender implements SenderFactory.SmsSender {
public class TwilioSmsSender implements SenderFactory.SmsSender, SenderFactory.VoxSender {
public static final String SAY_TWIML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<Response>\n" +
" <Say voice=\"woman\" language=\"en\">%s</Say>\n" +
"</Response>";
private final Meter smsMeter = Metrics.newMeter(TwilioSmsSender.class, "sms", "delivered", TimeUnit.MINUTES);
private static final String TWILIO_URL = "https://api.twilio.com/2010-04-01/Accounts/%s/SMS/Messages";
private final Meter voxMeter = Metrics.newMeter(TwilioSmsSender.class, "vox", "delivered", TimeUnit.MINUTES);
private final String accountId;
private final String accountToken;
private final String number;
private final String localDomain;
public TwilioSmsSender(TwilioConfiguration config) {
this.accountId = config.getAccountId();
this.accountToken = config.getAccountToken();
this.number = config.getNumber();
this.localDomain = config.getLocalDomain();
}
@Override
public void deliverSmsVerification(String destination, String verificationCode) throws IOException {
URL url = new URL(String.format(TWILIO_URL, accountId));
URLConnection connection = url.openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Authorization", getTwilioAuthorizationHeader());
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
Map<String, String> formData = new HashMap<>();
formData.put("From", number);
formData.put("To", destination);
formData.put("Body", VERIFICATION_TEXT + verificationCode);
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
writer.write(Util.encodeFormParams(formData));
writer.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
while (reader.readLine() != null) {}
writer.close();
reader.close();
public void deliverSmsVerification(String destination, String verificationCode)
throws IOException
{
try {
TwilioRestClient client = new TwilioRestClient(accountId, accountToken);
MessageFactory messageFactory = client.getAccount().getMessageFactory();
List<NameValuePair> messageParams = new LinkedList<>();
messageParams.add(new BasicNameValuePair("To", destination));
messageParams.add(new BasicNameValuePair("From", number));
messageParams.add(new BasicNameValuePair("Body", SenderFactory.SmsSender.VERIFICATION_TEXT + verificationCode));
messageFactory.create(messageParams);
} catch (TwilioRestException e) {
throw new IOException(e);
}
smsMeter.mark();
}
private String getTwilioAuthorizationHeader() {
String encoded = new String(Base64.encode(String.format("%s:%s", accountId, accountToken)));
return "Basic " + encoded.replace("\n", "");
}
@Override
public void deliverVoxVerification(String destination, String verificationCode) throws IOException {
try {
TwilioRestClient client = new TwilioRestClient(accountId, accountToken);
CallFactory callFactory = client.getAccount().getCallFactory();
Map<String, String> callParams = new HashMap<>();
callParams.put("To", destination);
callParams.put("From", number);
callParams.put("Url", "https://" + localDomain + "/v1/accounts/voice/twiml/" + verificationCode);
callFactory.create(callParams);
} catch (TwilioRestException e) {
throw new IOException(e);
}
voxMeter.mark();
}
}