Initial commit
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
package de.craftix.cloudnetwebinterface;
|
||||
|
||||
import de.craftix.cloudnetwebinterface.commands.MainCmd;
|
||||
import de.craftix.cloudnetwebinterface.utils.DBManager;
|
||||
import de.craftix.cloudnetwebinterface.utils.SQLite;
|
||||
import de.craftix.cloudnetwebinterface.webserver.WebServerManager;
|
||||
import de.craftix.cloudnetwebinterface.webserver.routes.ConsoleAPI;
|
||||
import de.craftix.cloudnetwebinterface.webserver.routes.PlayerAPI;
|
||||
import de.craftix.cloudnetwebinterface.webserver.routes.UserAPI;
|
||||
import de.dytanic.cloudnet.driver.module.ModuleLifeCycle;
|
||||
import de.dytanic.cloudnet.driver.module.ModuleTask;
|
||||
import de.dytanic.cloudnet.module.NodeCloudNetModule;
|
||||
|
||||
public class CloudNetWebinterface extends NodeCloudNetModule {
|
||||
private static CloudNetWebinterface instance;
|
||||
private static SQLite sqLite;
|
||||
private static WebServerManager webServerManager;
|
||||
|
||||
@ModuleTask(event = ModuleLifeCycle.STARTED)
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
sqLite = new SQLite("modules/Webinterface/data.db");
|
||||
DBManager.initialize();
|
||||
|
||||
//Start Server
|
||||
webServerManager = new WebServerManager(4041);
|
||||
webServerManager.addApi(new UserAPI());
|
||||
webServerManager.addApi(new PlayerAPI());
|
||||
webServerManager.addApi(new ConsoleAPI());
|
||||
|
||||
registerCommand(new MainCmd());
|
||||
}
|
||||
|
||||
@ModuleTask(event = ModuleLifeCycle.STOPPED)
|
||||
public void onDisable() {
|
||||
webServerManager.stop();
|
||||
}
|
||||
|
||||
public static CloudNetWebinterface getInstance() { return instance; }
|
||||
public static SQLite getSQLite() { return sqLite; }
|
||||
public static WebServerManager getWebServerManager() { return webServerManager; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package de.craftix.cloudnetwebinterface.commands;
|
||||
|
||||
import de.craftix.cloudnetwebinterface.utils.DBManager;
|
||||
import de.dytanic.cloudnet.command.DriverCommandSender;
|
||||
import de.dytanic.cloudnet.command.ICommandSender;
|
||||
import de.dytanic.cloudnet.command.sub.SubCommandHandler;
|
||||
import de.dytanic.cloudnet.common.Properties;
|
||||
import de.dytanic.cloudnet.driver.CloudNetDriver;
|
||||
import de.dytanic.cloudnet.ext.bridge.player.ICloudPlayer;
|
||||
import de.dytanic.cloudnet.ext.bridge.player.IPlayerManager;
|
||||
|
||||
public class MainCmd extends SubCommandHandler {
|
||||
|
||||
public MainCmd() {
|
||||
super("register");
|
||||
this.permission = "webinterface.admin";
|
||||
this.description = "Manage the Webinterface";
|
||||
this.usage = "/cloud register <username> <password>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(ICommandSender sender, String command, String[] args, String commandLine, Properties properties) {
|
||||
if (!(sender instanceof DriverCommandSender)) sender.sendMessage("Dieser Command kann nur als Spieler ausgeführt werden!");
|
||||
else if (args.length != 2) sender.sendMessage(getUsage());
|
||||
else {
|
||||
ICloudPlayer player = CloudNetDriver.getInstance().getServicesRegistry().getFirstService(IPlayerManager.class).getFirstOnlinePlayer(args[0]);
|
||||
if (player == null) throw new NullPointerException("Player cannot be null");
|
||||
String password = args[1];
|
||||
DBManager.addUser(player.getUniqueId(), password);
|
||||
sender.sendMessage("Erfolgreich im Webinterface registriert!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package de.craftix.cloudnetwebinterface.utils;
|
||||
|
||||
import de.craftix.cloudnetwebinterface.CloudNetWebinterface;
|
||||
import de.dytanic.cloudnet.driver.CloudNetDriver;
|
||||
import de.dytanic.cloudnet.driver.permission.IPermissionGroup;
|
||||
import de.dytanic.cloudnet.driver.permission.IPermissionUser;
|
||||
import de.dytanic.cloudnet.driver.permission.PermissionUserGroupInfo;
|
||||
import de.dytanic.cloudnet.ext.bridge.player.ICloudOfflinePlayer;
|
||||
import de.dytanic.cloudnet.ext.bridge.player.IPlayerManager;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.util.Base64;
|
||||
import java.util.UUID;
|
||||
|
||||
public class DBManager {
|
||||
private static SQLite sqLite;
|
||||
|
||||
public static void initialize() {
|
||||
sqLite = CloudNetWebinterface.getSQLite();
|
||||
sqLite.insert("CREATE TABLE IF NOT EXISTS Users (UUID VARCHAR(100), Password VARCHAR(50))");
|
||||
sqLite.insert("CREATE TABLE IF NOT EXISTS Sessions (UUID VARCHAR(100), SessionKey VARCHAR(100), CreationDate INT(255))");
|
||||
}
|
||||
|
||||
public static void addUser(UUID uuid, String password) {
|
||||
password = Base64.getEncoder().encodeToString(password.getBytes());
|
||||
sqLite.insert("DELETE FROM Users WHERE UUID = \"" + uuid + "\"");
|
||||
sqLite.insert("INSERT INTO Users VALUES (\"" + uuid + "\", \"" + password + "\")");
|
||||
}
|
||||
|
||||
public static boolean checkLoginData(String username, String password) {
|
||||
ICloudOfflinePlayer player = CloudNetDriver.getInstance().getServicesRegistry().getFirstService(IPlayerManager.class).getFirstOfflinePlayer(username);
|
||||
if (player == null) return false;
|
||||
return password.equals(getUserPassword(player.getUniqueId()));
|
||||
}
|
||||
|
||||
private static String getUserPassword(UUID uuid) {
|
||||
try {
|
||||
ResultSet rs = sqLite.getData("SELECT Password FROM Users WHERE UUID = \"" + uuid + "\"");
|
||||
if (rs.next()) {
|
||||
return new String(Base64.getDecoder().decode(rs.getString("Password")));
|
||||
}
|
||||
}catch (Exception e) { e.printStackTrace(); }
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String createSessionKey(UUID user) {
|
||||
String session = UUID.randomUUID().toString();
|
||||
sqLite.insert("DELETE FROM Sessions WHERE UUID = \"" + user + "\"");
|
||||
sqLite.insert("INSERT INTO Sessions VALUES (\"" + user + "\", \"" + session + "\", " + System.currentTimeMillis() + ")");
|
||||
return session;
|
||||
}
|
||||
|
||||
public static boolean validateSession(String session) {
|
||||
try {
|
||||
ResultSet rs = sqLite.getData("SELECT CreationDate FROM Sessions WHERE SessionKey = \"" + session + "\"");
|
||||
if (rs.next()) {
|
||||
long creationDate = rs.getLong("CreationDate");
|
||||
double time = System.currentTimeMillis() - creationDate;
|
||||
time = time / 1000 / 60 / 60;
|
||||
return time < 12;
|
||||
}
|
||||
}catch (Exception e) { e.printStackTrace(); }
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean validateForPermission(String sessionKey, String permission) {
|
||||
try {
|
||||
ResultSet rs = sqLite.getData("SELECT UUID FROM Sessions WHERE SessionKey = \"" + sessionKey + "\"");
|
||||
if (rs.next()) {
|
||||
UUID uuid = UUID.fromString(rs.getString("UUID"));
|
||||
IPermissionUser player = CloudNetDriver.getInstance().getPermissionManagement().getUser(uuid);
|
||||
if (player == null) return false;
|
||||
boolean hasPermission = player.hasPermission(permission).asBoolean();
|
||||
if (!hasPermission) {
|
||||
for (IPermissionGroup group : CloudNetDriver.getInstance().getPermissionManagement().getGroups(player)) {
|
||||
if (group.hasPermission(permission).asBoolean()) return true;
|
||||
}
|
||||
}
|
||||
return hasPermission;
|
||||
}
|
||||
}catch (Exception e) { e.printStackTrace(); }
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package de.craftix.cloudnetwebinterface.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
|
||||
public class SQLite {
|
||||
|
||||
protected String path;
|
||||
protected Connection con;
|
||||
|
||||
public SQLite(String path) {
|
||||
File file = new File(path);
|
||||
if (!file.exists()) {
|
||||
try {
|
||||
file.getParentFile().mkdirs();
|
||||
file.createNewFile();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
this.path = file.getAbsolutePath();
|
||||
connect();
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
if (isConnected()) return;
|
||||
try {
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
con = DriverManager.getConnection("jdbc:sqlite:" + path);
|
||||
}catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
if (!isConnected()) return;
|
||||
try {
|
||||
con.close();
|
||||
con = null;
|
||||
}catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
|
||||
public boolean isConnected() { return con != null; }
|
||||
|
||||
public void insert(String qry) {
|
||||
if (!isConnected()) throw new NullPointerException("SQLite not connected");
|
||||
try {
|
||||
con.prepareStatement(qry).executeUpdate();
|
||||
}catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
|
||||
public ResultSet getData(String qry) {
|
||||
if (!isConnected()) throw new NullPointerException("SQLite not connected");
|
||||
try {
|
||||
return con.prepareStatement(qry).executeQuery();
|
||||
}catch (Exception e) { e.printStackTrace(); }
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package de.craftix.cloudnetwebinterface.webserver;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
|
||||
public class WebFileManager implements HttpHandler {
|
||||
public WebFileManager(WebServerManager manager) {
|
||||
manager.getServer().createContext("/", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
String path = exchange.getRequestURI().getPath();
|
||||
byte[] response = getFileContent(path);
|
||||
|
||||
exchange.sendResponseHeaders(200, response.length);
|
||||
OutputStream out = exchange.getResponseBody();
|
||||
out.write(response);
|
||||
out.close();
|
||||
}
|
||||
|
||||
private byte[] getFileContent(String fileName) {
|
||||
try {
|
||||
File file = new File("modules/Webinterface/htdocs/" + fileName + "/");
|
||||
if (file.isDirectory()) file = new File("modules/Webinterface/htdocs/" + fileName + "/index.html");
|
||||
return Files.readAllBytes(file.toPath());
|
||||
}catch (Exception e) {
|
||||
System.err.println("Client tried to access invalid path: " + fileName);
|
||||
}
|
||||
return getFileContent("index.html");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package de.craftix.cloudnetwebinterface.webserver;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import de.craftix.cloudnetwebinterface.utils.DBManager;
|
||||
import de.craftix.cloudnetwebinterface.webserver.api.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class WebServerManager implements HttpHandler {
|
||||
private WebFileManager webFileManager;
|
||||
|
||||
private HttpServer server;
|
||||
|
||||
private final Map<String, Method> apis = new HashMap<>();
|
||||
private final Map<Method, WebApi> apiObjects = new HashMap<>();
|
||||
|
||||
public WebServerManager(int port) {
|
||||
try {
|
||||
server = HttpServer.create(new InetSocketAddress(port), 0);
|
||||
server.createContext("/api").setHandler(this);
|
||||
webFileManager = new WebFileManager(this);
|
||||
server.start();
|
||||
}catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() { server.stop(0); }
|
||||
|
||||
public HttpServer getServer() { return server; }
|
||||
public WebFileManager getWebFileManager() { return webFileManager; }
|
||||
|
||||
public static byte[] getApiTester() {
|
||||
return ("<!DOCTYPE html>\n" +
|
||||
"<html lang=\"en\">\n" +
|
||||
"<head>\n" +
|
||||
" <meta charset=\"UTF-8\">\n" +
|
||||
" <title>Http Request</title>\n" +
|
||||
" <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3\" crossorigin=\"anonymous\">\n" +
|
||||
" <script src=\"https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js\" integrity=\"sha384-7+zCNj/IqJ95wo16oMtfsKbZ9ccEh31eOz1HGyDuCQ6wgnyJNSYdrPa03rtR1zdB\" crossorigin=\"anonymous\"></script>\n" +
|
||||
" <script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js\" integrity=\"sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13\" crossorigin=\"anonymous\"></script>\n" +
|
||||
"\n" +
|
||||
" <style>\n" +
|
||||
" .hover:hover {\n" +
|
||||
" cursor: pointer;\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" .unselectable {\n" +
|
||||
" -webkit-touch-callout: none;\n" +
|
||||
" -webkit-user-select: none;\n" +
|
||||
" -moz-user-select: none;\n" +
|
||||
" -ms-user-select: none;\n" +
|
||||
" user-select: none;\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" #url_start {\n" +
|
||||
" border-top-right-radius: 0;\n" +
|
||||
" border-bottom-right-radius: 0;\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" #url, #apikey {\n" +
|
||||
" border-top-left-radius: 0;\n" +
|
||||
" border-bottom-left-radius: 0;\n" +
|
||||
" }\n" +
|
||||
" </style>\n" +
|
||||
"<body>\n" +
|
||||
"\n" +
|
||||
"<section style=\"margin-top: 20px\" id=\"options\">\n" +
|
||||
" <p class=\"text-center unselectable\" style=\"font-size: 50px\">Send Http Request</p>\n" +
|
||||
"\n" +
|
||||
" <form class=\"container\">\n" +
|
||||
" <div class=\"row\">\n" +
|
||||
" <div class=\"col\">\n" +
|
||||
" <label for=\"url\">URL</label>\n" +
|
||||
" <div class=\"input-group mb-3\">\n" +
|
||||
" <div class=\"input-group-prepend\">\n" +
|
||||
" <span class=\"input-group-text\" id=\"url_start\"></span>\n" +
|
||||
" </div>\n" +
|
||||
" <input type=\"text\" class=\"form-control\" id=\"url\" aria-describedby=\"url_start\">\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
" <div class=\"dropdown col col-md-2\">\n" +
|
||||
" <label for=\"method\" class=\"unselectable\">Select Method</label>\n" +
|
||||
" <input class=\"btn btn-secondary dropdown-toggle form-control unselectable\" type=\"button\" id=\"method\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\" value=\"GET\">\n" +
|
||||
" <ul class=\"dropdown-menu\" aria-labelledby=\"method\">\n" +
|
||||
" <li><a class=\"dropdown-item hover unselectable\" id=\"GET\">GET</a></li>\n" +
|
||||
" <li><a class=\"dropdown-item hover unselectable\" id=\"POST\">POST</a></li>\n" +
|
||||
" <li><a class=\"dropdown-item hover unselectable\" id=\"PUT\">PUT</a></li>\n" +
|
||||
" <li><a class=\"dropdown-item hover unselectable\" id=\"DELETE\">DELETE</a></li>\n" +
|
||||
" </ul>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
" <div class=\"row\">\n" +
|
||||
" <label for=\"apikey\">Session Key</label>\n" +
|
||||
" <input type=\"text\" class=\"form-control\" id=\"apikey\">\n" +
|
||||
" </div>\n" +
|
||||
" <button type=\"button\" class=\"btn btn-primary unselectable\" id=\"send\">Send</button>\n" +
|
||||
" </form>\n" +
|
||||
"</section>\n" +
|
||||
"\n" +
|
||||
"<br><br>\n" +
|
||||
"\n" +
|
||||
"<section id=\"response\" class=\"container\"></section>\n" +
|
||||
"\n" +
|
||||
"<script>\n" +
|
||||
" const url = new URL(window.location.href);\n" +
|
||||
" const startUrl = url.protocol + \"//\" + url.hostname + \":\" + url.port + \"/\";\n" +
|
||||
" document.getElementById(\"url_start\").innerHTML = startUrl;\n" +
|
||||
"\n" +
|
||||
" let method = \"GET\";\n" +
|
||||
"\n" +
|
||||
" const dropdowns = document.getElementsByClassName(\"dropdown-item\");\n" +
|
||||
" for (let i = 0; i < dropdowns.length; i++) {\n" +
|
||||
" const dropdown = dropdowns[i];\n" +
|
||||
" dropdown.onclick = function () {\n" +
|
||||
" method = dropdown.id;\n" +
|
||||
" document.getElementById(\"method\").value = method;\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" document.getElementById(\"send\").onclick = async function () {\n" +
|
||||
" const url = startUrl + document.getElementById(\"url\").value;\n" +
|
||||
" const sessionKey = document.getElementById(\"apikey\").value;\n" +
|
||||
" const response = await (await fetch(url, {\n" +
|
||||
" method: method,\n" +
|
||||
" mode: 'cors',\n" +
|
||||
" cache: 'no-cache',\n" +
|
||||
" credentials: 'same-origin',\n" +
|
||||
" headers: {\n" +
|
||||
" 'Content-Type': 'application/json',\n" +
|
||||
" 'Authorization': sessionKey\n" +
|
||||
" },\n" +
|
||||
" redirect: 'follow',\n" +
|
||||
" referrerPolicy: 'no-referrer',\n" +
|
||||
" })).text();\n" +
|
||||
" document.getElementById(\"response\").innerHtml = response;\n" +
|
||||
" }\n" +
|
||||
"</script>\n" +
|
||||
"\n" +
|
||||
"</body>\n" +
|
||||
"</html>").getBytes();
|
||||
}
|
||||
public static byte[] getErrorMessage() {
|
||||
return "ERROR: API-Route does not exsist".getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
String path = exchange.getRequestURI().getPath();
|
||||
if (path.equalsIgnoreCase("/api/tester")) {
|
||||
byte[] response = getApiTester();
|
||||
exchange.sendResponseHeaders(200, response.length);
|
||||
OutputStream out = exchange.getResponseBody();
|
||||
out.write(response);
|
||||
out.close();
|
||||
} else if (apis.containsKey(path)) {
|
||||
Method api = apis.get(path);
|
||||
String method = api.isAnnotationPresent(SetMethod.class) ? api.getAnnotation(SetMethod.class).value().toString() : RequestMethod.GET.toString();
|
||||
if (!exchange.getRequestMethod().equals(method)) {
|
||||
byte[] response = getErrorMessage();
|
||||
exchange.sendResponseHeaders(200, response.length);
|
||||
OutputStream out = exchange.getResponseBody();
|
||||
out.write(response);
|
||||
out.close();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean authorized = api.isAnnotationPresent(Authorized.class);
|
||||
if (authorized) {
|
||||
Authorized auth = api.getAnnotation(Authorized.class);
|
||||
String sessionKey = exchange.getRequestHeaders().getFirst("Authorization");
|
||||
if (DBManager.validateSession(sessionKey) && DBManager.validateForPermission(sessionKey, auth.permission())) {
|
||||
try {
|
||||
api.invoke(apiObjects.get(api), exchange);
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}else {
|
||||
exchange.sendResponseHeaders(401, -1);
|
||||
}
|
||||
}else {
|
||||
try {
|
||||
api.invoke(apiObjects.get(api), exchange);
|
||||
}catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
}else {
|
||||
byte[] response = getErrorMessage();
|
||||
exchange.sendResponseHeaders(200, response.length);
|
||||
OutputStream out = exchange.getResponseBody();
|
||||
out.write(response);
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getBytesFromStream(InputStream in) {
|
||||
try {
|
||||
if (in == null) throw new NullPointerException("InputStream cannot be null");
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
int nRead;
|
||||
byte[] data = new byte[16384];
|
||||
|
||||
while ((nRead = in.read(data, 0, data.length)) != -1) {
|
||||
buffer.write(data, 0, nRead);
|
||||
}
|
||||
|
||||
return buffer.toByteArray();
|
||||
}catch (Exception e) { e.printStackTrace(); }
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
public static List<String> getFileLines(File file) {
|
||||
try {
|
||||
return Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);
|
||||
}catch (Exception e) { e.printStackTrace(); }
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public void addApi(WebApi api) {
|
||||
if (!api.getClass().isAnnotationPresent(Route.class)) throw new IllegalStateException("API does not have a Route");
|
||||
String route = api.getClass().getAnnotation(Route.class).value();
|
||||
|
||||
for (Method m : api.getClass().getMethods()) {
|
||||
if (m.getParameters().length == 0) continue;
|
||||
if (!m.getParameters()[0].getType().equals(HttpExchange.class)) continue;
|
||||
String r = route + (m.isAnnotationPresent(Route.class) ? m.getAnnotation(Route.class).value() : "");
|
||||
apis.put(r, m);
|
||||
apiObjects.put(m, api);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.craftix.cloudnetwebinterface.webserver.api;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Authorized {
|
||||
String permission() default "web.use";
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package de.craftix.cloudnetwebinterface.webserver.api;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpQuery {
|
||||
private final Map<String, String> variables = new HashMap<>();
|
||||
|
||||
public HttpQuery(HttpExchange exchange) {
|
||||
if (!exchange.getRequestURI().toString().contains("?")) return;
|
||||
String raw = exchange.getRequestURI().getQuery();
|
||||
String[] vars = raw.split("&");
|
||||
for (String var : vars) {
|
||||
String[] param = var.split("=");
|
||||
if (param.length == 0) return;
|
||||
if (param.length == 1) variables.put(param[0].toLowerCase(), "");
|
||||
else variables.put(param[0].toLowerCase(), param[1]);
|
||||
}
|
||||
}
|
||||
|
||||
public String getVariable(String name) { return variables.get(name.toLowerCase()) != null ? variables.get(name.toLowerCase()) : ""; }
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package de.craftix.cloudnetwebinterface.webserver.api;
|
||||
|
||||
public enum LogicResult {
|
||||
|
||||
CONTINUE(100),
|
||||
SWITCHING_PROTOCOL(101),
|
||||
@Deprecated PROCESSING(102),
|
||||
EARLY_HINTS(103),
|
||||
|
||||
OK(200),
|
||||
CREATED(201),
|
||||
ACCEPTED(202),
|
||||
NOT_AUTHORIZED_CONTENT(203),
|
||||
NO_CONTENT(204),
|
||||
RESET_CONTENT(205),
|
||||
PARTIAL_CONTENT(206),
|
||||
@Deprecated ALREADY_REPORTED(208),
|
||||
@Deprecated IM_USED(226),
|
||||
|
||||
MULTIPLE_CHOICE(300),
|
||||
MOVED_PERMANENTLY(301),
|
||||
FOUND(302),
|
||||
SEE_OTHER(303),
|
||||
NOT_MODIFIED(304),
|
||||
@Deprecated USE_PROXY(305),
|
||||
@Deprecated UNUSED(306),
|
||||
TEMPORARY_REDIRECT(307),
|
||||
PERMANENT_REDIRECT(308),
|
||||
|
||||
BAD_REQUEST(400),
|
||||
UNAUTHORIZED(401),
|
||||
PAYMENT_REQUIRED(402),
|
||||
FORBIDDEN(403),
|
||||
NOT_FOUND(404),
|
||||
METHOD_NOT_ALLOWED(405),
|
||||
NOT_ACCEPTABLE(406),
|
||||
PROXY_AUTHORIZATION_REQUIRED(407),
|
||||
REQUEST_TIMEOUT(408),
|
||||
CONFLICT(409),
|
||||
GONE(410),
|
||||
LENGTH_REQUIRED(411),
|
||||
PRECONDITION_FAILED(412),
|
||||
PRECONDITION_TOO_LARGE(413),
|
||||
URI_TO_LONG(414),
|
||||
UNSUPPORTED_MEDIA_TYPE(415),
|
||||
REQUESTED_RANGE_NOT_SATISFIABLE(416),
|
||||
EXPECTATION_FAILED(417),
|
||||
@Deprecated MISDIRECTED_REQUEST(421),
|
||||
UPGRADE_REQUIRED(426),
|
||||
PRECONDITION_REQUIRED(428),
|
||||
TOO_MANY_REQUESTS(429),
|
||||
REQUEST_HEADER_FIELDS_TOO_LARGE(431),
|
||||
UNAVAILABLE_FOR_LEGAL_REASONS(451),
|
||||
|
||||
INTERNAL_SERVER_ERROR(500),
|
||||
NOT_IMPLEMENTED(501),
|
||||
BAD_GATEWAY(502),
|
||||
SERVICE_UNAVAILABLE(503),
|
||||
GATEWAY_TIMEOUT(504),
|
||||
HTTP_VERSION_NOT_SUPPORTED(505),
|
||||
VARIANT_ALSO_NEGOTIATES(506),
|
||||
INSUFFICIENT_STORAGE(507),
|
||||
LOOP_DETECTED(508),
|
||||
NOT_EXTENDED(510),
|
||||
NETWORK_AUTHENTICATION_REQUIRED(511);
|
||||
|
||||
private final int code;
|
||||
LogicResult(int code) { this.code = code; }
|
||||
public int getCode() { return code; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.craftix.cloudnetwebinterface.webserver.api;
|
||||
|
||||
public enum RequestMethod {
|
||||
GET("GET"),
|
||||
POST("POST"),
|
||||
PUT("PUT"),
|
||||
DELETE("DELETE");
|
||||
|
||||
private final String type;
|
||||
|
||||
RequestMethod(String type) { this.type = type; }
|
||||
|
||||
public String getType() { return type; }
|
||||
|
||||
@Override
|
||||
public String toString() { return type; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.craftix.cloudnetwebinterface.webserver.api;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
public @interface Route {
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.craftix.cloudnetwebinterface.webserver.api;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface SetMethod {
|
||||
RequestMethod value() default RequestMethod.GET;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package de.craftix.cloudnetwebinterface.webserver.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import de.craftix.cloudnetwebinterface.webserver.WebServerManager;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public class WebApi {
|
||||
|
||||
protected void sendDataAsJson(HttpExchange exchange, Serializable data) {
|
||||
if (data instanceof String) sendData(exchange, ((String) data).getBytes());
|
||||
else sendData(exchange, new Gson().toJson(data).getBytes());
|
||||
}
|
||||
|
||||
protected void sendData(HttpExchange exchange, byte[] data) {
|
||||
sendData(exchange, LogicResult.OK, data);
|
||||
}
|
||||
|
||||
protected <T> T getDataFromBody(HttpExchange exchange, Type type) {
|
||||
byte[] data = WebServerManager.getBytesFromStream(exchange.getRequestBody());
|
||||
Gson gson = new Gson();
|
||||
return gson.fromJson(new String(data), type);
|
||||
}
|
||||
|
||||
protected void sendData(HttpExchange exchange, LogicResult code, byte[] data) {
|
||||
try {
|
||||
if (data == null || data.length == 0) {
|
||||
exchange.sendResponseHeaders(code.getCode(), -1);
|
||||
OutputStream out = exchange.getResponseBody();
|
||||
out.write(new byte[0]);
|
||||
out.close();
|
||||
}
|
||||
exchange.sendResponseHeaders(code.getCode(), data.length);
|
||||
OutputStream out = exchange.getResponseBody();
|
||||
out.write(data);
|
||||
out.close();
|
||||
}catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package de.craftix.cloudnetwebinterface.webserver.routes;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import de.craftix.cloudnetwebinterface.webserver.WebServerManager;
|
||||
import de.craftix.cloudnetwebinterface.webserver.api.*;
|
||||
import de.dytanic.cloudnet.CloudNet;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
@Route("/api/console")
|
||||
public class ConsoleAPI extends WebApi {
|
||||
public static final int LOG_LENGTH = 500;
|
||||
|
||||
@Authorized
|
||||
public void getConsole(HttpExchange exchange) {
|
||||
String logFolder = "local/logs/";
|
||||
int logID = 0;
|
||||
while (true) {
|
||||
File log = new File(logFolder + "cloudnet." + logID + ".log");
|
||||
if (!log.exists()) {
|
||||
logID--;
|
||||
break;
|
||||
}
|
||||
logID++;
|
||||
}
|
||||
|
||||
File log = new File(logFolder + "cloudnet." + logID + ".log");
|
||||
List<String> lines = WebServerManager.getFileLines(log);
|
||||
if (lines.size() > LOG_LENGTH) lines = lines.subList(lines.size() - LOG_LENGTH, lines.size());
|
||||
StringBuilder out = new StringBuilder();
|
||||
lines.forEach((line) -> out.append("\n").append(line));
|
||||
sendData(exchange, out.toString().getBytes());
|
||||
}
|
||||
|
||||
@Authorized(permission = "web.command.main")
|
||||
@Route("/send")
|
||||
@SetMethod(RequestMethod.POST)
|
||||
public void sendConsoleCommand(HttpExchange exchange) {
|
||||
String cmd = new HttpQuery(exchange).getVariable("command");
|
||||
CloudNet.getInstance().getCommandMap().dispatchCommand(CloudNet.getInstance().getConsoleCommandSender(), cmd);
|
||||
sendData(exchange, LogicResult.OK, "Success".getBytes());
|
||||
}
|
||||
|
||||
@Authorized(permission = "web.reload")
|
||||
@Route("/reload")
|
||||
public void reload(HttpExchange exchange) {
|
||||
sendData(exchange, "Success".getBytes());
|
||||
CloudNet.getInstance().reload();
|
||||
}
|
||||
|
||||
@Authorized(permission = "web.stop")
|
||||
@Route("/stop")
|
||||
public void stop(HttpExchange exchange) {
|
||||
sendData(exchange, "Success".getBytes());
|
||||
CloudNet.getInstance().stop();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package de.craftix.cloudnetwebinterface.webserver.routes;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import de.craftix.cloudnetwebinterface.webserver.api.*;
|
||||
import de.dytanic.cloudnet.driver.CloudNetDriver;
|
||||
import de.dytanic.cloudnet.driver.service.ServiceInfoSnapshot;
|
||||
import de.dytanic.cloudnet.ext.bridge.BridgeServiceProperty;
|
||||
import de.dytanic.cloudnet.ext.bridge.player.ServicePlayer;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
@Route("/api/players")
|
||||
public class PlayerAPI extends WebApi {
|
||||
|
||||
@Authorized
|
||||
@SetMethod(RequestMethod.GET)
|
||||
public void getPlayers(HttpExchange exchange) {
|
||||
Collection<ServiceInfoSnapshot> servers = CloudNetDriver.getInstance().getCloudServiceProvider().getCloudServices();
|
||||
List<Player> processedPlayers = new ArrayList<>();
|
||||
|
||||
for (ServiceInfoSnapshot service : servers) {
|
||||
Collection<ServicePlayer> players = service.getProperty(BridgeServiceProperty.PLAYERS).orElse(new ArrayList<>());
|
||||
for (ServicePlayer player : players) {
|
||||
if (processedPlayers.contains(new Player(player.getName(), null))) continue;
|
||||
String server = player.asBungee().getServer();
|
||||
processedPlayers.add(new Player(player.getName(), server != null ? server : "ausstehend..."));
|
||||
}
|
||||
}
|
||||
|
||||
sendDataAsJson(exchange, processedPlayers.toArray(new Player[0]));
|
||||
}
|
||||
|
||||
private static class Player implements Serializable {
|
||||
public final String name;
|
||||
public final String server;
|
||||
|
||||
public Player(String name, String server) {
|
||||
this.name = name;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Player player = (Player) o;
|
||||
return Objects.equals(name, player.name);
|
||||
}
|
||||
|
||||
@Override public int hashCode() { return Objects.hash(name, server); }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package de.craftix.cloudnetwebinterface.webserver.routes;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import de.craftix.cloudnetwebinterface.utils.DBManager;
|
||||
import de.craftix.cloudnetwebinterface.webserver.api.HttpQuery;
|
||||
import de.craftix.cloudnetwebinterface.webserver.api.Route;
|
||||
import de.craftix.cloudnetwebinterface.webserver.api.WebApi;
|
||||
import de.dytanic.cloudnet.driver.CloudNetDriver;
|
||||
import de.dytanic.cloudnet.ext.bridge.player.ICloudOfflinePlayer;
|
||||
import de.dytanic.cloudnet.ext.bridge.player.IPlayerManager;
|
||||
|
||||
@Route("/api/users")
|
||||
public class UserAPI extends WebApi {
|
||||
|
||||
@Route("/validate")
|
||||
public void validate(HttpExchange exchange) {
|
||||
String sessionKey = exchange.getRequestHeaders().getFirst("Authorization");
|
||||
boolean valid = DBManager.validateSession(sessionKey);
|
||||
sendData(exchange, (valid ? "true" : "false").getBytes());
|
||||
}
|
||||
|
||||
@Route("/login")
|
||||
public void login(HttpExchange exchange) {
|
||||
HttpQuery query = new HttpQuery(exchange);
|
||||
String username = query.getVariable("username");
|
||||
String password = query.getVariable("password");
|
||||
boolean valid = DBManager.checkLoginData(username, password);
|
||||
if (valid) {
|
||||
ICloudOfflinePlayer player = CloudNetDriver.getInstance().getServicesRegistry().getFirstService(IPlayerManager.class).getFirstOfflinePlayer(username);
|
||||
if (player == null) sendData(exchange, "false".getBytes());
|
||||
else sendData(exchange, DBManager.createSessionKey(player.getUniqueId()).getBytes());
|
||||
}else {
|
||||
sendData(exchange, "false".getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
10
Plugins/CloudNetWebinterface/src/main/resources/module.json
Normal file
10
Plugins/CloudNetWebinterface/src/main/resources/module.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"group": "de.craftix",
|
||||
"name": "Webinterface",
|
||||
"version": "1.0-SNAPSHOT",
|
||||
"main": "de.craftix.cloudnetwebinterface.CloudNetWebinterface",
|
||||
"description": "This is an Admin-Webinterface for CloudNet v3",
|
||||
"minimumCoreVersion": "3.4.0",
|
||||
"title": "Webinterface",
|
||||
"author": "Leon Hoppe"
|
||||
}
|
||||
Reference in New Issue
Block a user