parent
055672468d
commit
c459ca8066
2
pom.xml
2
pom.xml
|
@ -3,7 +3,7 @@
|
|||
<groupId>xyz.etztech</groupId>
|
||||
<artifactId>ServerAPI</artifactId>
|
||||
<!-- Version is used in plugin.yml -->
|
||||
<version>0.0.2</version>
|
||||
<version>0.0.3</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<!-- Plugin Information -->
|
||||
|
|
|
@ -3,11 +3,13 @@ package xyz.etztech.serverapi;
|
|||
|
||||
import org.bukkit.BanEntry;
|
||||
import org.bukkit.BanList;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import xyz.etztech.serverapi.commands.MainCommand;
|
||||
import xyz.etztech.serverapi.token.TokenList;
|
||||
import xyz.etztech.serverapi.tps.TPS;
|
||||
import xyz.etztech.serverapi.web.IProvider;
|
||||
import xyz.etztech.serverapi.web.Web;
|
||||
|
@ -49,7 +51,8 @@ public class ServerAPI extends JavaPlugin implements IProvider {
|
|||
web.stop();
|
||||
web.start(
|
||||
getConfig().getInt("port", 8080),
|
||||
getConfig().getString("password", "")
|
||||
new TokenList(getConfig().getConfigurationSection("auth")),
|
||||
getConfig().getStringList("custom")
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -107,6 +110,44 @@ public class ServerAPI extends JavaPlugin implements IProvider {
|
|||
return bans;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(BanAPI kick) {
|
||||
Player player = Bukkit.getPlayerExact(kick.getTarget());
|
||||
if (player != null) {
|
||||
player.kickPlayer("You have been kicked: " + kick.getReason());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ban(BanAPI ban) {
|
||||
Date expires = null;
|
||||
if (ban.getExpiration() != 0) {
|
||||
expires = new Date(ban.getExpiration());
|
||||
}
|
||||
Bukkit.getBanList(BanList.Type.NAME).addBan(ban.getTarget(), ban.getReason(), expires, "ServerAPI");
|
||||
Player player = Bukkit.getPlayerExact(ban.getTarget());
|
||||
if (player != null) {
|
||||
player.kickPlayer("You have been banned: " + ban.getReason());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unban(BanAPI ban) {
|
||||
Bukkit.getBanList(BanList.Type.NAME).pardon(ban.getTarget());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcast(BroadcastAPI broadcast) {
|
||||
Bukkit.broadcastMessage(String.format("%s > %s", broadcast.getFrom(), broadcast.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void custom(CustomAPI custom) {
|
||||
getServer().getScheduler().runTask(this, () -> {
|
||||
getServer().dispatchCommand(getServer().getConsoleSender(), custom.build());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public PingAPI ping() {
|
||||
return PingAPI.fromMinecraft(getServer());
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package xyz.etztech.serverapi.token;
|
||||
|
||||
public class Token {
|
||||
private final String token;
|
||||
private final TokenScope scope;
|
||||
|
||||
public Token(String token, TokenScope scope) {
|
||||
this.token = token;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public TokenScope getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public boolean canGET() {
|
||||
return scope == TokenScope.GET || scope == TokenScope.ALL;
|
||||
}
|
||||
|
||||
public boolean canPOST() {
|
||||
return scope == TokenScope.POST || scope == TokenScope.ALL;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package xyz.etztech.serverapi.token;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TokenList {
|
||||
private final boolean protectGET;
|
||||
private final boolean protectPOST;
|
||||
private final List<Token> tokens;
|
||||
|
||||
public TokenList(boolean protectGET, boolean protectPOST, List<Token> tokens) {
|
||||
this.protectGET = protectGET;
|
||||
this.protectPOST = protectPOST;
|
||||
this.tokens = tokens;
|
||||
}
|
||||
|
||||
public TokenList(ConfigurationSection auth) {
|
||||
if (auth == null) {
|
||||
this.protectGET = false;
|
||||
this.protectPOST = false;
|
||||
this.tokens = null;
|
||||
return;
|
||||
}
|
||||
this.protectGET = auth.getBoolean("get", true);
|
||||
this.protectPOST = auth.getBoolean("post", true);
|
||||
this.tokens = new ArrayList<>();
|
||||
ConfigurationSection tokenSection = auth.getConfigurationSection("tokens");
|
||||
if (tokenSection == null) {
|
||||
return;
|
||||
}
|
||||
for (String token : tokenSection.getKeys(false)) {
|
||||
this.tokens.add(new Token(token, TokenScope.parseScope(tokenSection.getString(token, "none"))));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isProtectGET() {
|
||||
return protectGET;
|
||||
}
|
||||
|
||||
public boolean isProtectPOST() {
|
||||
return protectPOST;
|
||||
}
|
||||
|
||||
public List<Token> getTokens() {
|
||||
return tokens;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package xyz.etztech.serverapi.token;
|
||||
|
||||
public enum TokenScope {
|
||||
NONE,
|
||||
GET,
|
||||
POST,
|
||||
ALL;
|
||||
|
||||
public static TokenScope parseScope(String scope) {
|
||||
if (scope == null) {
|
||||
return NONE;
|
||||
}
|
||||
switch (scope.toLowerCase()) {
|
||||
case "get":
|
||||
return GET;
|
||||
case "post":
|
||||
return POST;
|
||||
case "all":
|
||||
return ALL;
|
||||
default:
|
||||
return NONE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
|
||||
public interface IProvider {
|
||||
|
||||
// GET
|
||||
Set<BanAPI> bans();
|
||||
Set<PlayerAPI> players();
|
||||
PingAPI ping();
|
||||
|
@ -14,5 +16,13 @@ public interface IProvider {
|
|||
WorldAPI world(String name);
|
||||
List<PluginAPI> plugins();
|
||||
|
||||
// POST
|
||||
void kick(BanAPI kick);
|
||||
void ban(BanAPI ban);
|
||||
void unban(BanAPI ban);
|
||||
void broadcast(BroadcastAPI broadcast);
|
||||
void custom(CustomAPI custom);
|
||||
|
||||
// MISC
|
||||
void log(String message);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import io.javalin.Javalin;
|
|||
import io.javalin.core.event.EventListener;
|
||||
import io.javalin.core.plugin.Plugin;
|
||||
import io.javalin.core.util.RouteOverviewPlugin;
|
||||
import io.javalin.http.BadRequestResponse;
|
||||
import io.javalin.http.Context;
|
||||
import io.javalin.http.UnauthorizedResponse;
|
||||
import io.javalin.plugin.graphql.GraphQLOptions;
|
||||
|
@ -12,18 +13,25 @@ import io.javalin.plugin.json.JavalinJson;
|
|||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.helpers.NOPLogger;
|
||||
import xyz.etztech.serverapi.ServerAPI;
|
||||
import xyz.etztech.serverapi.token.TokenList;
|
||||
import xyz.etztech.serverapi.web.api.BanAPI;
|
||||
import xyz.etztech.serverapi.web.api.BroadcastAPI;
|
||||
import xyz.etztech.serverapi.web.api.CustomAPI;
|
||||
import xyz.etztech.serverapi.web.api.ErrorAPI;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Web {
|
||||
private final IProvider provider;
|
||||
private String password;
|
||||
private TokenList tokens;
|
||||
private List<String> custom;
|
||||
private Javalin app;
|
||||
|
||||
public Web(IProvider provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public void start(int port, String password) {
|
||||
public void start(int port, TokenList tokens, List<String> custom) {
|
||||
Javalin.log = NOPLogger.NOP_LOGGER;
|
||||
app = Javalin.create(config -> {
|
||||
config.registerPlugin(graphql());
|
||||
|
@ -31,8 +39,14 @@ public class Web {
|
|||
config.enableCorsForAllOrigins();
|
||||
}).events(this::events).exception(Exception.class, this::exception);
|
||||
|
||||
this.password = password;
|
||||
if (!"".equals(password)) {
|
||||
this.custom = custom;
|
||||
|
||||
this.tokens = tokens;
|
||||
if (tokens != null) {
|
||||
provider.log(String.format("Loaded %d tokens", tokens.getTokens().size()));
|
||||
if (!tokens.isProtectPOST()) {
|
||||
provider.log("WARNING: You have disabled POST protection, which can enable users to arbitrarily run actions against your server.");
|
||||
}
|
||||
app.before(this::access);
|
||||
}
|
||||
|
||||
|
@ -46,6 +60,12 @@ public class Web {
|
|||
app.get("/worlds", this::worlds);
|
||||
app.get("/worlds/:name", this::world);
|
||||
|
||||
app.post("/kick", this::kick);
|
||||
app.post("/ban", this::ban);
|
||||
app.post("/unban", this::unban);
|
||||
app.post("/broadcast", this::broadcast);
|
||||
app.post("/custom", this::custom);
|
||||
|
||||
// What in the actual fuck... https://github.com/tipsy/javalin/issues/358
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(ServerAPI.class.getClassLoader());
|
||||
|
@ -77,13 +97,22 @@ public class Web {
|
|||
}
|
||||
|
||||
public void access(Context ctx) {
|
||||
String pw = ctx.header("X-ServerAPI-Password");
|
||||
if (pw == null) {
|
||||
pw = ctx.queryParam("password");
|
||||
String token = ctx.header("X-ServerAPI-Token");
|
||||
if (token == null) {
|
||||
token = ctx.queryParam("token");
|
||||
}
|
||||
boolean isGET = "GET".equalsIgnoreCase(ctx.method());
|
||||
boolean isPOST = "POST".equalsIgnoreCase(ctx.method());
|
||||
|
||||
if (!password.equals(pw)) {
|
||||
throw new UnauthorizedResponse(JavalinJson.toJson(new ErrorAPI(401, "Unauthorized")));
|
||||
if (isGET && !tokens.isProtectGET()) return;
|
||||
if (isPOST && !tokens.isProtectPOST()) return;
|
||||
|
||||
String finalToken = token;
|
||||
boolean pass = tokens.getTokens().stream().anyMatch(t -> t.getToken().equals(finalToken) &&
|
||||
((isGET && t.canGET()) || (isPOST && t.canPOST())));
|
||||
|
||||
if (!pass) {
|
||||
throw new UnauthorizedResponse();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,4 +150,33 @@ public class Web {
|
|||
public void ping(Context ctx) {
|
||||
ctx.json(provider.ping());
|
||||
}
|
||||
|
||||
public void kick(Context ctx) {
|
||||
provider.kick(JavalinJson.fromJson(ctx.body(), BanAPI.class));
|
||||
ctx.status(200);
|
||||
}
|
||||
|
||||
public void ban(Context ctx) {
|
||||
provider.ban(JavalinJson.fromJson(ctx.body(), BanAPI.class));
|
||||
ctx.status(200);
|
||||
}
|
||||
|
||||
public void unban(Context ctx) {
|
||||
provider.unban(JavalinJson.fromJson(ctx.body(), BanAPI.class));
|
||||
ctx.status(200);
|
||||
}
|
||||
|
||||
public void broadcast(Context ctx) {
|
||||
provider.broadcast(JavalinJson.fromJson(ctx.body(), BroadcastAPI.class));
|
||||
ctx.status(200);
|
||||
}
|
||||
|
||||
public void custom(Context ctx) {
|
||||
CustomAPI capi = JavalinJson.fromJson(ctx.body(), CustomAPI.class);
|
||||
if (custom.stream().noneMatch(c -> c.equalsIgnoreCase(capi.getCommand()))) {
|
||||
throw new BadRequestResponse();
|
||||
}
|
||||
provider.custom(capi);
|
||||
ctx.status(200);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,14 @@ public class BanAPI {
|
|||
private final long created;
|
||||
private final long expiration;
|
||||
|
||||
public BanAPI() {
|
||||
this.target = "";
|
||||
this.source = "";
|
||||
this.reason = "";
|
||||
this.created = 0;
|
||||
this.expiration = 0;
|
||||
}
|
||||
|
||||
public BanAPI(String target, String source, String reason, long created, long expiration) {
|
||||
this.target = target;
|
||||
this.source = source;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package xyz.etztech.serverapi.web.api;
|
||||
|
||||
public class BroadcastAPI {
|
||||
private final String from;
|
||||
private final String message;
|
||||
|
||||
public BroadcastAPI() {
|
||||
this.from = "";
|
||||
this.message = "";
|
||||
}
|
||||
|
||||
public BroadcastAPI(String from, String message) {
|
||||
this.from = from;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package xyz.etztech.serverapi.web.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CustomAPI {
|
||||
private final String command;
|
||||
private final List<String> args;
|
||||
|
||||
public CustomAPI() {
|
||||
this.command = "";
|
||||
this.args = new ArrayList<>();
|
||||
}
|
||||
|
||||
public CustomAPI(String command, List<String> args) {
|
||||
this.command = command;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public List<String> getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public String build() {
|
||||
return command + " " + String.join(" ", args);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,15 @@
|
|||
# The port to run on
|
||||
port: 8080
|
||||
|
||||
# (Optional) password
|
||||
password: ''
|
||||
# Authentication
|
||||
auth:
|
||||
# Protect GET routes. If false, GET routes are public.
|
||||
get: true
|
||||
# Protect POST routes. If false, POST routes are public.
|
||||
post: true
|
||||
tokens:
|
||||
token: access
|
||||
|
||||
# Custom commands (POST)
|
||||
custom:
|
||||
- say
|
|
@ -46,6 +46,21 @@ public class MockProvider implements IProvider {
|
|||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(BanAPI kick) {}
|
||||
|
||||
@Override
|
||||
public void ban(BanAPI ban) {}
|
||||
|
||||
@Override
|
||||
public void unban(BanAPI ban) {}
|
||||
|
||||
@Override
|
||||
public void broadcast(BroadcastAPI broadcast) {}
|
||||
|
||||
@Override
|
||||
public void custom(CustomAPI custom) {}
|
||||
|
||||
@Override
|
||||
public Set<PlayerAPI> players() {
|
||||
return new HashSet<>(Arrays.asList(
|
||||
|
|
|
@ -1,11 +1,30 @@
|
|||
package xyz.etztech.serverapi;
|
||||
|
||||
import xyz.etztech.serverapi.token.Token;
|
||||
import xyz.etztech.serverapi.token.TokenList;
|
||||
import xyz.etztech.serverapi.token.TokenScope;
|
||||
import xyz.etztech.serverapi.web.Web;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ServerRunner {
|
||||
|
||||
static TokenList tokens = new TokenList(true, true, Arrays.asList(
|
||||
new Token("testGET", TokenScope.GET),
|
||||
new Token("testPOST", TokenScope.POST),
|
||||
new Token("testAll", TokenScope.ALL),
|
||||
new Token("testNone", TokenScope.NONE)
|
||||
));
|
||||
|
||||
static List<String> custom = Arrays.asList(
|
||||
"test",
|
||||
"say"
|
||||
);
|
||||
|
||||
public static void main(String[] args) {
|
||||
Web web = new Web(new MockProvider());
|
||||
web.start(8080, "");
|
||||
web.start(8080, tokens, custom);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue