diff --git a/web_api/pom.xml b/web_api/pom.xml index bb5d46c..1d74966 100644 --- a/web_api/pom.xml +++ b/web_api/pom.xml @@ -1,32 +1,56 @@ - + + 4.0.0 + Web API + + web_api + 0.1-SNAPSHOT + bundle + arcadexsystem eu.arcadex.system ${global.version} - 4.0.0 - - web_api - jar eu.arcadex.system - database - ${global.version} + core + 0.1-SNAPSHOT - eu.arcadex.system - configuration - ${global.version} + org.eclipse.jetty + jetty-server + 9.3.6.v20151106 - eu.arcadex.system - server_manager - ${global.version} + com.google.code.gson + gson + 2.3.1 + + + ${groupId}.${artifactId}-${version} + + + org.apache.felix + maven-bundle-plugin + 2.3.5 + true + + + Arcadex System: ${name} ${version} + ${groupId}.${artifactId} + eu.arcadex.system.web_api.Activator + eu.arcadex.system.core.api, * + + + + + \ No newline at end of file diff --git a/web_api/src/main/java/eu/arcadex/system/web_api/Activator.java b/web_api/src/main/java/eu/arcadex/system/web_api/Activator.java new file mode 100644 index 0000000..7721610 --- /dev/null +++ b/web_api/src/main/java/eu/arcadex/system/web_api/Activator.java @@ -0,0 +1,30 @@ +package eu.arcadex.system.web_api; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; +import eu.arcadex.system.core.api.ICore; + +/** + * @author DmitriyMX + * 2016 + */ +public class Activator implements BundleActivator { + private ServiceTracker tracker; + private WebModule webModule; + + @Override + public void start(BundleContext bundleContext) throws Exception { + tracker = new ServiceTracker(bundleContext, ICore.class.getName(), null); + tracker.open(); + + webModule = new WebModule(tracker); + webModule.init(); + } + + @Override + public void stop(BundleContext bundleContext) throws Exception { + webModule.stop(); + tracker.close(); + } +} diff --git a/web_api/src/main/java/eu/arcadex/system/web_api/RequestHandler.java b/web_api/src/main/java/eu/arcadex/system/web_api/RequestHandler.java new file mode 100644 index 0000000..6041f51 --- /dev/null +++ b/web_api/src/main/java/eu/arcadex/system/web_api/RequestHandler.java @@ -0,0 +1,303 @@ +package eu.arcadex.system.web_api; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Daniil on 19.03.2016. + * @author DmitriyMX 2016 (порт на ArcadexSystem Reborn) + */ +public class RequestHandler extends AbstractHandler { + private final DatabaseModule database; + private final ArcadexSystem system; + private final Gson gson; + private Logger logger = LoggerFactory.getLogger(RequestHandler.class.getName()); + private final String dbKey = "jhkljdsjklfjkljdasf"; + private final String apiKey = "hkdhaskhafasdf"; + private List blackList = new ArrayList<>(); + + + protected String md5(String origin) { + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + return ""; + } + md.update(origin.getBytes()); + byte[] digest = md.digest(); + StringBuffer sb = new StringBuffer(); + for (byte b : digest) { + sb.append(String.format("%02x", b & 0xff)); + } + + return sb.toString(); + } + + public String getRequestFingerprint(Request request, String body, String url, String apiKey) { + String text = apiKey + ":" + request.getMethod() + ":" + url + ":" + (request.getMethod().equalsIgnoreCase("POST") ? body + ":" + apiKey : apiKey); + System.out.println("Fingerprint text: " + text); + System.out.println("Fingerprint: " + md5(text)); + return md5(text); + } + + @Override + public void handle(String url, Request request, HttpServletRequest httpRequest, HttpServletResponse response) throws IOException, ServletException { + response.setContentType("text/html;charset=utf-8"); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Headers", "Authorization"); + request.setHandled(true); + if (!request.getMethod().equalsIgnoreCase("OPTIONS")) { + Response response1 = handleInternal(url, request, httpRequest, response); + response.getWriter().println(response1.toJson(gson)); + } else { + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().println(new Response("success", "Ok").toJson(gson)); + } + } + + protected Response handleInternal(String url, Request request, HttpServletRequest httpRequest, HttpServletResponse response) throws IOException, ServletException { + try { + if (url.trim().startsWith("/db/")) { + // Form handling and authorization + + String postData = ""; + if (request.getMethod().equalsIgnoreCase("POST")) { + StringBuilder builder = new StringBuilder(); + String aux; + + while ((aux = httpRequest.getReader().readLine()) != null) { + builder.append(aux); + } + + postData = builder.toString(); + } + + if (request.getHeader("Authorization") == null || + !request.getHeader("Authorization").equals("Basic " + getRequestFingerprint(request, postData, url, dbKey))) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return new Response("failed", "Error: missing or wrong Authorization header"); + } + + // Here comes actual DB listener + + String key = url.substring(4).trim(); + if (request.getMethod().equalsIgnoreCase("GET")) { + // Methods for reading db values + if (!database.containsKey(key)) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + return (new Response("error", "Error: key not found")); + } else { + response.setStatus(HttpServletResponse.SC_OK); + return (new Response("success", database.get(key))); + } + } else if (request.getMethod().equalsIgnoreCase("POST")) { + database.set(key, postData); + response.setStatus(HttpServletResponse.SC_OK); + return (new Response("success", "Ok")); + } else if (request.getMethod().equalsIgnoreCase("DELETE")) { + if (!database.containsKey(key)) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + return (new Response("error", "Error: key not found")); + } else { + response.setStatus(HttpServletResponse.SC_OK); + database.remove(key); + return (new Response("success", "Ok")); + } + } else { + response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + return new Response("error", "Error: this method is not supported for RESTful database"); + } + + } else if (url.trim().startsWith("/api/")) { + return handleApiRequests(url, request, httpRequest, response); + } else { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + return (new Response("error", "Error: action not found")); + } + } catch (Exception e) { + e.printStackTrace(); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + return (new Response("error", "Error: internal server error")); + + } + } + + public Response handleApiRequests(String url, Request request, HttpServletRequest httpRequest, HttpServletResponse response) throws IOException, ServletException { + // Auth check + if (request.getHeader("Authorization") == null) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return (new Response("failed", "Error: missing Authorization header")); + } + + String[] general = request.getHeader("Authorization").substring(6).trim().split(":"); + if (general.length != 2) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return (new Response("failed", "Error: malformed Authorization header")); + } + + String userName = general[0]; + String hash = general[1]; + + if (!md5(apiKey + ":" + userName + ":" + apiKey).equals(hash)) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return (new Response("failed", "Error: wrong Authorization header")); + } + + if (blackList.contains(userName)) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + return (new Response("failed", "Error: this user is forbidden to connect this server")); + } + + if (!request.getMethod().equalsIgnoreCase("POST")) { + response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + return (new Response("failed", "Error: all API requests are done by POST")); + } + + // General methods + url = url.substring(5); + if (url.startsWith("check_login")) { + response.setStatus(HttpServletResponse.SC_OK); + logger.info("Login accepted: " + userName); + return (new Response("success", "Ok")); + } + + if (url.startsWith("list_servers")) { + JsonArray array = new JsonArray(); + for (ServerData srv : new ArrayList<>(system.getServerManager().getServerList())) { + JsonObject object = new JsonObject(); + object.addProperty("id", srv.getId()); + object.addProperty("type", srv.getType()); + object.addProperty("modification", srv.getModification()); + object.addProperty("status", srv.getStatus().toString()); + object.addProperty("canAccept", srv.canAcceptPlayers()); + object.addProperty("port", srv.getPort()); + object.addProperty("online", srv.getOnline()); + object.addProperty("max", srv.getMaxOnline()); + array.add(object); + } + return new Response("success", array); + } + + if (url.startsWith("stop_server")) { + String sid = url.substring(12); + ServerData serverData = system.getServerManager().getServerById(sid); + if (serverData == null) { + return new Response("error", "Error: server not found"); + } + serverData.getWorker().stopServer(); + return new Response("success", "Ok"); + } + + if (url.startsWith("change_variables")) { + String sid = url.substring(17); + ServerData serverData = system.getServerManager().getServerById(sid); + if (serverData == null) { + return new Response("error", "Error: server not found"); + } + + if (request.getParameter("status") != null) { + try { + serverData.setStatus(ServerData.ServerStatus.valueOf(request.getParameter("status"))); + } catch (Exception e) { + return new Response("error", "Error: unknown server status '" + request.getParameter("status") + "'!"); + } + } + + if (request.getParameter("policy") != null) { + try { + serverData.setServerJoinPolicy(ServerData.ServerJoinMode.valueOf(request.getParameter("policy"))); + } catch (Exception e) { + return new Response("error", "Error: unknown server join policy '" + request.getParameter("policy") + "'!"); + } + } + + if (request.getParameter("frozen") != null) { + serverData.setFrozen(request.getParameter("frozen").equals("true") || request.getParameter("frozen").equals("1")); + } + + return new Response("success", "Ok"); + } + + if (url.startsWith("full_information")) { + String sid = url.substring(17); + ServerData serverData = system.getServerManager().getServerById(sid); + if (serverData == null) { + return new Response("error", "Error: server not found"); + } + + JsonObject object = new JsonObject(); + object.addProperty("id", serverData.getId()); + object.addProperty("type", serverData.getType()); + object.addProperty("modification", serverData.getModification()); + object.addProperty("status", serverData.getStatus().toString()); + object.addProperty("canAccept", serverData.canAcceptPlayers()); + object.addProperty("port", serverData.getPort()); + object.addProperty("online", serverData.getOnline()); + object.addProperty("max", serverData.getMaxOnline()); + object.addProperty("frozen", serverData.isFrozen()); + object.addProperty("policy", serverData.getServerJoinPolicy().toString()); + JsonArray array = new JsonArray(); + array.add(new JsonPrimitive("This operation is not supported")); + + object.add("players", array); + + return new Response("success", object); + } + + if (url.startsWith("stop")) { + String sid = url.substring(5); + ServerData serverData = system.getServerManager().getServerById(sid); + if (serverData == null) { + return new Response("error", "Error: server not found"); + } + + serverData.getWorker().stopServer(); + return new Response("success", "Ok"); + } + + if (url.startsWith("freeze")) { + String sid = url.substring("freeze".length() + 1); + ServerData serverData = system.getServerManager().getServerById(sid); + if (serverData == null) { + return new Response("error", "Error: server not found"); + } + serverData.setFrozen(!serverData.isFrozen()); + } + + if (url.startsWith("command")) { + String sid = url.substring("command".length() + 1); + ServerData serverData = system.getServerManager().getServerById(sid); + if (serverData == null) { + return new Response("error", "Error: server not found"); + } + StringBuilder builder = new StringBuilder(); + String aux; + + while ((aux = httpRequest.getReader().readLine()) != null) { + builder.append(aux); + } + + String postData = builder.toString(); + serverData.getWorker().consoleCommand(postData); + return new Response("success", "Ok"); + } + + return (new Response("success", "Not implemented yet")); + } +} diff --git a/web_api/src/main/java/eu/arcadex/system/web_api/Response.java b/web_api/src/main/java/eu/arcadex/system/web_api/Response.java new file mode 100644 index 0000000..53b176f --- /dev/null +++ b/web_api/src/main/java/eu/arcadex/system/web_api/Response.java @@ -0,0 +1,44 @@ +package eu.arcadex.system.web_api; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +/** + * @author Daniil 05.11.2015 + * @author DmitriyMX 2016 (порт на ArcadexSystem Reborn) + */ +public class Response { + private String status; + private JsonElement response; + + public Response(String status, JsonElement response) { + this.status = status; + this.response = response; + } + + public Response(String status, String response) { + this.status = status; + this.response = new JsonPrimitive(response); + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public JsonElement getResponse() { + return response; + } + + public void setResponse(JsonElement response) { + this.response = response; + } + + public String toJson(Gson gson) { + return gson.toJson(this); + } +} diff --git a/web_api/src/main/java/eu/arcadex/system/web_api/WebModule.java b/web_api/src/main/java/eu/arcadex/system/web_api/WebModule.java new file mode 100644 index 0000000..8bf2d7a --- /dev/null +++ b/web_api/src/main/java/eu/arcadex/system/web_api/WebModule.java @@ -0,0 +1,50 @@ +package eu.arcadex.system.web_api; + +import com.google.gson.Gson; +import eu.arcadex.system.core.api.ICore; +import org.eclipse.jetty.server.Server; +import org.osgi.util.tracker.ServiceTracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Модуль для поддержки Web-интерфейса ArcadexSystem + * + * @author Daniil 2015 + * @author DmitriyMX 2016 (порт на ArcadexSystem Reborn) + */ +class WebModule { + private Logger logger = LoggerFactory.getLogger(WebModule.class.getName()); + private ICore core; + private Server server; + private final int port = 8090; + + WebModule(ServiceTracker tracker) { + core = tracker.getService(); + if (core == null) { + throw new RuntimeException("Service \"ICore\" not found!"); + } + } + + void init() { + logger.trace("Initializing WEB-server on localhost: {}", port); + + server = new Server(port); + server.setStopAtShutdown(true); + server.setHandler(new RequestHandler((DatabaseModule) system.getModule("database"), system, new Gson(), system.getLogger())); + + try { + server.start(); + } catch (Exception e) { + logger.error("Error start JettyServer", e); + } + } + + void stop() { + try { + server.stop(); + } catch (Exception e) { + logger.error("Error stop JettyServer", e); + } + } +}