Commit 6618fcfd authored by Daniel Gultsch's avatar Daniel Gultsch
Browse files

implement MetaVerificationProvider to choose verification provider based on country code

parent 2cf0682d
......@@ -31,8 +31,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.time.Duration;
import java.util.HashMap;
import java.util.Optional;
import java.util.*;
public class Configuration {
......@@ -43,12 +42,9 @@ public class Configuration {
private Web web = new Web();
private HashMap<String, DatabaseConfiguration> db;
private PayPal payPal = new PayPal();
private String twilioAuthToken;
private String nexmoApiKey;
private String nexmoPhoneNumber;
private TreeMap<String, ProviderConfiguration> provider;
private String nexmoApiSecret;
private String cimAuthToken;
private Version minVersion;
private Duration accountInactivity = Duration.ofDays(28);
......@@ -135,22 +131,6 @@ public class Configuration {
return new DatabaseConfigurationBundle.Builder().setEjabberdConfiguration(db.get("ejabberd")).setQuicksyConfiguration(db.get("quicksy")).build();
}
public String getTwilioAuthToken() {
return twilioAuthToken;
}
public String getNexmoApiKey() {
return nexmoApiKey;
}
public String getNexmoApiSecret() {
return nexmoApiSecret;
}
public String getNexmoPhoneNumber() {
return nexmoPhoneNumber;
}
public Optional<String> getCimAuthToken() {
return Optional.ofNullable(cimAuthToken);
}
......@@ -171,6 +151,10 @@ public class Configuration {
return minVersion;
}
public TreeMap<String, Configuration.ProviderConfiguration> getProvider() {
return this.provider;
}
public static class XMPP {
private String host = "localhost";
private int port = 5347;
......@@ -232,4 +216,18 @@ public class Configuration {
return username != null && password != null && signature != null;
}
}
public static class ProviderConfiguration {
private Map<String, String> parameter;
private List<Integer> deny;
public Map<String, String> getParameter() {
return parameter;
}
public List<Integer> getDeny() {
return deny;
}
}
}
......@@ -21,6 +21,7 @@ import com.github.zafarkhaja.semver.Version;
import com.google.common.base.Splitter;
import com.google.common.net.InetAddresses;
import im.quicksy.server.configuration.Configuration;
import im.quicksy.server.verification.MetaVerificationProvider;
import im.quicksy.server.verification.NexmoVerificationProvider;
import im.quicksy.server.verification.TwilioVerificationProvider;
import im.quicksy.server.verification.VerificationProvider;
......@@ -52,7 +53,7 @@ public class BaseController {
protected static Pattern PIN_PATTERN = Pattern.compile("^[0-9]{6}$");
protected static Pattern UUID_PATTERN = Pattern.compile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$");
protected static final VerificationProvider VERIFICATION_PROVIDER = new NexmoVerificationProvider();
protected static final VerificationProvider VERIFICATION_PROVIDER = new MetaVerificationProvider();
protected static InetAddress getClientIp(Request request) {
final InetAddress remote = InetAddresses.forString(request.ip());
......
package im.quicksy.server.verification;
import java.util.Map;
public abstract class AbstractVerificationProvider implements VerificationProvider {
public AbstractVerificationProvider(final Map<String, String> parameter) {
}
}
package im.quicksy.server.verification;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.i18n.phonenumbers.Phonenumber;
import im.quicksy.server.configuration.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class MetaVerificationProvider implements VerificationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(MetaVerificationProvider.class);
final List<ProviderWrapper> providerList;
public MetaVerificationProvider() {
final TreeMap<String, Configuration.ProviderConfiguration> provider = Configuration.getInstance().getProvider();
ImmutableList.Builder<ProviderWrapper> providerListBuilder = ImmutableList.builder();
for(final Map.Entry<String,Configuration.ProviderConfiguration> entry : provider.entrySet()) {
final String className = entry.getKey();
final Configuration.ProviderConfiguration configuration = entry.getValue();
final Class<? extends AbstractVerificationProvider> clazz;
try {
clazz = (Class<? extends AbstractVerificationProvider>) Class.forName(className);
} catch (ClassNotFoundException | ClassCastException e) {
LOGGER.warn("No VerificationProvider found matching for name {}", className);
continue;
}
final AbstractVerificationProvider providerInstance;
try {
Constructor<? extends AbstractVerificationProvider> constructor = clazz.getConstructor(Map.class);
providerInstance = constructor.newInstance(configuration.getParameter());
} catch (NoSuchMethodException e) {
LOGGER.warn("{} does not implement Map<String,String> constructor", clazz.getName());
continue;
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
LOGGER.warn("Unable to construct VerificationProvider",e);
continue;
}
providerListBuilder.add(new ProviderWrapper(configuration.getDeny(), providerInstance));
LOGGER.info("found provider {} ", className);
}
final ImmutableList<ProviderWrapper> providerList = providerListBuilder.build();
LOGGER.info("Found {} providers", providerList.size());
if (providerList.size() == 0) {
throw new IllegalStateException("No VerificationProviders found");
}
this.providerList = providerList;
}
@Override
public boolean verify(Phonenumber.PhoneNumber phoneNumber, String pin) throws RequestFailedException {
return getVerificationProvider(phoneNumber).verify(phoneNumber, pin);
}
@Override
public void request(Phonenumber.PhoneNumber phoneNumber, Method method) throws RequestFailedException {
getVerificationProvider(phoneNumber).request(phoneNumber, method);
}
@Override
public void request(Phonenumber.PhoneNumber phoneNumber, Method method, String language) throws RequestFailedException {
getVerificationProvider(phoneNumber).request(phoneNumber, method, language);
}
private AbstractVerificationProvider getVerificationProvider(Phonenumber.PhoneNumber phoneNumber) throws RequestFailedException {
final int countryCode = phoneNumber.getCountryCode();
for(ProviderWrapper providerWrapper : this.providerList) {
if (providerWrapper.deny.contains(countryCode)) {
continue;
}
return providerWrapper.provider;
}
throw new RequestFailedException(String.format("No Verification Provider found to handle country code %d", countryCode));
}
private static class ProviderWrapper {
private final List<Integer> deny;
private final AbstractVerificationProvider provider;
private ProviderWrapper(List<Integer> deny, AbstractVerificationProvider provider) {
this.deny = deny;
this.provider = provider;
}
}
}
......@@ -20,10 +20,16 @@ import com.google.i18n.phonenumbers.Phonenumber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MockVerificationProvider implements VerificationProvider {
import java.util.Map;
public class MockVerificationProvider extends AbstractVerificationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(MockVerificationProvider.class);
public MockVerificationProvider(Map<String, String> parameter) {
super(parameter);
}
@Override
public boolean verify(Phonenumber.PhoneNumber phoneNumber, String pin) {
return pin != null && pin.length() == 6 && String.valueOf(phoneNumber.getNationalNumber()).startsWith(pin);
......
package im.quicksy.server.verification;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
......@@ -18,8 +19,9 @@ import java.security.SecureRandom;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class NexmoVerificationProvider implements VerificationProvider {
public class NexmoVerificationProvider extends AbstractVerificationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(NexmoVerificationProvider.class);
......@@ -44,6 +46,17 @@ public class NexmoVerificationProvider implements VerificationProvider {
.expireAfterWrite(Duration.ofMinutes(5))
.build();
private final String phoneNumber;
private final String apiKey;
private final String apiSecret;
public NexmoVerificationProvider(Map<String, String> parameter) {
super(parameter);
this.phoneNumber = parameter.get("phone_number");
this.apiKey = Preconditions.checkNotNull(parameter.get("api_key"));
this.apiSecret = Preconditions.checkNotNull(parameter.get("api_secret"));
}
@Override
public boolean verify(Phonenumber.PhoneNumber phoneNumber, String input) throws RequestFailedException {
final Pin pin = PIN_CACHE.getIfPresent(phoneNumber);
......@@ -70,7 +83,7 @@ public class NexmoVerificationProvider implements VerificationProvider {
final Pin pin = Pin.generate();
PIN_CACHE.put(phoneNumber, pin);
final String to = String.format("%d%d", phoneNumber.getCountryCode(), phoneNumber.getNationalNumber());
final String nexmoPhoneNumber = Configuration.getInstance().getNexmoPhoneNumber();
final String nexmoPhoneNumber = this.phoneNumber;
final String from;
if (Strings.isNullOrEmpty(nexmoPhoneNumber) || COUNTRY_CODES_SUPPORTING_ALPHA_NUMERIC.contains(phoneNumber.getCountryCode())) {
from = BRAND_NAME;
......@@ -83,8 +96,8 @@ public class NexmoVerificationProvider implements VerificationProvider {
.add("from", from)
.add("text", String.format(MESSAGE, pin.toString()))
.add("to", to)
.add("api_key", Configuration.getInstance().getNexmoApiKey())
.add("api_secret", Configuration.getInstance().getNexmoApiSecret())
.add("api_key", this.apiKey)
.add("api_secret", this.apiSecret)
.build())
.url(NEXMO_API_URL)
.build());
......
......@@ -16,6 +16,7 @@
package im.quicksy.server.verification;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
......@@ -34,12 +35,10 @@ import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
public class TwilioVerificationProvider implements VerificationProvider {
public class TwilioVerificationProvider extends AbstractVerificationProvider {
public static final int PHONE_VERIFICATION_INCORRECT = 60022;
......@@ -51,6 +50,23 @@ public class TwilioVerificationProvider implements VerificationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TwilioVerificationProvider.class);
private final GsonBuilder gsonBuilder = new GsonBuilder();
private final String authToken;
public TwilioVerificationProvider(Map<String, String> parameter) {
super(parameter);
this.authToken = Preconditions.checkNotNull(parameter.get("auth_token"));
}
public TwilioVerificationProvider() {
super(Collections.emptyMap());
final TreeMap<String, Configuration.ProviderConfiguration> provider = Configuration.getInstance().getProvider();
final Configuration.ProviderConfiguration myConfiguration = provider.get(getClass().getName());
if (myConfiguration == null) {
throw new RuntimeException("No configuration found for "+getClass().getSimpleName());
}
this.authToken = Preconditions.checkNotNull(myConfiguration.getParameter().get("auth_token"));
}
@Override
public boolean verify(Phonenumber.PhoneNumber phoneNumber, String pin) throws RequestFailedException {
Map<String, String> params = new HashMap<>();
......@@ -124,7 +140,7 @@ public class TwilioVerificationProvider implements VerificationProvider {
try {
final Gson gson = this.gsonBuilder.create();
final HttpURLConnection connection = (HttpURLConnection) new URL(TWILIO_API_URL + method).openConnection();
connection.setRequestProperty("X-Authy-API-Key", Configuration.getInstance().getTwilioAuthToken());
connection.setRequestProperty("X-Authy-API-Key", this.authToken);
if (params != null && params.size() > 0) {
connection.setRequestMethod("POST");
final String output = getQuery(params);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment