Commit e4a05b35 authored by Daniel Gultsch's avatar Daniel Gultsch
Browse files

add verification provider that generates a fixed pin based on a given phone number

parent 6a68c3d9
......@@ -25,6 +25,12 @@
}
},
"provider": {
"im.quicksy.server.verification.FixedPinVerificationProvider": {
"pattern": "^\\+1\\d{3}55501\\d{2}$",
"parameter": {
"salt": "secret"
}
},
"im.quicksy.server.verification.NexmoVerificationProvider": {
"deny": [
91
......
......@@ -24,6 +24,7 @@ import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import de.gultsch.xmpp.addr.adapter.Adapter;
import im.quicksy.server.json.DurationDeserializer;
import im.quicksy.server.json.PatternDeserializer;
import im.quicksy.server.json.VersionDeserializer;
import rocks.xmpp.addr.Jid;
......@@ -32,6 +33,7 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.time.Duration;
import java.util.*;
import java.util.regex.Pattern;
public class Configuration {
......@@ -79,6 +81,7 @@ public class Configuration {
Adapter.register(gsonBuilder);
gsonBuilder.registerTypeAdapter(Version.class, new VersionDeserializer());
gsonBuilder.registerTypeAdapter(Duration.class, new DurationDeserializer());
gsonBuilder.registerTypeAdapter(Pattern.class, new PatternDeserializer());
final Gson gson = gsonBuilder.create();
try {
System.out.println("Reading configuration from " + FILE.getAbsolutePath());
......@@ -221,6 +224,7 @@ public class Configuration {
public static class ProviderConfiguration {
private Map<String, String> parameter;
private List<Integer> deny;
private Pattern pattern;
public Map<String, String> getParameter() {
return parameter;
......@@ -229,5 +233,9 @@ public class Configuration {
public List<Integer> getDeny() {
return deny == null ? Collections.emptyList() : deny;
}
public Pattern getPattern() {
return this.pattern;
}
}
}
......@@ -108,7 +108,7 @@ public class PasswordController extends BaseController {
}
return "";
} else {
System.out.println("verification provider reported failed");
LOGGER.info("verification provider reported wrong pin");
return halt(401);
}
} catch (TokenExpiredException e) {
......
package im.quicksy.server.json;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
import java.util.regex.Pattern;
public class PatternDeserializer implements JsonDeserializer<Pattern> {
@Override
public Pattern deserialize(
final JsonElement jsonElement,
final Type type,
final JsonDeserializationContext context)
throws JsonParseException {
if (jsonElement.isJsonNull()) {
return null;
}
final String pattern = jsonElement.getAsString();
try {
return Pattern.compile(pattern);
} catch (Exception e) {
throw new JsonParseException("invalid pattern", e);
}
}
}
package im.quicksy.server.verification;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.hash.Hashing;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.util.Map;
public class FixedPinVerificationProvider extends AbstractVerificationProvider {
private static final Logger LOGGER =
LoggerFactory.getLogger(FixedPinVerificationProvider.class);
private final String salt;
public FixedPinVerificationProvider(final Map<String, String> parameter) {
super(parameter);
this.salt = Strings.nullToEmpty(parameter.get("salt"));
}
@Override
public boolean verify(final Phonenumber.PhoneNumber phoneNumber, final String pin)
throws RequestFailedException {
final boolean verified = generatePin(phoneNumber).equals(pin);
if (verified) {
LOGGER.info("Pin for {} has been verified successfully", phoneNumber);
return true;
} else {
LOGGER.info("Pin for {} was incorrect", phoneNumber);
return false;
}
}
@Override
public void request(final Phonenumber.PhoneNumber phoneNumber, final Method method)
throws RequestFailedException {
final String pin = generatePin(phoneNumber);
LOGGER.info("requesting pin for {}. Pin is going to be {}", phoneNumber, pin);
}
@Override
public void request(Phonenumber.PhoneNumber phoneNumber, Method method, String language)
throws RequestFailedException {
this.request(phoneNumber, method);
}
@SuppressWarnings("UnstableApiUsage")
private String generatePin(final Phonenumber.PhoneNumber phoneNumber) {
final String e164 =
PhoneNumberUtil.getInstance()
.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
return new BigInteger(
1,
Hashing.sha256()
.newHasher()
.putString(e164, Charsets.UTF_8)
.putChar('\0')
.putString(salt, Charsets.UTF_8)
.hash()
.asBytes())
.toString()
.substring(0, 6);
}
}
......@@ -2,6 +2,7 @@ package im.quicksy.server.verification;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
import im.quicksy.server.configuration.Configuration;
import org.slf4j.Logger;
......@@ -12,6 +13,7 @@ import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Pattern;
public class MetaVerificationProvider implements VerificationProvider {
......@@ -43,7 +45,7 @@ public class MetaVerificationProvider implements VerificationProvider {
LOGGER.warn("Unable to construct VerificationProvider",e);
continue;
}
providerListBuilder.add(new ProviderWrapper(configuration.getDeny(), providerInstance));
providerListBuilder.add(new ProviderWrapper(configuration.getDeny(), configuration.getPattern(), providerInstance));
LOGGER.info("found provider {} ", className);
}
final ImmutableList<ProviderWrapper> providerList = providerListBuilder.build();
......@@ -71,22 +73,29 @@ public class MetaVerificationProvider implements VerificationProvider {
private AbstractVerificationProvider getVerificationProvider(Phonenumber.PhoneNumber phoneNumber) throws RequestFailedException {
final int countryCode = phoneNumber.getCountryCode();
for(ProviderWrapper providerWrapper : this.providerList) {
if (providerWrapper.deny.contains(countryCode)) {
final String e164 = PhoneNumberUtil.getInstance().format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
for(final ProviderWrapper providerWrapper : this.providerList) {
if (providerWrapper.reject(e164) || providerWrapper.deny.contains(countryCode)) {
continue;
}
return providerWrapper.provider;
}
throw new RequestFailedException(String.format("No Verification Provider found to handle country code %d", countryCode));
throw new RequestFailedException(String.format("No Verification Provider found to handle phone number %s", e164));
}
private static class ProviderWrapper {
private final List<Integer> deny;
private final Pattern pattern;
private final AbstractVerificationProvider provider;
private ProviderWrapper(List<Integer> deny, AbstractVerificationProvider provider) {
private ProviderWrapper(List<Integer> deny, Pattern pattern, AbstractVerificationProvider provider) {
this.deny = Preconditions.checkNotNull(deny);
this.pattern = pattern;
this.provider = Preconditions.checkNotNull(provider);
}
public boolean reject(final String e164) {
return pattern != null && !pattern.matcher(e164).matches();
}
}
}
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