diff --git a/web_api/src/main/java/eu/arcadex/system/web_api/RequestRouter.java b/web_api/src/main/java/eu/arcadex/system/web_api/RequestRouter.java new file mode 100644 index 0000000..2721c73 --- /dev/null +++ b/web_api/src/main/java/eu/arcadex/system/web_api/RequestRouter.java @@ -0,0 +1,82 @@ +package eu.arcadex.system.web_api; + +import com.google.gson.Gson; +import eu.arcadex.system.web_api.methods.DBMethodHandler; +import eu.arcadex.system.web_api.methods.AdminMethodHandler; +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; + +/** + * @author Daniil on 19.03.2016. + * @author DmitriyMX 2016 (порт на ArcadexSystem Reborn) + */ +public class RequestRouter extends AbstractHandler { + private final Gson gson; + private Logger logger = LoggerFactory.getLogger(RequestRouter.class.getName()); + + private DBMethodHandler dbHandler = new DBMethodHandler(); + private AdminMethodHandler restMethodHandler = new AdminMethodHandler(this); + + public RequestRouter() { + this.gson = new Gson(); + } + + @Override + public void handle(String url, Request request, HttpServletRequest httpRequest, HttpServletResponse response) throws IOException, ServletException { + request.setHandled(true); + response.setContentType("text/html;charset=utf-8"); + // Some headers for Cross Domain Origin + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Headers", "Authorization"); + + // OPTIONS method is used by cross ajax request to + // check which hosts are allowed to send requests to this backend + // We don't want to have something done if we receive an OPTIONS request + if (!request.getMethod().equalsIgnoreCase("OPTIONS")) { + Response response1 = route(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)); + } + } + + /** + * Redirect requests to specific MethodHandlers + * + * @param url + * @param request + * @param httpRequest + * @param response + * @return + * @throws IOException + * @throws ServletException + */ + protected Response route(String url, Request request, HttpServletRequest httpRequest, HttpServletResponse response) throws IOException, ServletException { + try { + if (url.trim().startsWith("/db/")) { + return dbHandler.handle(url, request, httpRequest, response); + } else if (url.trim().startsWith("/api/")) { + return restMethodHandler.handle(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 Logger getLogger() { + return logger; + } +} 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 index 8bf2d7a..02d386a 100644 --- 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 @@ -1,6 +1,5 @@ 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; @@ -31,7 +30,7 @@ class WebModule { server = new Server(port); server.setStopAtShutdown(true); - server.setHandler(new RequestHandler((DatabaseModule) system.getModule("database"), system, new Gson(), system.getLogger())); + server.setHandler(new RequestRouter()); try { server.start(); @@ -44,7 +43,7 @@ class WebModule { try { server.stop(); } catch (Exception e) { - logger.error("Error stop JettyServer", e); + logger.error("Error stopping JettyServer", e); } } } diff --git a/web_api/src/main/java/eu/arcadex/system/web_api/methods/AbstractMethodHandler.java b/web_api/src/main/java/eu/arcadex/system/web_api/methods/AbstractMethodHandler.java new file mode 100644 index 0000000..46ef886 --- /dev/null +++ b/web_api/src/main/java/eu/arcadex/system/web_api/methods/AbstractMethodHandler.java @@ -0,0 +1,43 @@ +package eu.arcadex.system.web_api.methods; + +import eu.arcadex.system.web_api.Response; +import org.eclipse.jetty.server.Request; + +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; + +/** + * @author Daniil on 06.04.16. + */ +public abstract class AbstractMethodHandler { + protected 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); + } + + protected String md5(String origin) { + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + return ""; + } + md.update(origin.getBytes()); + byte[] digest = md.digest(); + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b & 0xff)); + } + + return sb.toString(); + } + + public abstract Response handle(String url, Request request, HttpServletRequest httpRequest, HttpServletResponse response) throws IOException, ServletException; + +} 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/methods/AdminMethodHandler.java similarity index 53% rename from web_api/src/main/java/eu/arcadex/system/web_api/RequestHandler.java rename to web_api/src/main/java/eu/arcadex/system/web_api/methods/AdminMethodHandler.java index 6041f51..312f2af 100644 --- a/web_api/src/main/java/eu/arcadex/system/web_api/RequestHandler.java +++ b/web_api/src/main/java/eu/arcadex/system/web_api/methods/AdminMethodHandler.java @@ -1,144 +1,57 @@ -package eu.arcadex.system.web_api; +package eu.arcadex.system.web_api.methods; -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; +import eu.arcadex.system.web_api.RequestRouter; +import eu.arcadex.system.web_api.Response; 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) + * Handler for Control Panel API requests + * + * @author Daniil on 06.04.16. */ -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"; +public class AdminMethodHandler extends AbstractMethodHandler { + // TODO: Get this two variables from cfg private final String apiKey = "hkdhaskhafasdf"; private List blackList = new ArrayList<>(); + private RequestRouter handler; - 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); + public AdminMethodHandler(RequestRouter handler) { + this.handler = handler; } + /** + * Handle CP requests + *

+ * Authorization: + *

+ * All requests should contain Authorization handler + * Formed like this: + * Basic {login}:md5({apiKey}:{login}:{apiKey}) + *

+ * The feature of this way is not to have a dedicated database + * for storing admin accounts. Also, instead of registering new + * accounts we just need to generate signature for user to operate + *

+ * Hint: api+login+api hash acts as password + * + * @param url + * @param request + * @param httpRequest + * @param response + * @return + * @throws IOException + * @throws ServletException + */ @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 { + public Response handle(String url, Request request, HttpServletRequest httpRequest, HttpServletResponse response) throws IOException, ServletException { // Auth check if (request.getHeader("Authorization") == null) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); @@ -171,12 +84,21 @@ public class RequestHandler extends AbstractHandler { // 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")); + String methodName = url.substring(0, url.indexOf("/")); + + switch (methodName.toLowerCase()) { + case "check_login": + // If user has come to this point, than he has correct + // authorization handler + response.setStatus(HttpServletResponse.SC_OK); + handler.getLogger().info("Login accepted: " + userName); + return (new Response("success", "Ok")); + default: + return (new Response("success", "Not implemented yet")); } + // TODO: Rewrite everything to fit to new module system + /* if (url.startsWith("list_servers")) { JsonArray array = new JsonArray(); for (ServerData srv : new ArrayList<>(system.getServerManager().getServerList())) { @@ -297,7 +219,6 @@ public class RequestHandler extends AbstractHandler { 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/methods/DBMethodHandler.java b/web_api/src/main/java/eu/arcadex/system/web_api/methods/DBMethodHandler.java new file mode 100644 index 0000000..51bf9f7 --- /dev/null +++ b/web_api/src/main/java/eu/arcadex/system/web_api/methods/DBMethodHandler.java @@ -0,0 +1,109 @@ +package eu.arcadex.system.web_api.methods; + +import eu.arcadex.system.web_api.RequestRouter; +import eu.arcadex.system.web_api.Response; +import org.eclipse.jetty.server.Request; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; + +/** + * Handler for Database API requests + * + * @author Daniil on 06.04.16. + */ +public class DBMethodHandler extends AbstractMethodHandler { + // TODO: Get this from cfg + private final String dbKey = "jhkljdsjklfjkljdasf"; + // TODO: Replace with module call + private HashMap database = new HashMap<>(); + + + /** + * Handle database API requests + * + * Authorization: + * + * In order to access API methods you need to provide + * request signature in Authorization handler. + * + * For POST requests the signature is: + * Basic md5({apiKey}:POST:{uri[/db/key/]}:{postBody}:{apiKey}) + * + * For other methods the signature is generated like this + * Basic md5({apiKey}:{methodName}:{uri[/db/key/]}:{apiKey}) + * + * Usage: + * + * To get variable you need to send + * GET request to /db/{key} + * + * To set variable you need to send + * POST request to /db/{key} + * + * To delete variable you need to send + * DELETE request to /db/{key} + * + * @param url + * @param request + * @param httpRequest + * @param response + * @return + * @throws IOException + * @throws ServletException + */ + @Override + public Response handle(String url, Request request, HttpServletRequest httpRequest, HttpServletResponse response) throws IOException, ServletException { + // 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.put(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"); + } + } +}