Skip to content
Snippets Groups Projects
Commit 6618fcfd authored by Daniel Gultsch's avatar Daniel Gultsch
Browse files

implement MetaVerificationProvider to choose verification provider based on country code

parent 2cf0682d
Branches
No related merge requests found
...@@ -31,8 +31,7 @@ import java.io.File; ...@@ -31,8 +31,7 @@ import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileReader; import java.io.FileReader;
import java.time.Duration; import java.time.Duration;
import java.util.HashMap; import java.util.*;
import java.util.Optional;
public class Configuration { public class Configuration {
...@@ -43,12 +42,9 @@ public class Configuration { ...@@ -43,12 +42,9 @@ public class Configuration {
private Web web = new Web(); private Web web = new Web();
private HashMap<String, DatabaseConfiguration> db; private HashMap<String, DatabaseConfiguration> db;
private PayPal payPal = new PayPal(); private PayPal payPal = new PayPal();
private String twilioAuthToken;
private String nexmoApiKey; private TreeMap<String, ProviderConfiguration> provider;
private String nexmoPhoneNumber;
private String nexmoApiSecret;
private String cimAuthToken; private String cimAuthToken;
private Version minVersion; private Version minVersion;
private Duration accountInactivity = Duration.ofDays(28); private Duration accountInactivity = Duration.ofDays(28);
...@@ -135,22 +131,6 @@ public class Configuration { ...@@ -135,22 +131,6 @@ public class Configuration {
return new DatabaseConfigurationBundle.Builder().setEjabberdConfiguration(db.get("ejabberd")).setQuicksyConfiguration(db.get("quicksy")).build(); 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() { public Optional<String> getCimAuthToken() {
return Optional.ofNullable(cimAuthToken); return Optional.ofNullable(cimAuthToken);
} }
...@@ -171,6 +151,10 @@ public class Configuration { ...@@ -171,6 +151,10 @@ public class Configuration {
return minVersion; return minVersion;
} }
public TreeMap<String, Configuration.ProviderConfiguration> getProvider() {
return this.provider;
}
public static class XMPP { public static class XMPP {
private String host = "localhost"; private String host = "localhost";
private int port = 5347; private int port = 5347;
...@@ -232,4 +216,18 @@ public class Configuration { ...@@ -232,4 +216,18 @@ public class Configuration {
return username != null && password != null && signature != null; 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; ...@@ -21,6 +21,7 @@ import com.github.zafarkhaja.semver.Version;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.net.InetAddresses; import com.google.common.net.InetAddresses;
import im.quicksy.server.configuration.Configuration; import im.quicksy.server.configuration.Configuration;
import im.quicksy.server.verification.MetaVerificationProvider;
import im.quicksy.server.verification.NexmoVerificationProvider; import im.quicksy.server.verification.NexmoVerificationProvider;
import im.quicksy.server.verification.TwilioVerificationProvider; import im.quicksy.server.verification.TwilioVerificationProvider;
import im.quicksy.server.verification.VerificationProvider; import im.quicksy.server.verification.VerificationProvider;
...@@ -52,7 +53,7 @@ public class BaseController { ...@@ -52,7 +53,7 @@ public class BaseController {
protected static Pattern PIN_PATTERN = Pattern.compile("^[0-9]{6}$"); 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 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) { protected static InetAddress getClientIp(Request request) {
final InetAddress remote = InetAddresses.forString(request.ip()); 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; ...@@ -20,10 +20,16 @@ import com.google.i18n.phonenumbers.Phonenumber;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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); private static final Logger LOGGER = LoggerFactory.getLogger(MockVerificationProvider.class);
public MockVerificationProvider(Map<String, String> parameter) {
super(parameter);
}
@Override @Override
public boolean verify(Phonenumber.PhoneNumber phoneNumber, String pin) { public boolean verify(Phonenumber.PhoneNumber phoneNumber, String pin) {
return pin != null && pin.length() == 6 && String.valueOf(phoneNumber.getNationalNumber()).startsWith(pin); return pin != null && pin.length() == 6 && String.valueOf(phoneNumber.getNationalNumber()).startsWith(pin);
......
package im.quicksy.server.verification; package im.quicksy.server.verification;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
...@@ -18,8 +19,9 @@ import java.security.SecureRandom; ...@@ -18,8 +19,9 @@ import java.security.SecureRandom;
import java.time.Duration; import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; 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); private static final Logger LOGGER = LoggerFactory.getLogger(NexmoVerificationProvider.class);
...@@ -44,6 +46,17 @@ public class NexmoVerificationProvider implements VerificationProvider { ...@@ -44,6 +46,17 @@ public class NexmoVerificationProvider implements VerificationProvider {
.expireAfterWrite(Duration.ofMinutes(5)) .expireAfterWrite(Duration.ofMinutes(5))
.build(); .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 @Override
public boolean verify(Phonenumber.PhoneNumber phoneNumber, String input) throws RequestFailedException { public boolean verify(Phonenumber.PhoneNumber phoneNumber, String input) throws RequestFailedException {
final Pin pin = PIN_CACHE.getIfPresent(phoneNumber); final Pin pin = PIN_CACHE.getIfPresent(phoneNumber);
...@@ -70,7 +83,7 @@ public class NexmoVerificationProvider implements VerificationProvider { ...@@ -70,7 +83,7 @@ public class NexmoVerificationProvider implements VerificationProvider {
final Pin pin = Pin.generate(); final Pin pin = Pin.generate();
PIN_CACHE.put(phoneNumber, pin); PIN_CACHE.put(phoneNumber, pin);
final String to = String.format("%d%d", phoneNumber.getCountryCode(), phoneNumber.getNationalNumber()); 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; final String from;
if (Strings.isNullOrEmpty(nexmoPhoneNumber) || COUNTRY_CODES_SUPPORTING_ALPHA_NUMERIC.contains(phoneNumber.getCountryCode())) { if (Strings.isNullOrEmpty(nexmoPhoneNumber) || COUNTRY_CODES_SUPPORTING_ALPHA_NUMERIC.contains(phoneNumber.getCountryCode())) {
from = BRAND_NAME; from = BRAND_NAME;
...@@ -83,8 +96,8 @@ public class NexmoVerificationProvider implements VerificationProvider { ...@@ -83,8 +96,8 @@ public class NexmoVerificationProvider implements VerificationProvider {
.add("from", from) .add("from", from)
.add("text", String.format(MESSAGE, pin.toString())) .add("text", String.format(MESSAGE, pin.toString()))
.add("to", to) .add("to", to)
.add("api_key", Configuration.getInstance().getNexmoApiKey()) .add("api_key", this.apiKey)
.add("api_secret", Configuration.getInstance().getNexmoApiSecret()) .add("api_secret", this.apiSecret)
.build()) .build())
.url(NEXMO_API_URL) .url(NEXMO_API_URL)
.build()); .build());
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package im.quicksy.server.verification; package im.quicksy.server.verification;
import com.google.common.base.Preconditions;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
...@@ -34,12 +35,10 @@ import java.io.UnsupportedEncodingException; ...@@ -34,12 +35,10 @@ import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.HashMap; import java.util.*;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class TwilioVerificationProvider implements VerificationProvider { public class TwilioVerificationProvider extends AbstractVerificationProvider {
public static final int PHONE_VERIFICATION_INCORRECT = 60022; public static final int PHONE_VERIFICATION_INCORRECT = 60022;
...@@ -51,6 +50,23 @@ public class TwilioVerificationProvider implements VerificationProvider { ...@@ -51,6 +50,23 @@ public class TwilioVerificationProvider implements VerificationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TwilioVerificationProvider.class); private static final Logger LOGGER = LoggerFactory.getLogger(TwilioVerificationProvider.class);
private final GsonBuilder gsonBuilder = new GsonBuilder(); 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 @Override
public boolean verify(Phonenumber.PhoneNumber phoneNumber, String pin) throws RequestFailedException { public boolean verify(Phonenumber.PhoneNumber phoneNumber, String pin) throws RequestFailedException {
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
...@@ -124,7 +140,7 @@ public class TwilioVerificationProvider implements VerificationProvider { ...@@ -124,7 +140,7 @@ public class TwilioVerificationProvider implements VerificationProvider {
try { try {
final Gson gson = this.gsonBuilder.create(); final Gson gson = this.gsonBuilder.create();
final HttpURLConnection connection = (HttpURLConnection) new URL(TWILIO_API_URL + method).openConnection(); 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) { if (params != null && params.size() > 0) {
connection.setRequestMethod("POST"); connection.setRequestMethod("POST");
final String output = getQuery(params); final String output = getQuery(params);
......
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