From 5adbe63edeeb1be71a3a7d6ecd41c274a4bc61c2 Mon Sep 17 00:00:00 2001 From: Carlos <28845529+mesacarlos@users.noreply.github.com> Date: Sun, 11 Aug 2019 02:46:36 +0200 Subject: [PATCH] First commit --- .gitignore | 83 +++++++++-- README.md | 31 ++++ plugin.yml | 7 + pom.xml | 83 +++++++++++ src/com/mesacarlos/webconsole/WebConsole.java | 69 +++++++++ .../webconsole/command/CommandFactory.java | 13 ++ .../webconsole/command/ExecuteCmdCommand.java | 27 ++++ .../webconsole/command/LogInCommand.java | 31 ++++ .../webconsole/command/WSCommand.java | 9 ++ .../webconsole/util/DateTimeUtils.java | 10 ++ .../mesacarlos/webconsole/util/LogFilter.java | 138 ++++++++++++++++++ .../webconsole/util/LoginManager.java | 29 ++++ .../webconsole/websockets/WSServer.java | 98 +++++++++++++ 13 files changed, 616 insertions(+), 12 deletions(-) create mode 100644 README.md create mode 100644 plugin.yml create mode 100644 pom.xml create mode 100644 src/com/mesacarlos/webconsole/WebConsole.java create mode 100644 src/com/mesacarlos/webconsole/command/CommandFactory.java create mode 100644 src/com/mesacarlos/webconsole/command/ExecuteCmdCommand.java create mode 100644 src/com/mesacarlos/webconsole/command/LogInCommand.java create mode 100644 src/com/mesacarlos/webconsole/command/WSCommand.java create mode 100644 src/com/mesacarlos/webconsole/util/DateTimeUtils.java create mode 100644 src/com/mesacarlos/webconsole/util/LogFilter.java create mode 100644 src/com/mesacarlos/webconsole/util/LoginManager.java create mode 100644 src/com/mesacarlos/webconsole/websockets/WSServer.java diff --git a/.gitignore b/.gitignore index 5f2dbe1..b7bdbd2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,71 @@ -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties - -# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) -!/.mvn/wrapper/maven-wrapper.jar +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.classpath +.project +.mvn/timing.properties + +# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) +!/.mvn/wrapper/maven-wrapper.jar +/bin/ + +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d605250 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# WebConsole + +WebConsole is a Spigot plugin for Minecraft 1.14 that enables you to view your server console and manage your server from anywhere. It creates a WebSocket server in the background used by the web interface to send commands, receive your console log and manage your server. +Dont worry about privacy: all data is stored in your browser offline and your PC will connect directly to your minecraft server. No intermediary web servers, just you and your server. + +## Plugin installation +1. Plugin download +2. Filling config.yml. Port and password configuration + +## How it works +1. How to install web interface / connect to github pages +2. How to add servers + +## Technical information + +### WebSocket commands +The following tables represent how server communicates with the client(s), something like a language between them. + +#### Websocket Server -> Client +| Code |Meaning | +|---------------------|--------------------------------------| +|200 *(message)* |Query was processed with no errors | +|403 *(message)* |You are not allowed to do that action | +|LOG *(message)* |Message is a console output | + + +#### Client -> Websocket Server +| Code |Meaning |Extra info | +|---------------------|-----------------------------------------|--------------| +|LOGIN *(password)* |Login to start communication with server | | +|EXEC *(command)* |Run desired command in Minecraft Server |Login required| diff --git a/plugin.yml b/plugin.yml new file mode 100644 index 0000000..809278f --- /dev/null +++ b/plugin.yml @@ -0,0 +1,7 @@ +name: WebConsole +main: com.mesacarlos.webconsole.WebConsole +version: 1.0 +description: WebSockets-based web console +api-version: 1.14 +author: Carlos Mesa +commands: \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1f8a065 --- /dev/null +++ b/pom.xml @@ -0,0 +1,83 @@ + + 4.0.0 + WebConsole + WebConsole + 1.0 + + src + + + maven-compiler-plugin + 3.8.0 + + 1.8 + 1.8 + + + + maven-assembly-plugin + + + package + + single + + + + + + jar-with-dependencies + + + + + + + . + + **/*.yml + + + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + + + bungeecord-repo + https://oss.sonatype.org/content/repositories/snapshots + + + + + + org.spigotmc + spigot-api + 1.14.2-R0.1-SNAPSHOT + provided + + + + org.java-websocket + Java-WebSocket + 1.4.0 + + + + org.apache.logging.log4j + log4j-api + 2.12.1 + + + org.apache.logging.log4j + log4j-core + 2.12.1 + + + \ No newline at end of file diff --git a/src/com/mesacarlos/webconsole/WebConsole.java b/src/com/mesacarlos/webconsole/WebConsole.java new file mode 100644 index 0000000..95a7d98 --- /dev/null +++ b/src/com/mesacarlos/webconsole/WebConsole.java @@ -0,0 +1,69 @@ +package com.mesacarlos.webconsole; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Filter; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import com.mesacarlos.webconsole.util.LogFilter; +import com.mesacarlos.webconsole.websockets.WSServer; + +public class WebConsole extends JavaPlugin { + FileConfiguration config = this.getConfig(); + + // Websocket server and thread + private WSServer server; + private Thread wsThread; + + @Override + public void onEnable() { + createConfig(); + startWS(); + + Filter f = new LogFilter(getWSServer()); + ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addFilter(f); + } + + @Override + public void onDisable() { + try { + server.stop(); + wsThread = null; + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * Creates configuration file + */ + private void createConfig() { + config.addDefault("host", "localhost"); + config.addDefault("port", 8080); + config.addDefault("password", 1234); + config.options().copyDefaults(true); + saveConfig(); + } + + /** + * Start WebSockets server + */ + private void startWS() { + //Start WebSockets server + server = new WSServer(this, new InetSocketAddress(config.getString("host"), config.getInt("port"))); + wsThread = new Thread(new Runnable() { + @Override + public void run() { + server.run(); + } + }); + wsThread.start(); + } + + public WSServer getWSServer() { + return (WSServer)server; + } +} \ No newline at end of file diff --git a/src/com/mesacarlos/webconsole/command/CommandFactory.java b/src/com/mesacarlos/webconsole/command/CommandFactory.java new file mode 100644 index 0000000..bfa259c --- /dev/null +++ b/src/com/mesacarlos/webconsole/command/CommandFactory.java @@ -0,0 +1,13 @@ +package com.mesacarlos.webconsole.command; + +import java.util.HashMap; + +public class CommandFactory { + + public static HashMap getCommandsHashMap() { + HashMap commands = new HashMap(); + commands.put("LOGIN", new LogInCommand()); + commands.put("EXEC", new ExecuteCmdCommand()); + return commands; + } +} \ No newline at end of file diff --git a/src/com/mesacarlos/webconsole/command/ExecuteCmdCommand.java b/src/com/mesacarlos/webconsole/command/ExecuteCmdCommand.java new file mode 100644 index 0000000..6f3fa99 --- /dev/null +++ b/src/com/mesacarlos/webconsole/command/ExecuteCmdCommand.java @@ -0,0 +1,27 @@ +package com.mesacarlos.webconsole.command; + +import java.util.concurrent.ExecutionException; + +import org.bukkit.Bukkit; +import org.bukkit.command.ConsoleCommandSender; +import org.java_websocket.WebSocket; + +import com.mesacarlos.webconsole.websockets.WSServer; + +public class ExecuteCmdCommand implements WSCommand{ + + @Override + public void execute(WSServer wsServer, WebSocket conn, String command) { + ConsoleCommandSender sender = Bukkit.getServer().getConsoleSender(); + + try { + @SuppressWarnings("unused") + boolean success = Bukkit.getScheduler().callSyncMethod( wsServer.getMainClass(), () -> Bukkit.dispatchCommand( sender, command ) ).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + + + } + +} \ No newline at end of file diff --git a/src/com/mesacarlos/webconsole/command/LogInCommand.java b/src/com/mesacarlos/webconsole/command/LogInCommand.java new file mode 100644 index 0000000..fb98c52 --- /dev/null +++ b/src/com/mesacarlos/webconsole/command/LogInCommand.java @@ -0,0 +1,31 @@ +package com.mesacarlos.webconsole.command; + +import org.bukkit.Bukkit; +import org.java_websocket.WebSocket; + +import com.mesacarlos.webconsole.util.LoginManager; +import com.mesacarlos.webconsole.websockets.WSServer; + +public class LogInCommand implements WSCommand{ + + @Override + public void execute(WSServer wsServer, WebSocket conn, String password) { + //If user is logged in, then return. + if(LoginManager.getInstance().isLoggedIn(conn.getRemoteSocketAddress().getAddress().toString())) + return; + + //Get password from config files + String receivedPassword = wsServer.getMainClass().getConfig().getString("password"); + + if(receivedPassword.equals(password)) { + //Password is correct, logging in + LoginManager.getInstance().logIn(conn.getRemoteSocketAddress().getAddress().toString()); + conn.send("200 Logged In"); + Bukkit.getLogger().info("[WebConsole] Successfully logged in from " + conn.getRemoteSocketAddress()); + }else { + conn.send("403 Forbidden"); + Bukkit.getLogger().info("[WebConsole] Password incorrect while login from " + conn.getRemoteSocketAddress()); + } + } + +} \ No newline at end of file diff --git a/src/com/mesacarlos/webconsole/command/WSCommand.java b/src/com/mesacarlos/webconsole/command/WSCommand.java new file mode 100644 index 0000000..4f4103d --- /dev/null +++ b/src/com/mesacarlos/webconsole/command/WSCommand.java @@ -0,0 +1,9 @@ +package com.mesacarlos.webconsole.command; + +import org.java_websocket.WebSocket; + +import com.mesacarlos.webconsole.websockets.WSServer; + +public interface WSCommand { + void execute(WSServer wsServer, WebSocket conn, String params); +} \ No newline at end of file diff --git a/src/com/mesacarlos/webconsole/util/DateTimeUtils.java b/src/com/mesacarlos/webconsole/util/DateTimeUtils.java new file mode 100644 index 0000000..d5de833 --- /dev/null +++ b/src/com/mesacarlos/webconsole/util/DateTimeUtils.java @@ -0,0 +1,10 @@ +package com.mesacarlos.webconsole.util; + +import java.text.SimpleDateFormat; +import java.util.Date; + +public class DateTimeUtils { + public static String getDateAsString() { + return new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(new Date()); + } +} \ No newline at end of file diff --git a/src/com/mesacarlos/webconsole/util/LogFilter.java b/src/com/mesacarlos/webconsole/util/LogFilter.java new file mode 100644 index 0000000..09e81db --- /dev/null +++ b/src/com/mesacarlos/webconsole/util/LogFilter.java @@ -0,0 +1,138 @@ +package com.mesacarlos.webconsole.util; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.message.Message; + +import com.mesacarlos.webconsole.websockets.WSServer; + +public class LogFilter implements Filter{ + private WSServer wsServer; + + public LogFilter(WSServer wsServer) { + this.wsServer = wsServer; + } + + @Override + public State getState() { + return null; + } + + @Override + public void initialize() { + + } + + @Override + public void start() { + + } + + @Override + public void stop() { + + } + + @Override + public boolean isStarted() { + return false; + } + + @Override + public boolean isStopped() { + return false; + } + + @Override + public Result getOnMismatch() { + return null; + } + + @Override + public Result getOnMatch() { + return null; + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) { + return null; + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String message, Object p0) { + return null; + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1) { + return null; + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2) { + return null; + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, + Object p3) { + return null; + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, + Object p3, Object p4) { + return null; + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, + Object p3, Object p4, Object p5) { + return null; + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, + Object p3, Object p4, Object p5, Object p6) { + return null; + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, + Object p3, Object p4, Object p5, Object p6, Object p7) { + return null; + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, + Object p3, Object p4, Object p5, Object p6, Object p7, Object p8) { + return null; + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, + Object p3, Object p4, Object p5, Object p6, Object p7, Object p8, Object p9) { + return null; + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) { + return null; + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) { + return null; + } + + @Override + public Result filter(LogEvent event) { + String message = event.getMessage().getFormattedMessage().replaceAll("\u001b"," "); + wsServer.onNewConsoleLinePrinted(message); + return null; + } + +} \ No newline at end of file diff --git a/src/com/mesacarlos/webconsole/util/LoginManager.java b/src/com/mesacarlos/webconsole/util/LoginManager.java new file mode 100644 index 0000000..7b7b49f --- /dev/null +++ b/src/com/mesacarlos/webconsole/util/LoginManager.java @@ -0,0 +1,29 @@ +package com.mesacarlos.webconsole.util; + +import java.util.ArrayList; +import java.util.List; + +public class LoginManager { + private List loggedInUsers = new ArrayList(); + private static LoginManager instance; + + private LoginManager() {} + + public static LoginManager getInstance() { + if(instance == null) + instance = new LoginManager(); + return instance; + } + + public void logIn(String address) { + loggedInUsers.add(address); + } + + public void logOut(String address) { + loggedInUsers.remove(address); + } + + public boolean isLoggedIn(String address) { + return loggedInUsers.contains(address); + } +} \ No newline at end of file diff --git a/src/com/mesacarlos/webconsole/websockets/WSServer.java b/src/com/mesacarlos/webconsole/websockets/WSServer.java new file mode 100644 index 0000000..56ad7d2 --- /dev/null +++ b/src/com/mesacarlos/webconsole/websockets/WSServer.java @@ -0,0 +1,98 @@ +package com.mesacarlos.webconsole.websockets; + +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.HashMap; + +import org.bukkit.Bukkit; +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; + +import com.mesacarlos.webconsole.WebConsole; +import com.mesacarlos.webconsole.command.CommandFactory; +import com.mesacarlos.webconsole.command.WSCommand; +import com.mesacarlos.webconsole.util.LoginManager; + +public class WSServer extends WebSocketServer { + private WebConsole plugin; + private HashMap commands = CommandFactory.getCommandsHashMap(); + + public WSServer(WebConsole plugin, InetSocketAddress address) { + super(address); + this.plugin = plugin; + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + conn.send("Connection started, waiting login"); + Bukkit.getLogger().info("[WebConsole] Connected and waiting login from " + conn.getRemoteSocketAddress()); + } + + @Override + public void onMessage(WebSocket conn, String message) { + // Log this action to console + Bukkit.getLogger().info("[WebConsole] Received signal from " + conn.getRemoteSocketAddress() + ": " + message); + + // Get command and params + String wsCommand = message.split(" ")[0]; + String wsCommandParams = ""; + if (message.contains(" ")) + wsCommandParams = message.split(" ", 2)[1]; + + // Run command + WSCommand cmd = commands.get(wsCommand); + + if (cmd == null) { + Bukkit.getLogger().info( + "[WebConsole] Signal was not processed since is not valid. Is your plugin/web interface up to date?"); + } else if (!LoginManager.getInstance().isLoggedIn(conn.getRemoteSocketAddress().getAddress().toString()) + && !wsCommand.equals("LOGIN")) { + // DO NOTHING. User is not authorised + conn.send("403 Forbidden"); + Bukkit.getLogger().warning("[WebConsole] " + conn.getRemoteSocketAddress() + + " tried to run a command while not authenticated!"); + } else { + cmd.execute(this, conn, wsCommandParams); + } + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + LoginManager.getInstance().logOut(conn.getRemoteSocketAddress().getAddress().toString()); + Bukkit.getLogger() + .info("[WebConsole] Closed WS connection " + conn.getRemoteSocketAddress() + ". Reason: " + reason); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + Bukkit.getLogger() + .warning("[WebConsole] Error occured on connection " + conn.getRemoteSocketAddress() + ":" + ex); + } + + @Override + public void onStart() { + Bukkit.getLogger().info("[WebConsole] WebSockets Server started successfully"); + } + + /** + * Sends the message to all connected AND logged-in users + */ + public void onNewConsoleLinePrinted(String line) { + Collection connections = getConnections(); + for (WebSocket connection : connections) { + if (LoginManager.getInstance().isLoggedIn(connection.getRemoteSocketAddress().getAddress().toString())) + connection.send("LOG " + line); + } + } + + /** + * Returns main class + * + * @return + */ + public WebConsole getMainClass() { + return plugin; + } + +} \ No newline at end of file