From ef885af13756db5a110d45c510df31d2b107ff58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AD=B1=E5=82=91?= Date: Thu, 6 Oct 2022 13:27:04 +0800 Subject: [PATCH] Impl token persistence (#19) --- .../jie65535/opencommand/OpenCommandData.java | 42 +++++++++++++++++ .../opencommand/OpenCommandHandler.java | 39 +++++++-------- .../OpenCommandOnlyHttpHandler.java | 47 ++++++++----------- .../opencommand/OpenCommandPlugin.java | 34 ++++++++++++++ .../jie65535/opencommand/model/Client.java | 16 +++++++ 5 files changed, 128 insertions(+), 50 deletions(-) create mode 100644 src/main/java/com/github/jie65535/opencommand/OpenCommandData.java create mode 100644 src/main/java/com/github/jie65535/opencommand/model/Client.java diff --git a/src/main/java/com/github/jie65535/opencommand/OpenCommandData.java b/src/main/java/com/github/jie65535/opencommand/OpenCommandData.java new file mode 100644 index 0000000..6ef6575 --- /dev/null +++ b/src/main/java/com/github/jie65535/opencommand/OpenCommandData.java @@ -0,0 +1,42 @@ +package com.github.jie65535.opencommand; + +import com.github.jie65535.opencommand.model.Client; + +import java.util.Date; +import java.util.Vector; + +/** + * 插件持久化数据 + */ +public class OpenCommandData { + + /** + * 连接的客户端列表 + */ + public Vector clients = new Vector<>(); + + /** + * 通过令牌获取客户端 + * @param token 令牌 + * @return 客户端对象,若未找到返回null + */ + public Client getClientByToken(String token) { + for (var c : clients) { + if (c.token.equals(token)) + return c; + } + return null; + } + + /** + * 移除所有过期的客户端 + */ + public void removeExpiredClients() { + var now = new Date(); + clients.removeIf(client -> client.tokenExpireTime.before(now)); + } + + public void addClient(Client client) { + clients.add(client); + } +} diff --git a/src/main/java/com/github/jie65535/opencommand/OpenCommandHandler.java b/src/main/java/com/github/jie65535/opencommand/OpenCommandHandler.java index 4ca1722..c06d007 100644 --- a/src/main/java/com/github/jie65535/opencommand/OpenCommandHandler.java +++ b/src/main/java/com/github/jie65535/opencommand/OpenCommandHandler.java @@ -19,6 +19,7 @@ package com.github.jie65535.opencommand; import com.github.jie65535.opencommand.json.JsonRequest; import com.github.jie65535.opencommand.json.JsonResponse; +import com.github.jie65535.opencommand.model.Client; import com.github.jie65535.opencommand.socket.SocketData; import emu.grasscutter.command.CommandMap; import emu.grasscutter.server.http.Router; @@ -42,17 +43,17 @@ public final class OpenCommandHandler implements Router { javalin.post("/opencommand/api", OpenCommandHandler::handle); } - private static final Map clients = new HashMap<>(); - private static final Map tokenExpireTime = new HashMap<>(); private static final Map codes = new HashMap<>(); private static final Int2ObjectMap codeExpireTime = new Int2ObjectOpenHashMap<>(); public static void handle(Context context) { - // Trigger cleanup action - cleanupExpiredData(); var plugin = OpenCommandPlugin.getInstance(); var config = plugin.getConfig(); + var data = plugin.getData(); var now = new Date(); + // Trigger cleanup action + cleanupExpiredCodes(); + data.removeExpiredClients(); var req = context.bodyAsClass(JsonRequest.class); if (req.action.equals("sendCode")) { @@ -74,9 +75,8 @@ public final class OpenCommandHandler implements Router { token = Utils.bytesToHex(Crypto.createSessionKey(32)); int code = Utils.randomRange(1000, 9999); codeExpireTime.put(playerId, new Date(now.getTime() + config.codeExpirationTime_S * 1000L)); - tokenExpireTime.put(token, new Date(now.getTime() + config.tempTokenExpirationTime_S * 1000L)); codes.put(token, code); - clients.put(token, playerId); + data.addClient(new Client(token, playerId, new Date(now.getTime() + config.tempTokenExpirationTime_S * 1000L))); player.dropMessage("[Open Command] Verification code: " + code); context.json(new JsonResponse(token)); } @@ -97,7 +97,8 @@ public final class OpenCommandHandler implements Router { return; } var isConsole = req.token.equals(config.consoleToken); - if (!isConsole && !clients.containsKey(req.token)) { + var client = data.getClientByToken(req.token); + if (!isConsole && client == null) { context.json(new JsonResponse(401, "Unauthorized")); return; } @@ -131,9 +132,10 @@ public final class OpenCommandHandler implements Router { if (codes.get(req.token).equals(req.data)) { codes.remove(req.token); // update token expire time - tokenExpireTime.put(req.token, new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L)); + client.tokenExpireTime = new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L); context.json(new JsonResponse()); - plugin.getLogger().info(String.format("Player %d has passed the verification, ip: %s", clients.get(req.token), context.ip())); + plugin.getLogger().info(String.format("Player %d has passed the verification, ip: %s", client.playerId, context.ip())); + plugin.saveData(); } else { context.json(new JsonResponse(400, "Verification failed")); } @@ -142,9 +144,8 @@ public final class OpenCommandHandler implements Router { } else { if (req.action.equals("command")) { // update token expire time - tokenExpireTime.put(req.token, new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L)); - var playerId = clients.get(req.token); - var player = plugin.getServer().getPlayerByUid(playerId); + client.tokenExpireTime = new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L); + var player = plugin.getServer().getPlayerByUid(client.playerId); var command = req.data.toString(); if (player == null) { context.json(new JsonResponse(404, "Player not found")); @@ -171,18 +172,10 @@ public final class OpenCommandHandler implements Router { context.json(new JsonResponse(403, "forbidden")); } - private static void cleanupExpiredData() { + private static void cleanupExpiredCodes() { var now = new Date(); codeExpireTime.int2ObjectEntrySet().removeIf(entry -> entry.getValue().before(now)); - - var it = tokenExpireTime.entrySet().iterator(); - while (it.hasNext()) { - var entry = it.next(); - if (entry.getValue().before(now)) { - it.remove(); - // remove expired token - clients.remove(entry.getKey()); - } - } + if (codeExpireTime.isEmpty()) + codes.clear(); } } diff --git a/src/main/java/com/github/jie65535/opencommand/OpenCommandOnlyHttpHandler.java b/src/main/java/com/github/jie65535/opencommand/OpenCommandOnlyHttpHandler.java index 374d4c3..4e7d604 100644 --- a/src/main/java/com/github/jie65535/opencommand/OpenCommandOnlyHttpHandler.java +++ b/src/main/java/com/github/jie65535/opencommand/OpenCommandOnlyHttpHandler.java @@ -19,6 +19,7 @@ package com.github.jie65535.opencommand; import com.github.jie65535.opencommand.json.JsonRequest; import com.github.jie65535.opencommand.json.JsonResponse; +import com.github.jie65535.opencommand.model.Client; import com.github.jie65535.opencommand.socket.SocketData; import com.github.jie65535.opencommand.socket.SocketDataWait; import com.github.jie65535.opencommand.socket.SocketServer; @@ -45,17 +46,17 @@ public final class OpenCommandOnlyHttpHandler implements Router { javalin.post("/opencommand/api", OpenCommandOnlyHttpHandler::handle); } - private static final Map clients = new HashMap<>(); - private static final Map tokenExpireTime = new HashMap<>(); private static final Map codes = new HashMap<>(); private static final Int2ObjectMap codeExpireTime = new Int2ObjectOpenHashMap<>(); public static void handle(Context context) { - // Trigger cleanup action - cleanupExpiredData(); var plugin = OpenCommandPlugin.getInstance(); var config = plugin.getConfig(); + var data = plugin.getData(); var now = new Date(); + // Trigger cleanup action + cleanupExpiredCodes(); + data.removeExpiredClients(); var req = context.bodyAsClass(JsonRequest.class); if (req.action.equals("sendCode")) { @@ -77,9 +78,8 @@ public final class OpenCommandOnlyHttpHandler implements Router { token = Utils.bytesToHex(Crypto.createSessionKey(32)); int code = Utils.randomRange(1000, 9999); codeExpireTime.put(playerId, new Date(now.getTime() + config.codeExpirationTime_S * 1000L)); - tokenExpireTime.put(token, new Date(now.getTime() + config.tempTokenExpirationTime_S * 1000L)); codes.put(token, code); - clients.put(token, playerId); + data.addClient(new Client(token, playerId, new Date(now.getTime() + config.tempTokenExpirationTime_S * 1000L))); Player.dropMessage(playerId, "[Open Command] Verification code: " + code); context.json(new JsonResponse(token)); } @@ -98,7 +98,8 @@ public final class OpenCommandOnlyHttpHandler implements Router { return; } var isConsole = req.token.equals(config.consoleToken); - if (!isConsole && !clients.containsKey(req.token)) { + var client = data.getClientByToken(req.token); + if (!isConsole && client == null) { context.json(new JsonResponse(401, "Unauthorized")); return; } @@ -130,12 +131,12 @@ public final class OpenCommandOnlyHttpHandler implements Router { }; SocketServer.sendPacketAndWait(server.ip, new RunConsoleCommand(req.data.toString()), wait); - var data = wait.getData(); - if (data == null) { + var packet = wait.getData(); + if (packet == null) { context.json(new JsonResponse(408, "Timeout")); return; } - context.json(new JsonResponse(data.code, data.message, data.data)); + context.json(new JsonResponse(packet.code, packet.message, packet.data)); return; } else if (req.action.equals("server")) { context.json(new JsonResponse(200, "Success", SocketServer.getOnlineClient())); @@ -149,9 +150,10 @@ public final class OpenCommandOnlyHttpHandler implements Router { if (codes.get(req.token).equals(req.data)) { codes.remove(req.token); // update token expire time - tokenExpireTime.put(req.token, new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L)); + client.tokenExpireTime = new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L); context.json(new JsonResponse()); - plugin.getLogger().info(String.format("Player %d has passed the verification, ip: %s", clients.get(req.token), context.ip())); + plugin.getLogger().info(String.format("Player %d has passed the verification, ip: %s", client.playerId, context.ip())); + plugin.saveData(); } else { context.json(new JsonResponse(400, "Verification failed")); } @@ -175,15 +177,14 @@ public final class OpenCommandOnlyHttpHandler implements Router { }; // update token expire time - tokenExpireTime.put(req.token, new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L)); - var playerId = clients.get(req.token); + client.tokenExpireTime = new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L); var command = req.data.toString(); var player = new Player(); - player.uid = playerId; + player.uid = client.playerId; player.type = PlayerEnum.RunCommand; player.data = command; - if (!SocketServer.sendUidPacketAndWait(playerId, player, socketDataWait)) { + if (!SocketServer.sendUidPacketAndWait(client.playerId, player, socketDataWait)) { context.json(new JsonResponse(404, "Player Not Found.")); return; } @@ -201,18 +202,10 @@ public final class OpenCommandOnlyHttpHandler implements Router { context.json(new JsonResponse(403, "forbidden")); } - private static void cleanupExpiredData() { + private static void cleanupExpiredCodes() { var now = new Date(); codeExpireTime.int2ObjectEntrySet().removeIf(entry -> entry.getValue().before(now)); - - var it = tokenExpireTime.entrySet().iterator(); - while (it.hasNext()) { - var entry = it.next(); - if (entry.getValue().before(now)) { - it.remove(); - // remove expired token - clients.remove(entry.getKey()); - } - } + if (codeExpireTime.isEmpty()) + codes.clear(); } } diff --git a/src/main/java/com/github/jie65535/opencommand/OpenCommandPlugin.java b/src/main/java/com/github/jie65535/opencommand/OpenCommandPlugin.java index b33fd71..c012c2c 100644 --- a/src/main/java/com/github/jie65535/opencommand/OpenCommandPlugin.java +++ b/src/main/java/com/github/jie65535/opencommand/OpenCommandPlugin.java @@ -40,6 +40,8 @@ public final class OpenCommandPlugin extends Plugin { private OpenCommandConfig config; + private OpenCommandData data; + private Grasscutter.ServerRunMode runMode = Grasscutter.ServerRunMode.HYBRID; @Override @@ -47,6 +49,8 @@ public final class OpenCommandPlugin extends Plugin { instance = this; // 加载配置 loadConfig(); + // 加载数据 + loadData(); // 启动Socket startSocket(); } @@ -77,6 +81,7 @@ public final class OpenCommandPlugin extends Plugin { @Override public void onDisable() { + saveData(); getLogger().info("[OpenCommand] Disabled"); } @@ -84,6 +89,10 @@ public final class OpenCommandPlugin extends Plugin { return config; } + public OpenCommandData getData() { + return data; + } + private void loadConfig() { var configFile = new File(getDataFolder(), "config.json"); if (!configFile.exists()) { @@ -110,6 +119,31 @@ public final class OpenCommandPlugin extends Plugin { } } + private void loadData() { + var dataFile = new File(getDataFolder(), "data.json"); + if (!dataFile.exists()) { + data = new OpenCommandData(); + saveData(); + } else { + try { + data = JsonUtils.loadToClass(dataFile.getAbsolutePath(), OpenCommandData.class); + } catch (Exception exception) { + data = new OpenCommandData(); + getLogger().error("[OpenCommand] There was an error while trying to load the data from data.json. Please make sure that there are no syntax errors. If you want to start with a default data, delete your existing data.json."); + } + } + } + + public void saveData() { + try (var file = new FileWriter(new File(getDataFolder(), "data.json"))) { + file.write(JsonUtils.encode(data)); + } catch (IOException e) { + getLogger().error("[OpenCommand] Unable to write to data file."); + } catch (Exception e) { + getLogger().error("[OpenCommand] Unable to save data file."); + } + } + private void startSocket() { if (runMode == Grasscutter.ServerRunMode.GAME_ONLY) { getLogger().info("[OpenCommand] Starting socket client..."); diff --git a/src/main/java/com/github/jie65535/opencommand/model/Client.java b/src/main/java/com/github/jie65535/opencommand/model/Client.java new file mode 100644 index 0000000..5eb3265 --- /dev/null +++ b/src/main/java/com/github/jie65535/opencommand/model/Client.java @@ -0,0 +1,16 @@ +package com.github.jie65535.opencommand.model; + +import java.util.Date; + +public final class Client { + + public String token; + public Integer playerId; + public Date tokenExpireTime; + + public Client(String token, Integer playerId, Date tokenExpireTime) { + this.token = token; + this.playerId = playerId; + this.tokenExpireTime = tokenExpireTime; + } +}