diff --git a/Dockerfile b/Dockerfile index b00b7cf..160ca33 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ RUN apt-get update && apt-get upgrade -y RUN apt-get install -y maven git nano -RUN git clone https://github.com/matlink/token-dispenser.git /token-dispenser +ADD . /token-dispenser RUN groupadd -g 666 dispenser && \ useradd -m -g dispenser -u 666 -s /bin/bash dispenser && \ diff --git a/README.md b/README.md index 5ca3bcd..cd8fc16 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,14 @@ Two things are configurable: Token dispenser uses [spark framework](http://sparkjava.com/). To configure network address and port on which spark should listen change `spark-host` and `spark-port`. +Basic auth is also available. To enable, change `basic-auth` to `:`. + #### Storage There are three storage options supported: * **Plain text** Set `storage` to `plaintext` to use it. `storage-plaintext-path` property is used to store filesystem path to a plain text file with email-password pairs. There is an example [here](/passwords/passwords.txt). Securing it is up to you. * **MongoDB** Set `storage` to `mongodb` to use it. Configurable parameters are self-explanatory. -* **Environment** Set `storage` to `env` to use it. Set the environment variables `TOKEN_EMAIL` and `TOKEN_PASSWORD` before starting Token dispenser. (Only one email/account is supported.) +* **Environment** Set `storage` to `env` to use it. Set the environment variable `TOKEN_CREDENTIALS` before starting Token dispenser. `TOKEN_CREDENTIALS` takes URL-encoded, comma-separated pairs of emails and passwords. Each pair must contain a colon to separate the email and password. For example, `TOKEN_CREDENTIALS=myemail%40gmail.com:password,myotheremail%40yahoo.com:1234`. ### Usage Once server is configured, you can get the tokens for **regular requests** at http://server-address:port/token/email/youremail@gmail.com @@ -47,3 +49,4 @@ gplaycli requires also the GSFid. Token and GSFid can be retrieved using http:// * [play-store-api](https://github.com/yeriomin/play-store-api) * [spark](http://sparkjava.com/) + diff --git a/src/main/java/com/github/yeriomin/tokendispenser/PasswordsDbEnv.java b/src/main/java/com/github/yeriomin/tokendispenser/PasswordsDbEnv.java index b13fa65..7a320b0 100644 --- a/src/main/java/com/github/yeriomin/tokendispenser/PasswordsDbEnv.java +++ b/src/main/java/com/github/yeriomin/tokendispenser/PasswordsDbEnv.java @@ -1,30 +1,54 @@ package com.github.yeriomin.tokendispenser; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Properties; +import java.util.Random; public class PasswordsDbEnv implements PasswordsDbInterface { - private String email; - private String password; + + static private final String FIELD_SEPARATOR = ":"; + static private final String LINE_SEPARATOR = ","; + + private Map passwords = new HashMap<>(); PasswordsDbEnv(Properties config) { - email = System.getenv(Server.ENV_EMAIL); - password = System.getenv(Server.ENV_PASSWORD); - if (email == null || password == null) { - throw new IllegalArgumentException("empty email/password, make sure to set " + Server.ENV_EMAIL + " and " + Server.ENV_PASSWORD); + + String envString = System.getenv(Server.ENV_TOKEN_CREDENTIALS); + String[] lines = envString.split(LINE_SEPARATOR); + for (String line : lines) { + String[] pair = line.split(FIELD_SEPARATOR); + if (pair.length != 2) { + Server.LOG.warn("Invalid user:pass pair in " + Server.ENV_TOKEN_CREDENTIALS); + continue; + } + try { + String email = URLDecoder.decode(pair[0], StandardCharsets.UTF_8.name()); + String password = URLDecoder.decode(pair[1], StandardCharsets.UTF_8.name()); + passwords.put(email, password); + } catch (UnsupportedEncodingException e) { + Server.LOG.error("UTF-8 is unsupported."); + return; + } } + } @Override public String getRandomEmail() { - return email; + List emails = new ArrayList<>(passwords.keySet()); + return emails.get(new Random().nextInt(emails.size())); } @Override public String get(String email) { - if (!email.equals(this.email)) { - throw new IllegalArgumentException("invalid email: " + email); - } - return password; + Server.LOG.info(email + (passwords.containsKey(email) ? "" : " NOT") + " found"); + return passwords.get(email); } @Override diff --git a/src/main/java/com/github/yeriomin/tokendispenser/Server.java b/src/main/java/com/github/yeriomin/tokendispenser/Server.java index d5a7024..359b3f9 100644 --- a/src/main/java/com/github/yeriomin/tokendispenser/Server.java +++ b/src/main/java/com/github/yeriomin/tokendispenser/Server.java @@ -5,11 +5,17 @@ import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; import java.util.Properties; import static spark.Spark.after; import static spark.Spark.before; import static spark.Spark.get; +import static spark.Spark.halt; import static spark.Spark.ipAddress; import static spark.Spark.notFound; import static spark.Spark.port; @@ -31,13 +37,13 @@ public class Server { static final String PROPERTY_MONGODB_DB = "mongodb-databaseNameStorage"; static final String PROPERTY_MONGODB_COLLECTION = "mongodb-collectionName"; static final String PROPERTY_EMAIL_RETRIEVAL = "enable-email-retrieval"; + static final String PROPERTY_BASIC_AUTH = "basic-auth"; static public final String STORAGE_MONGODB = "mongodb"; static public final String STORAGE_PLAINTEXT = "plaintext"; static public final String STORAGE_ENV = "env"; - - static public final String ENV_EMAIL = "TOKEN_EMAIL"; - static public final String ENV_PASSWORD = "TOKEN_PASSWORD"; + + static public final String ENV_TOKEN_CREDENTIALS = "TOKEN_CREDENTIALS"; static PasswordsDbInterface passwords; @@ -59,7 +65,34 @@ public static void main(String[] args) { res.header("Access-Control-Request-Method", "GET"); }); after((req, res) -> res.type("text/plain")); + String basicAuth = config.getProperty(PROPERTY_BASIC_AUTH, ""); + if (!basicAuth.equals("")) { + try { + String[] pair = basicAuth.split(":"); + if (pair.length != 2) { + LOG.error(PROPERTY_BASIC_AUTH + " not in the format ':'."); + return; + } + String user = URLDecoder.decode(pair[0], StandardCharsets.UTF_8.name()); + String pass = URLDecoder.decode(pair[1], StandardCharsets.UTF_8.name()); + before((req, res) -> { + if (req.pathInfo().equals("/health")) return; + String header = req.headers("Authorization"); + if (header == null) halt(401, "Access denied."); + String[] parts = header.split(" "); + if (parts.length != 2) halt(400, "Malformed auth header."); + if (!parts[0].equals("Basic")) halt(401, "Unsupported auth method."); + String[] creds = new String(Base64.getDecoder().decode(parts[1])).split(":"); + if (creds.length != 2) halt(400, "Malformed auth header."); + if (!creds[0].equals(user) || !creds[1].equals(pass)) halt(401, "Access denied."); + }); + } catch (UnsupportedEncodingException e) { + Server.LOG.error("UTF-8 is unsupported."); + return; + } + } Server.passwords = PasswordsDbFactory.get(config); + get("/health", (req, res) -> ""); get("/token/email/:email", (req, res) -> new TokenResource().handle(req, res)); get("/token-ac2dm/email/:email", (req, res) -> new TokenAc2dmResource().handle(req, res)); if (config.getProperty(PROPERTY_EMAIL_RETRIEVAL, "false").equals("true")) { @@ -84,6 +117,14 @@ static Properties getConfig() { properties.put(PROPERTY_MONGODB_PASSWORD, System.getenv("OPENSHIFT_MONGODB_DB_PASSWORD")); properties.put(PROPERTY_MONGODB_DB, System.getenv("OPENSHIFT_APP_NAME")); } + String basicAuth = System.getenv(PROPERTY_BASIC_AUTH.replace("-", "_").toUpperCase()); + if (basicAuth != null) { + properties.put(PROPERTY_BASIC_AUTH, basicAuth); + } + String storage = System.getenv(PROPERTY_STORAGE.toUpperCase()); + if (Arrays.asList(STORAGE_MONGODB, STORAGE_PLAINTEXT, STORAGE_ENV).contains(storage)) { + properties.put(PROPERTY_STORAGE, storage); + } return properties; } } diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties index 254de46..b0ba6d5 100644 --- a/src/main/resources/config.properties +++ b/src/main/resources/config.properties @@ -9,3 +9,5 @@ mongodb-password=pwd mongodb-databaseNameStorage=token-dispenser mongodb-collectionName=passwords enable-email-retrieval=true +basic-auth= +