http-service: add account api
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,11 @@
|
||||
<version>1.0.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.scribejava</groupId>
|
||||
<artifactId>scribejava-apis</artifactId>
|
||||
<version>4.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
@@ -114,6 +119,8 @@
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>runelite-${project.version}</finalName>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.tomcat.maven</groupId>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||
* 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<Instant>
|
||||
{
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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<Class, Converter> 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);
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||
* 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 "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -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<XteaEntry> entries = con.createQuery("select * from xtea where rev = :rev")
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user