From 45640a34beff19eda4c52f1e14bc97dba36eb8f1 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 16 May 2017 16:24:50 -0400 Subject: [PATCH] http-service: add config service --- .../net/runelite/http/api/RuneliteAPI.java | 2 + .../http/api/config/ConfigClient.java | 115 ++++++++++++++++ .../runelite/http/api/config/ConfigEntry.java | 51 +++++++ .../http/api/config/Configuration.java | 43 ++++++ .../net/runelite/http/service/Service.java | 15 +++ .../runelite/http/service/ServiceModule.java | 2 + .../http/service/account/AuthFilter.java | 5 +- .../http/service/config/ConfigService.java | 124 ++++++++++++++++++ 8 files changed, 354 insertions(+), 3 deletions(-) create mode 100644 http-api/src/main/java/net/runelite/http/api/config/ConfigClient.java create mode 100644 http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java create mode 100644 http-api/src/main/java/net/runelite/http/api/config/Configuration.java create mode 100644 http-service/src/main/java/net/runelite/http/service/config/ConfigService.java diff --git a/http-api/src/main/java/net/runelite/http/api/RuneliteAPI.java b/http-api/src/main/java/net/runelite/http/api/RuneliteAPI.java index f3f0cbbeaa..80499ca8d5 100644 --- a/http-api/src/main/java/net/runelite/http/api/RuneliteAPI.java +++ b/http-api/src/main/java/net/runelite/http/api/RuneliteAPI.java @@ -37,6 +37,8 @@ public class RuneliteAPI { private static final Logger logger = LoggerFactory.getLogger(RuneliteAPI.class); + public static final String RUNELITE_AUTH = "RUNELITE-AUTH"; + public static final OkHttpClient CLIENT = new OkHttpClient(); public static final Gson GSON = new Gson(); diff --git a/http-api/src/main/java/net/runelite/http/api/config/ConfigClient.java b/http-api/src/main/java/net/runelite/http/api/config/ConfigClient.java new file mode 100644 index 0000000000..7ff535aca9 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/config/ConfigClient.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.api.config; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.UUID; +import net.runelite.http.api.RuneliteAPI; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ConfigClient +{ + private static final Logger logger = LoggerFactory.getLogger(ConfigClient.class); + + private static final MediaType TEXT_PLAIN = MediaType.parse("text/plain"); + + private final UUID uuid; + + public ConfigClient(UUID uuid) + { + this.uuid = uuid; + } + + public Configuration get() throws IOException + { + HttpUrl url = RuneliteAPI.getApiBase().newBuilder() + .addPathSegment("config") + .build(); + + logger.debug("Built URI: {}", url); + + Request request = new Request.Builder() + .header(RuneliteAPI.RUNELITE_AUTH, uuid.toString()) + .url(url) + .build(); + + Response response = RuneliteAPI.CLIENT.newCall(request).execute(); + + try (ResponseBody body = response.body()) + { + InputStream in = body.byteStream(); + return RuneliteAPI.GSON.fromJson(new InputStreamReader(in), Configuration.class); + } + } + + public void set(String key, String value) throws IOException + { + HttpUrl url = RuneliteAPI.getApiBase().newBuilder() + .addPathSegment("config") + .addPathSegment(key) + .build(); + + logger.debug("Built URI: {}", url); + + Request request = new Request.Builder() + .put(RequestBody.create(TEXT_PLAIN, value)) + .header(RuneliteAPI.RUNELITE_AUTH, uuid.toString()) + .build(); + + try (Response response = RuneliteAPI.CLIENT.newCall(request).execute()) + { + logger.debug("Set configuration value '{}' to '{}'", key, value); + } + } + + public void unset(String key) throws IOException + { + HttpUrl url = RuneliteAPI.getApiBase().newBuilder() + .addPathSegment("config") + .addPathSegment(key) + .build(); + + logger.debug("Built URI: {}", url); + + Request request = new Request.Builder() + .delete() + .header(RuneliteAPI.RUNELITE_AUTH, uuid.toString()) + .build(); + + try (Response response = RuneliteAPI.CLIENT.newCall(request).execute()) + { + logger.debug("Unset configuration value '{}'", key); + } + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java b/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java new file mode 100644 index 0000000000..783d68361e --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.api.config; + +public class ConfigEntry +{ + private String key; + private String value; + + public String getKey() + { + return key; + } + + public void setKey(String key) + { + this.key = key; + } + + public String getValue() + { + return value; + } + + public void setValue(String value) + { + this.value = value; + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/config/Configuration.java b/http-api/src/main/java/net/runelite/http/api/config/Configuration.java new file mode 100644 index 0000000000..7b256e0c7a --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/config/Configuration.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.api.config; + +import java.util.ArrayList; +import java.util.List; + +public class Configuration +{ + private List config = new ArrayList<>(); + + public Configuration(List config) + { + this.config = config; + } + + public List getConfig() + { + return config; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/Service.java b/http-service/src/main/java/net/runelite/http/service/Service.java index 38b71dff4d..5349f947a0 100644 --- a/http-service/src/main/java/net/runelite/http/service/Service.java +++ b/http-service/src/main/java/net/runelite/http/service/Service.java @@ -29,6 +29,7 @@ import com.google.inject.Inject; import net.runelite.http.api.RuneliteAPI; import net.runelite.http.service.account.AccountService; import net.runelite.http.service.account.AuthFilter; +import net.runelite.http.service.config.ConfigService; import net.runelite.http.service.hiscore.HiscoreService; import net.runelite.http.service.updatecheck.UpdateCheckService; import net.runelite.http.service.worlds.WorldsService; @@ -50,6 +51,9 @@ public class Service implements SparkApplication @Inject private AccountService accounts; + @Inject + private ConfigService config; + @Inject private HiscoreService hiscores; @@ -73,6 +77,7 @@ public class Service implements SparkApplication { xtea.init(); accounts.init(); + config.init(); get("/version", (request, response) -> RuneliteAPI.getVersion()); get("/update-check", updateCheck::check, transformer); @@ -88,6 +93,16 @@ public class Service implements SparkApplication before("/logout", authFilter); get("/logout", accounts::logout); }); + before("/config", authFilter); + path("/config", () -> + { + // Just using before(authFilter); here doesn't work + before("/*", authFilter); + + get("", config::get, transformer); + put("/:key", config::setKey); + delete("/:key", config::unsetKey); + }); exception(Exception.class, (exception, request, response) -> logger.warn(null, exception)); } diff --git a/http-service/src/main/java/net/runelite/http/service/ServiceModule.java b/http-service/src/main/java/net/runelite/http/service/ServiceModule.java index f7f94f92c0..73bc1d3e1f 100644 --- a/http-service/src/main/java/net/runelite/http/service/ServiceModule.java +++ b/http-service/src/main/java/net/runelite/http/service/ServiceModule.java @@ -36,6 +36,7 @@ import javax.naming.NamingException; import javax.sql.DataSource; import net.runelite.http.service.account.AccountService; import net.runelite.http.service.account.AuthFilter; +import net.runelite.http.service.config.ConfigService; import net.runelite.http.service.hiscore.HiscoreService; import net.runelite.http.service.updatecheck.UpdateCheckService; import net.runelite.http.service.worlds.WorldsService; @@ -118,6 +119,7 @@ public class ServiceModule extends AbstractModule bind(AuthFilter.class); bind(AccountService.class); + bind(ConfigService.class); bind(HiscoreService.class); bind(UpdateCheckService.class); bind(WorldsService.class); diff --git a/http-service/src/main/java/net/runelite/http/service/account/AuthFilter.java b/http-service/src/main/java/net/runelite/http/service/account/AuthFilter.java index 4521565e09..d4ab7a70e7 100644 --- a/http-service/src/main/java/net/runelite/http/service/account/AuthFilter.java +++ b/http-service/src/main/java/net/runelite/http/service/account/AuthFilter.java @@ -30,6 +30,7 @@ import com.google.inject.name.Named; import java.sql.Timestamp; import java.time.Instant; import java.util.UUID; +import net.runelite.http.api.RuneliteAPI; import org.sql2o.Connection; import org.sql2o.Sql2o; import spark.Filter; @@ -40,8 +41,6 @@ import static spark.Spark.halt; public class AuthFilter implements Filter { - private static final String RUNELITE_AUTH = "RUNELITE-AUTH"; - private final Sql2o sql2o; @Inject @@ -53,7 +52,7 @@ public class AuthFilter implements Filter @Override public void handle(Request request, Response response) throws Exception { - String runeliteAuth = request.headers(RUNELITE_AUTH); + String runeliteAuth = request.headers(RuneliteAPI.RUNELITE_AUTH); if (runeliteAuth == null) { halt(401, "Access denied"); diff --git a/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java b/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java new file mode 100644 index 0000000000..6f30ae99de --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.config; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import java.util.List; +import net.runelite.http.api.config.ConfigEntry; +import net.runelite.http.api.config.Configuration; +import net.runelite.http.service.account.beans.SessionEntry; +import org.sql2o.Connection; +import org.sql2o.Sql2o; +import org.sql2o.Sql2oException; +import spark.Request; +import spark.Response; + +public class ConfigService +{ + private static final String CREATE_CONFIG = "CREATE TABLE IF NOT EXISTS `config` (\n" + + " `user` int(11) NOT NULL,\n" + + " `key` tinytext NOT NULL,\n" + + " `value` text NOT NULL,\n" + + " UNIQUE KEY `user_key` (`user`,`key`(64))\n" + + ") ENGINE=InnoDB;"; + + private static final String CONFIG_FK = "ALTER TABLE `config`\n" + + " ADD CONSTRAINT `user_fk` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;"; + + private final Sql2o sql2o; + + @Inject + public ConfigService(@Named("Runelite SQL2O") Sql2o sql2o) + { + this.sql2o = sql2o; + } + + public void init() + { + try (Connection con = sql2o.open()) + { + con.createQuery(CREATE_CONFIG) + .executeUpdate(); + + try + { + con.createQuery(CONFIG_FK) + .executeUpdate(); + } + catch (Sql2oException ex) + { + // Ignore, happens when index already exists + } + } + } + + public Configuration get(Request request, Response response) + { + SessionEntry session = request.session().attribute("session"); + + List config; + + try (Connection con = sql2o.open()) + { + config = con.createQuery("select `key`, value from config where user = :user") + .addParameter("user", session.getUser()) + .executeAndFetch(ConfigEntry.class); + } + + return new Configuration(config); + } + + public Object setKey(Request request, Response response) + { + SessionEntry session = request.session().attribute("session"); + + try (Connection con = sql2o.open()) + { + con.createQuery("insert into config (user, `key`, value) values (:user, :key, :value) on duplicate key update value = :value") + .addParameter("user", session.getUser()) + .addParameter("key", request.params("key")) + .addParameter("value", request.body()) + .executeUpdate(); + } + + return ""; + } + + public Object unsetKey(Request request, Response response) + { + SessionEntry session = request.session().attribute("session"); + + try (Connection con = sql2o.open()) + { + con.createQuery("delete from config where user = :user and `key` = :key") + .addParameter("user", session.getUser()) + .addParameter("key", request.params("key")) + .executeUpdate(); + } + + return ""; + } +}