From 7bac276e2564feb168b05b1c1bc01b7f55e9c37b Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 5 May 2017 23:11:29 -0400 Subject: [PATCH] http-service: add account api --- .../http/api/account/LoginResponse.java | 53 ++++ http-service/pom.xml | 7 + .../http/service/InstantConverter.java | 47 ++++ .../net/runelite/http/service/Service.java | 17 ++ .../runelite/http/service/ServiceModule.java | 58 ++++- .../http/service/account/AccountService.java | 227 ++++++++++++++++++ .../http/service/account/AuthFilter.java | 91 +++++++ .../runelite/http/service/account/State.java | 53 ++++ .../http/service/account/UserInfo.java | 46 ++++ .../service/account/beans/SessionEntry.java | 76 ++++++ .../http/service/account/beans/UserEntry.java | 57 +++++ .../http/service/xtea/XteaService.java | 14 +- .../runelite/http/service/ServiceTest.java | 42 ++-- 13 files changed, 756 insertions(+), 32 deletions(-) create mode 100644 http-api/src/main/java/net/runelite/http/api/account/LoginResponse.java create mode 100644 http-service/src/main/java/net/runelite/http/service/InstantConverter.java create mode 100644 http-service/src/main/java/net/runelite/http/service/account/AccountService.java create mode 100644 http-service/src/main/java/net/runelite/http/service/account/AuthFilter.java create mode 100644 http-service/src/main/java/net/runelite/http/service/account/State.java create mode 100644 http-service/src/main/java/net/runelite/http/service/account/UserInfo.java create mode 100644 http-service/src/main/java/net/runelite/http/service/account/beans/SessionEntry.java create mode 100644 http-service/src/main/java/net/runelite/http/service/account/beans/UserEntry.java diff --git a/http-api/src/main/java/net/runelite/http/api/account/LoginResponse.java b/http-api/src/main/java/net/runelite/http/api/account/LoginResponse.java new file mode 100644 index 0000000000..b4bbf0da0c --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/account/LoginResponse.java @@ -0,0 +1,53 @@ +/* + * 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.account; + +import java.util.UUID; + +public class LoginResponse +{ + private String oauthUrl; + private UUID uid; + + public String getOauthUrl() + { + return oauthUrl; + } + + public void setOauthUrl(String oauthUrl) + { + this.oauthUrl = oauthUrl; + } + + public UUID getUid() + { + return uid; + } + + public void setUid(UUID uid) + { + this.uid = uid; + } +} diff --git a/http-service/pom.xml b/http-service/pom.xml index b546146ab0..f154e66376 100644 --- a/http-service/pom.xml +++ b/http-service/pom.xml @@ -80,6 +80,11 @@ 1.0.0 runtime + + com.github.scribejava + scribejava-apis + 4.1.0 + junit @@ -114,6 +119,8 @@ + runelite-${project.version} + org.apache.tomcat.maven diff --git a/http-service/src/main/java/net/runelite/http/service/InstantConverter.java b/http-service/src/main/java/net/runelite/http/service/InstantConverter.java new file mode 100644 index 0000000000..808479018b --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/InstantConverter.java @@ -0,0 +1,47 @@ +/* + * 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; + +import java.sql.Timestamp; +import java.time.Instant; +import org.sql2o.converters.Converter; +import org.sql2o.converters.ConverterException; + +public class InstantConverter implements Converter +{ + @Override + public Instant convert(Object val) throws ConverterException + { + Timestamp ts = (Timestamp) val; + return ts.toInstant(); + } + + @Override + public Object toDatabaseParam(Instant val) + { + return Timestamp.from(val); + } + +} 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 f3c383df26..38b71dff4d 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 @@ -27,6 +27,8 @@ package net.runelite.http.service; import com.google.inject.Guice; 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.hiscore.HiscoreService; import net.runelite.http.service.updatecheck.UpdateCheckService; import net.runelite.http.service.worlds.WorldsService; @@ -42,6 +44,12 @@ public class Service implements SparkApplication private final JsonTransformer transformer = new JsonTransformer(); + @Inject + private AuthFilter authFilter; + + @Inject + private AccountService accounts; + @Inject private HiscoreService hiscores; @@ -64,6 +72,7 @@ public class Service implements SparkApplication public void setupRoutes() { xtea.init(); + accounts.init(); get("/version", (request, response) -> RuneliteAPI.getVersion()); get("/update-check", updateCheck::check, transformer); @@ -71,6 +80,14 @@ public class Service implements SparkApplication get("/worlds", (request, response) -> worlds.listWorlds(), transformer); post("/xtea", xtea::submit); get("/xtea/:rev", xtea::get, transformer); + path("/account", () -> + { + get("/login", accounts::login, transformer); + get("/callback", accounts::callback); + + before("/logout", authFilter); + get("/logout", accounts::logout); + }); 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 fa572fe090..f7f94f92c0 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 @@ -27,14 +27,22 @@ package net.runelite.http.service; import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.name.Named; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; import javax.naming.Context; import javax.naming.InitialContext; 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.hiscore.HiscoreService; import net.runelite.http.service.updatecheck.UpdateCheckService; import net.runelite.http.service.worlds.WorldsService; import net.runelite.http.service.xtea.XteaService; +import org.sql2o.Sql2o; +import org.sql2o.converters.Converter; +import org.sql2o.quirks.NoQuirks; public class ServiceModule extends AbstractModule { @@ -45,17 +53,56 @@ public class ServiceModule extends AbstractModule this.service = service; } + private Context getContext() throws NamingException + { + Context initCtx = new InitialContext(); + return (Context) initCtx.lookup("java:comp/env"); + } + @Provides @Named("Runelite JDBC") DataSource provideDataSource() { try { - // It is difficult to inject things into Spark - Context initCtx = new InitialContext(); - Context envCtx = (Context) initCtx.lookup("java:comp/env"); + return (DataSource) getContext().lookup("jdbc/runelite"); + } + catch (NamingException ex) + { + throw new RuntimeException(ex); + } + } - return (DataSource) envCtx.lookup("jdbc/runelite"); + @Provides + @Named("Runelite SQL2O") + Sql2o provideSql2o(@Named("Runelite JDBC") DataSource datasource) + { + Map converters = new HashMap<>(); + converters.put(Instant.class, new InstantConverter()); + return new Sql2o(datasource, new NoQuirks(converters)); + } + + @Provides + @Named("OAuth Client ID") + String provideOAuthClientID() + { + try + { + return (String) getContext().lookup("runelite-oauth-client-id"); + } + catch (NamingException ex) + { + throw new RuntimeException(ex); + } + } + + @Provides + @Named("OAuth Client Secret") + String provideOAuthClientSecret() + { + try + { + return (String) getContext().lookup("runelite-oauth-client-secret"); } catch (NamingException ex) { @@ -68,6 +115,9 @@ public class ServiceModule extends AbstractModule { bind(Service.class).toInstance(service); + bind(AuthFilter.class); + + bind(AccountService.class); bind(HiscoreService.class); bind(UpdateCheckService.class); bind(WorldsService.class); diff --git a/http-service/src/main/java/net/runelite/http/service/account/AccountService.java b/http-service/src/main/java/net/runelite/http/service/account/AccountService.java new file mode 100644 index 0000000000..ae82592218 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/account/AccountService.java @@ -0,0 +1,227 @@ +/* + * 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.account; + +import net.runelite.http.service.account.beans.UserEntry; +import net.runelite.http.service.account.beans.SessionEntry; +import com.github.scribejava.apis.GoogleApi20; +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.model.OAuth2AccessToken; +import com.github.scribejava.core.model.OAuthRequest; +import com.github.scribejava.core.model.Verb; +import com.github.scribejava.core.oauth.OAuth20Service; +import com.google.gson.Gson; +import com.google.inject.Inject; +import com.google.inject.name.Named; +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import net.runelite.http.api.RuneliteAPI; +import net.runelite.http.api.account.LoginResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sql2o.Connection; +import org.sql2o.Sql2o; +import org.sql2o.Sql2oException; +import spark.Request; +import spark.Response; + +public class AccountService +{ + private static final Logger logger = LoggerFactory.getLogger(AccountService.class); + + private static final String CREATE_SESSIONS = "CREATE TABLE IF NOT EXISTS `sessions` (\n" + + " `user` int(11) NOT NULL,\n" + + " `uuid` varchar(36) NOT NULL,\n" + + " `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n" + + " `last_used` timestamp NOT NULL,\n" + + " UNIQUE KEY `user_2` (`user`,`uuid`),\n" + + " KEY `user` (`user`)\n" + + ") ENGINE=InnoDB"; + + private static final String CREATE_USERS = "CREATE TABLE IF NOT EXISTS `users` (\n" + + " `id` int(11) NOT NULL,\n" + + " `username` tinytext NOT NULL,\n" + + " `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n" + + " PRIMARY KEY (`id`),\n" + + " UNIQUE KEY `username` (`username`(64))\n" + + ") ENGINE=InnoDB"; + + private static final String SESSIONS_FK = "ALTER TABLE `sessions`\n" + + " ADD CONSTRAINT `id` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;"; + + private static final String SCOPE = "https://www.googleapis.com/auth/userinfo.email"; + private static final String USERINFO = "https://www.googleapis.com/oauth2/v2/userinfo"; + private static final String RL_OAUTH_URL = "https://api.runelite.net/oauth/"; + private static final String RL_REDIR = "http://runelite.net/logged-in"; + + private final Gson gson = new Gson(); + + private final Sql2o sql2o; + private final String oauthClientId; + private final String oauthClientSecret; + + @Inject + public AccountService( + @Named("Runelite SQL2O") Sql2o sql2o, + @Named("OAuth Client ID") String oauthClientId, + @Named("OAuth Client Secret") String oauthClientSecret + ) + { + this.sql2o = sql2o; + this.oauthClientId = oauthClientId; + this.oauthClientSecret = oauthClientSecret; + } + + public void init() + { + try (Connection con = sql2o.beginTransaction()) + { + con.createQuery(CREATE_SESSIONS) + .executeUpdate(); + + con.createQuery(CREATE_USERS) + .executeUpdate(); + + try + { + con.createQuery(SESSIONS_FK) + .executeUpdate(); + } + catch (Sql2oException ex) + { + // Ignore, happens when index already exists + } + } + } + + public LoginResponse login(Request request, Response response) + { + UUID uuid = UUID.randomUUID(); + + State state = new State(); + state.setUuid(uuid); + state.setApiVersion(RuneliteAPI.getVersion()); + + OAuth20Service service = new ServiceBuilder() + .apiKey(oauthClientId) + .apiSecret(oauthClientSecret) + .scope(SCOPE) + .callback(RL_OAUTH_URL) + .state(gson.toJson(state)) + .build(GoogleApi20.instance()); + + String authorizationUrl = service.getAuthorizationUrl(); + + LoginResponse lr = new LoginResponse(); + lr.setOauthUrl(authorizationUrl); + lr.setUid(uuid); + + return lr; + } + + public Object callback(Request request, Response response) throws IOException, InterruptedException, ExecutionException + { + String error = request.queryParams("error"); + + if (error != null) + { + logger.info("Error in oauth callback: {}", error); + return null; + } + + String authorizationCode = request.queryParams("code"); + State state = gson.fromJson(request.queryParams("state"), State.class); + + logger.info("Got authorization code {} for uuid {}", authorizationCode, state.getUuid()); + + OAuth20Service service = new ServiceBuilder() + .apiKey(oauthClientId) + .apiSecret(oauthClientSecret) + .scope(SCOPE) + .callback(RL_OAUTH_URL) + .state(gson.toJson(state)) + .build(GoogleApi20.instance()); + + OAuth2AccessToken accessToken = service.getAccessToken(authorizationCode); + + // Access user info + OAuthRequest orequest = new OAuthRequest(Verb.GET, USERINFO); + service.signRequest(accessToken, orequest); + + com.github.scribejava.core.model.Response oresponse = service.execute(orequest); + + if (oresponse.getCode() / 100 != 2) + { + // Could be a forged result + return null; + } + + UserInfo userInfo = gson.fromJson(oresponse.getBody(), UserInfo.class); + + logger.info("Got user info: {}", userInfo); + + try (Connection con = sql2o.open()) + { + con.createQuery("insert ignore into users (username) values (:username)") + .addParameter("username", userInfo.getEmail()) + .executeUpdate(); + + UserEntry user = con.createQuery("select id from users where username = :username") + .addParameter("username", userInfo.getEmail()) + .executeAndFetchFirst(UserEntry.class); + + if (user == null) + { + return null; // that's weird + } + + // insert session + con.createQuery("insert ignore into sessions (user, uuid) values (:user, :uuid)") + .addParameter("user", user.getId()) + .addParameter("uuid", state.getUuid().toString()) + .executeUpdate(); + + logger.info("Created session for user {}", user.getUsername()); + } + + response.redirect(RL_REDIR); + return ""; + } + + public Object logout(Request request, Response response) + { + SessionEntry session = request.session().attribute("session"); + + try (Connection con = sql2o.open()) + { + con.createQuery("delete from sessions where uuid = :uuid") + .addParameter("uuid", session.getUuid().toString()) + .executeUpdate(); + } + + return ""; + } +} 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 new file mode 100644 index 0000000000..4521565e09 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/account/AuthFilter.java @@ -0,0 +1,91 @@ +/* + * 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.account; + +import net.runelite.http.service.account.beans.SessionEntry; +import com.google.inject.Inject; +import com.google.inject.name.Named; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.UUID; +import org.sql2o.Connection; +import org.sql2o.Sql2o; +import spark.Filter; +import spark.Request; +import spark.Response; +import spark.Session; +import static spark.Spark.halt; + +public class AuthFilter implements Filter +{ + private static final String RUNELITE_AUTH = "RUNELITE-AUTH"; + + private final Sql2o sql2o; + + @Inject + public AuthFilter(@Named("Runelite SQL2O") Sql2o sql2o) + { + this.sql2o = sql2o; + } + + @Override + public void handle(Request request, Response response) throws Exception + { + String runeliteAuth = request.headers(RUNELITE_AUTH); + if (runeliteAuth == null) + { + halt(401, "Access denied"); + return; + } + + UUID uuid = UUID.fromString(runeliteAuth); + + try (Connection con = sql2o.open()) + { + SessionEntry sessionEntry = con.createQuery("select user, uuid, created from sessions where uuid = :uuid") + .addParameter("uuid", uuid.toString()) + .executeAndFetchFirst(SessionEntry.class); + + if (sessionEntry == null) + { + halt(401, "Access denied"); + return; + } + + Instant now = Instant.now(); + + con.createQuery("update sessions set last_used = :last_used where uuid = :uuid") + .addParameter("last_used", Timestamp.from(now)) + .addParameter("uuid", uuid.toString()) + .executeUpdate(); + + sessionEntry.setLastUsed(now); + + Session session = request.session(); + session.attribute("session", sessionEntry); + } + } + +} diff --git a/http-service/src/main/java/net/runelite/http/service/account/State.java b/http-service/src/main/java/net/runelite/http/service/account/State.java new file mode 100644 index 0000000000..50b47b2c19 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/account/State.java @@ -0,0 +1,53 @@ +/* + * 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.account; + +import java.util.UUID; + +public class State +{ + private UUID uuid; + private String apiVersion; + + public UUID getUuid() + { + return uuid; + } + + public void setUuid(UUID uuid) + { + this.uuid = uuid; + } + + public String getApiVersion() + { + return apiVersion; + } + + public void setApiVersion(String apiVersion) + { + this.apiVersion = apiVersion; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/account/UserInfo.java b/http-service/src/main/java/net/runelite/http/service/account/UserInfo.java new file mode 100644 index 0000000000..a1cde03f79 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/account/UserInfo.java @@ -0,0 +1,46 @@ +/* + * 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.account; + +public class UserInfo +{ + private String email; + + @Override + public String toString() + { + return "UserInfo{" + "email=" + email + '}'; + } + + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/account/beans/SessionEntry.java b/http-service/src/main/java/net/runelite/http/service/account/beans/SessionEntry.java new file mode 100644 index 0000000000..ded67c7a45 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/account/beans/SessionEntry.java @@ -0,0 +1,76 @@ +/* + * 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.account.beans; + +import java.time.Instant; +import java.util.UUID; + +public class SessionEntry +{ + private int user; + private UUID uuid; + private Instant created; + private Instant lastUsed; + + public int getUser() + { + return user; + } + + public void setUser(int user) + { + this.user = user; + } + + public UUID getUuid() + { + return uuid; + } + + public void setUuid(UUID uuid) + { + this.uuid = uuid; + } + + public Instant getCreated() + { + return created; + } + + public void setCreated(Instant created) + { + this.created = created; + } + + public Instant getLastUsed() + { + return lastUsed; + } + + public void setLastUsed(Instant lastUsed) + { + this.lastUsed = lastUsed; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/account/beans/UserEntry.java b/http-service/src/main/java/net/runelite/http/service/account/beans/UserEntry.java new file mode 100644 index 0000000000..83dd4152ac --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/account/beans/UserEntry.java @@ -0,0 +1,57 @@ +/* + * 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.account.beans; + +public class UserEntry +{ + private int id; + private String username; + + @Override + public String toString() + { + return "UserEntry{" + "id=" + id + ", username=" + username + '}'; + } + + public int getId() + { + return id; + } + + public void setId(int id) + { + this.id = id; + } + + public String getUsername() + { + return username; + } + + public void setUsername(String username) + { + this.username = username; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/xtea/XteaService.java b/http-service/src/main/java/net/runelite/http/service/xtea/XteaService.java index 3863ac02c3..a057b4da24 100644 --- a/http-service/src/main/java/net/runelite/http/service/xtea/XteaService.java +++ b/http-service/src/main/java/net/runelite/http/service/xtea/XteaService.java @@ -29,7 +29,6 @@ import com.google.inject.Inject; import com.google.inject.name.Named; import java.util.List; import java.util.stream.Collectors; -import javax.sql.DataSource; import net.runelite.http.api.xtea.XteaKey; import net.runelite.http.api.xtea.XteaRequest; import org.slf4j.Logger; @@ -54,20 +53,17 @@ public class XteaService + " PRIMARY KEY (`rev`,`region`,`key1`,`key2`,`key3`,`key4`)\n" + ") ENGINE=InnoDB;"; - - private final DataSource datasource; + private final Sql2o sql2o; private final Gson gson = new Gson(); @Inject - public XteaService(@Named("Runelite JDBC") DataSource datasource) + public XteaService(@Named("Runelite SQL2O") Sql2o sql2o) { - this.datasource = datasource; + this.sql2o = sql2o; } public void init() { - Sql2o sql2o = new Sql2o(datasource); - try (Connection con = sql2o.beginTransaction()) { con.createQuery(CREATE_SQL) @@ -79,8 +75,6 @@ public class XteaService { XteaRequest xteaRequest = gson.fromJson(request.body(), XteaRequest.class); - Sql2o sql2o = new Sql2o(datasource); - try (Connection con = sql2o.beginTransaction()) { Query query = con.createQuery("insert ignore into xtea (rev, region, key1, key2, key3, key4) values (:rev, :region, :key1, :key2, :key3, :key4)"); @@ -108,8 +102,6 @@ public class XteaService String revStr = request.params("rev"); int revision = Integer.parseInt(revStr); - Sql2o sql2o = new Sql2o(datasource); - try (Connection con = sql2o.open()) { List entries = con.createQuery("select * from xtea where rev = :rev") diff --git a/http-service/src/test/java/net/runelite/http/service/ServiceTest.java b/http-service/src/test/java/net/runelite/http/service/ServiceTest.java index cbd0912258..4bc2d48489 100644 --- a/http-service/src/test/java/net/runelite/http/service/ServiceTest.java +++ b/http-service/src/test/java/net/runelite/http/service/ServiceTest.java @@ -35,7 +35,6 @@ import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import javax.servlet.ServletContext; -import javax.sql.DataSource; import net.runelite.http.api.hiscore.HiscoreResult; import net.runelite.http.api.hiscore.Skill; import net.runelite.http.service.hiscore.HiscoreService; @@ -48,23 +47,32 @@ import org.mockito.Mock; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.mockito.MockitoAnnotations; +import org.sql2o.Sql2o; import spark.Spark; public class ServiceTest { private static final String URL_BASE = "http://localhost:4567"; - + private Service service; - + @Bind @Mock(answer = Answers.RETURNS_DEEP_STUBS) - @Named("Runelite JDBC") - private DataSource dataSource; - + @Named("Runelite SQL2O") + private Sql2o sql2o; + + @Bind + @Named("OAuth Client ID") + private String clientId = "test"; + + @Bind + @Named("OAuth Client Secret") + private String clientSecret = "test"; + @Bind @Mock private HiscoreService hiscoreService; - + @Before public void before() { @@ -73,38 +81,38 @@ public class ServiceTest // Inject everything in the test object Injector injector = Guice.createInjector(BoundFieldModule.of(this)); injector.injectMembers(this); - + ServletContextLogger.setServletContext(mock(ServletContext.class)); - + service = injector.getInstance(Service.class); service.setupRoutes(); - + Spark.awaitInitialization(); } - + @After public void after() { Spark.stop(); } - + @Test public void testInit() throws Exception { HiscoreResult result = new HiscoreResult(); result.setAttack(new Skill(1, 99, 42)); - + when(hiscoreService.lookup("zezima")).thenReturn(result); - + URL url = new URL(URL_BASE + "/hiscore?username=zezima"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.connect(); - + Gson gson = new Gson(); HiscoreResult res = gson.fromJson(new InputStreamReader(connection.getInputStream()), HiscoreResult.class); - + Assert.assertEquals(result, res); } - + }