From e6fca61a59dcb42c768f23402b2f0d17d36acd6b Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 29 Jul 2017 20:01:08 +0300 Subject: [PATCH] Finished writing webserver functionality: - Http server if cert not found - Automatic path based on level - Cleaned Responses a bit - Using own Authentication - Commands notify about missing user only when using auth - New settings: UsingExternalWebserver, ExternalWebServerLinkProtocol (renamed LinkProtocol) Removed TODO note from NicknamesTable --- .../java/com/djrapitops/plan/Settings.java | 3 +- .../plan/command/commands/AnalyzeCommand.java | 30 +- .../plan/command/commands/InspectCommand.java | 2 +- .../plan/database/tables/NicknamesTable.java | 16 +- .../plan/ui/webserver/Authenticator.java | 66 ---- .../plan/ui/webserver/WebServer.java | 360 +++++++++++++----- .../response/AnalysisPageResponse.java | 5 +- .../webserver/response/ForbiddenResponse.java | 5 +- .../response/InspectPageResponse.java | 4 +- .../response/InternalErrorResponse.java | 5 +- .../response/JavaScriptResponse.java | 4 +- .../webserver/response/NotFoundResponse.java | 8 +- .../response/PlayersPageResponse.java | 4 +- .../response/PromptAuthorizationResponse.java | 7 +- .../webserver/response/RedirectResponse.java | 5 +- .../plan/ui/webserver/response/Response.java | 23 +- .../djrapitops/plan/utilities/HtmlUtils.java | 38 +- Plan/src/main/resources/config.yml | 3 +- 18 files changed, 328 insertions(+), 260 deletions(-) delete mode 100644 Plan/src/main/java/com/djrapitops/plan/ui/webserver/Authenticator.java diff --git a/Plan/src/main/java/com/djrapitops/plan/Settings.java b/Plan/src/main/java/com/djrapitops/plan/Settings.java index adc62ee66..4a29ab0c6 100644 --- a/Plan/src/main/java/com/djrapitops/plan/Settings.java +++ b/Plan/src/main/java/com/djrapitops/plan/Settings.java @@ -28,6 +28,7 @@ public enum Settings { SECURITY_IP_UUID("Settings.WebServer.Security.DisplayIPsAndUUIDs"), GRAPH_PLAYERS_USEMAXPLAYERS_SCALE("Customization.Graphs.PlayersOnlineGraph.UseMaxPlayersAsScale"), PLAYERLIST_SHOW_IMAGES("Customization.SmallHeadImagesOnAnalysisPlayerlist"), + EXTERNAL_WEBSERVER("Settings.WebServer.UsingExternalWebServer"), // Integer ANALYSIS_MINUTES_FOR_ACTIVE("Settings.Analysis.MinutesPlayedUntilConsidiredActive"), SAVE_CACHE_MIN("Settings.Cache.DataCache.SaveEveryXMinutes"), @@ -53,7 +54,7 @@ public enum Settings { WEBSERVER_CERTIFICATE_KEYPASS("Settings.WebServer.Security.Certificate.KeyPass"), WEBSERVER_CERTIFICATE_STOREPASS("Settings.WebServer.Security.Certificate.KeyPass"), WEBSERVER_CERTIFICATE_ALIAS("Settings.WebServer.Security.Certificate.Alias"), - LINK_PROTOCOL("Settings.WebServer.LinkProtocol"), + LINK_PROTOCOL("Settings.WebServer.ExternalWebServerLinkProtocol"), // SERVER_NAME("Customization.ServerName"), // diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/AnalyzeCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/AnalyzeCommand.java index 5b1b2ce97..93db0e186 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/AnalyzeCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/AnalyzeCommand.java @@ -68,23 +68,25 @@ public class AnalyzeCommand extends SubCommand { } sender.sendMessage(Phrase.GRABBING_DATA_MESSAGE + ""); - plugin.getRunnableFactory().createNew(new AbsRunnable("WebUser exist check task") { - @Override - public void run() { - try { - if (CommandUtils.isPlayer(sender)) { - boolean senderHasWebUser = plugin.getDB().getSecurityTable().userExists(sender.getName()); - if (!senderHasWebUser) { - sender.sendMessage(ChatColor.YELLOW + "[Plan] You might not have a web user, use /plan register "); + if (plugin.getUiServer().isAuthRequired()) { + plugin.getRunnableFactory().createNew(new AbsRunnable("WebUser exist check task") { + @Override + public void run() { + try { + if (CommandUtils.isPlayer(sender)) { + boolean senderHasWebUser = plugin.getDB().getSecurityTable().userExists(sender.getName()); + if (!senderHasWebUser) { + sender.sendMessage(ChatColor.YELLOW + "[Plan] You might not have a web user, use /plan register "); + } } + } catch (Exception e) { + Log.toLog(this.getClass().getName() + getName(), e); + } finally { + this.cancel(); } - } catch (Exception e) { - Log.toLog(this.getClass().getName() + getName(), e); - } finally { - this.cancel(); } - } - }).runTaskAsynchronously(); + }).runTaskAsynchronously(); + } updateCache(); runMessageSenderTask(sender); return true; diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/InspectCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/InspectCommand.java index 50ac77269..627af3b88 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/InspectCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/InspectCommand.java @@ -90,7 +90,7 @@ public class InspectCommand extends SubCommand { return; } sender.sendMessage(Phrase.GRABBING_DATA_MESSAGE + ""); - if (CommandUtils.isPlayer(sender)) { + if (CommandUtils.isPlayer(sender) && plugin.getUiServer().isAuthRequired()) { boolean senderHasWebUser = plugin.getDB().getSecurityTable().userExists(sender.getName()); if (!senderHasWebUser) { sender.sendMessage(ChatColor.YELLOW + "[Plan] You might not have a web user, use /plan register "); diff --git a/Plan/src/main/java/com/djrapitops/plan/database/tables/NicknamesTable.java b/Plan/src/main/java/com/djrapitops/plan/database/tables/NicknamesTable.java index b94152b47..037004271 100644 --- a/Plan/src/main/java/com/djrapitops/plan/database/tables/NicknamesTable.java +++ b/Plan/src/main/java/com/djrapitops/plan/database/tables/NicknamesTable.java @@ -199,15 +199,17 @@ public class NicknamesTable extends Table { lastNicks.put(id, nickname); } } - - //TODO figure out what the heck that method does @Rsl1122 - for (Map.Entry entrySet : lastNicks.entrySet()) { - Integer id = entrySet.getKey(); - String lastNick = entrySet.getValue(); + for (Map.Entry entry : lastNicks.entrySet()) { + Integer id = entry.getKey(); + String lastNick = entry.getValue(); List list = nicks.get(id); - list.remove(lastNick); //NOTE: Remove here? - list.add(lastNick); //NOTE: And add here again? + + // Moves the last known nickname to the end of the List. + // This is due to the way nicknames are added to UserData, + // Nicknames are stored as a Set and last Nickname is a separate String. + list.remove(lastNick); + list.add(lastNick); } return nicks; diff --git a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/Authenticator.java b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/Authenticator.java deleted file mode 100644 index 5c797d3a7..000000000 --- a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/Authenticator.java +++ /dev/null @@ -1,66 +0,0 @@ -package main.java.com.djrapitops.plan.ui.webserver; - -import com.sun.net.httpserver.BasicAuthenticator; -import main.java.com.djrapitops.plan.Log; -import main.java.com.djrapitops.plan.Plan; -import main.java.com.djrapitops.plan.data.WebUser; -import main.java.com.djrapitops.plan.database.tables.SecurityTable; -import main.java.com.djrapitops.plan.utilities.PassEncryptUtil; - -import java.sql.SQLException; - -public class Authenticator extends BasicAuthenticator { - - private final Plan plugin; - - public Authenticator(Plan plugin, String realm) { - super(realm); - this.plugin = plugin; - } - - @Override - public boolean checkCredentials(String user, String pwd) { - try { - return isAuthorized(user, pwd, this.realm); - } catch (Exception e) { - Log.toLog(this.getClass().getName(), e); - return false; - } - } - - private boolean isAuthorized(String user, String passwordRaw, String target) throws PassEncryptUtil.CannotPerformOperationException, PassEncryptUtil.InvalidHashException, SQLException { - SecurityTable securityTable = plugin.getDB().getSecurityTable(); - if (!securityTable.userExists(user)) { - return false; - } - WebUser securityInfo = securityTable.getSecurityInfo(user); - - boolean correctPass = PassEncryptUtil.verifyPassword(passwordRaw, securityInfo.getSaltedPassHash()); - if (!correctPass) { - return false; - } - int permLevel = securityInfo.getPermLevel(); // Lower number has higher clearance. - int required = getRequiredPermLevel(target, securityInfo.getName()); - return permLevel <= required; - } - - private int getRequiredPermLevel(String target, String user) { - String[] t = target.split("/"); - if (t.length < 3) { - return 0; - } - final String wantedUser = t[2].toLowerCase().trim(); - final String theUser = user.trim().toLowerCase(); - if (t[1].equals("players")) { - return 1; - } - if (t[1].equals("player")) { - if (wantedUser.equals(theUser)) { - return 2; - } else { - return 1; - } - } - return 0; - } -} diff --git a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/WebServer.java b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/WebServer.java index a1e689f75..080279ac6 100644 --- a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/WebServer.java +++ b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/WebServer.java @@ -1,14 +1,20 @@ package main.java.com.djrapitops.plan.ui.webserver; +import com.djrapitops.plugin.utilities.Verify; import com.sun.net.httpserver.*; import main.java.com.djrapitops.plan.Log; import main.java.com.djrapitops.plan.Phrase; import main.java.com.djrapitops.plan.Plan; import main.java.com.djrapitops.plan.Settings; +import main.java.com.djrapitops.plan.data.WebUser; +import main.java.com.djrapitops.plan.database.tables.SecurityTable; import main.java.com.djrapitops.plan.ui.html.DataRequestHandler; import main.java.com.djrapitops.plan.ui.webserver.response.*; import main.java.com.djrapitops.plan.utilities.HtmlUtils; +import main.java.com.djrapitops.plan.utilities.MiscUtils; +import main.java.com.djrapitops.plan.utilities.PassEncryptUtil; import main.java.com.djrapitops.plan.utilities.uuid.UUIDUtility; +import org.bukkit.ChatColor; import javax.net.ssl.*; import java.io.FileInputStream; @@ -20,6 +26,8 @@ import java.net.URI; import java.security.*; import java.security.cert.Certificate; import java.security.cert.CertificateException; +import java.util.Base64; +import java.util.List; import java.util.UUID; import java.util.concurrent.Executors; @@ -33,7 +41,8 @@ public class WebServer { private boolean enabled = false; private HttpServer server; private final int port; - private boolean shutdown; + + private boolean usingHttps; /** * Class Constructor. @@ -45,7 +54,6 @@ public class WebServer { public WebServer(Plan plugin) { this.plugin = plugin; this.port = Settings.WEBSERVER_PORT.getNumber(); - shutdown = false; dataReqHandler = new DataRequestHandler(plugin); } @@ -59,89 +67,82 @@ public class WebServer { } Log.info(Phrase.WEBSERVER_INIT.toString()); try { - String keyStorePath = Settings.WEBSERVER_CERTIFICATE_PATH.toString(); - if (!keyStorePath.contains(":")) { - keyStorePath = plugin.getDataFolder() + keyStorePath; - } - char[] storepass = Settings.WEBSERVER_CERTIFICATE_STOREPASS.toString().toCharArray(); - char[] keypass = Settings.WEBSERVER_CERTIFICATE_KEYPASS.toString().toCharArray(); - String alias = Settings.WEBSERVER_CERTIFICATE_ALIAS.toString(); + usingHttps = startHttpsServer(); - boolean startSuccessful = false; - try (FileInputStream fIn = new FileInputStream(keyStorePath)) { - KeyStore keystore = KeyStore.getInstance("JKS"); + Log.debug(usingHttps ? "Https Start Successful." : "Https Start Failed."); - keystore.load(fIn, storepass); - Certificate cert = keystore.getCertificate(alias); + if (!usingHttps) { + server = HttpServer.create(); + return; + // TODO Http Redirect Server +// } else { +// HttpServer protocolUpdateServer = HttpServer.create(new InetSocketAddress(port), 10); +// protocolUpdateServer.setExecutor(Executors.newSingleThreadExecutor()); +// +// protocolUpdateServer.createContext("/", new HttpHandler() { +// @Override +// public void handle(HttpExchange exchange) throws IOException { +// try { +// URI uri = exchange.getRequestURI(); +// Headers requestHeaders = exchange.getRequestHeaders(); +// List host = requestHeaders.get("Host"); +// String currentAddress; +// if (Verify.isEmpty(host)) { +// currentAddress = HtmlUtils.getIP(); +// } else { +// currentAddress = host.get(0); +// } +// Headers responseHeaders = exchange.getResponseHeaders(); +// String newAddress = "https://" + currentAddress + uri.toASCIIString(); +// responseHeaders.set("Location", newAddress); +// Log.debug("Redirected http " + uri + " to new address: " + newAddress); +// exchange.sendResponseHeaders(301, 0); +// exchange.getResponseBody().close(); +// } finally { +// exchange.close(); +// } +// +// } +// }); - Log.info("Found Certificate: " + cert.getType()); - - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); - keyManagerFactory.init(keystore, keypass); - - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); - trustManagerFactory.init(keystore); - - server = HttpsServer.create(new InetSocketAddress(port), 10); - SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); - sslContext.init(keyManagerFactory.getKeyManagers(), null/*trustManagerFactory.getTrustManagers()*/, null); - - ((HttpsServer) server).setHttpsConfigurator(new HttpsConfigurator(sslContext) { - @Override - public void configure(HttpsParameters params) { - SSLEngine engine = sslContext.createSSLEngine(); - - params.setNeedClientAuth(false); - params.setCipherSuites(engine.getEnabledCipherSuites()); - params.setProtocols(engine.getEnabledProtocols()); - - SSLParameters defaultSSLParameters = sslContext.getDefaultSSLParameters(); - params.setSSLParameters(defaultSSLParameters); - } - }); - startSuccessful = true; - } catch (KeyManagementException | NoSuchAlgorithmException e) { - Log.error("WebServer: SSL Context Initialization Failed."); - Log.toLog(this.getClass().getName(), e); - } catch (FileNotFoundException e) { - Log.error("!--------!---------!---------!"); - Log.error("WebServer: SSL Certificate KeyStore File not Found: " + keyStorePath); - Log.error("!--------!---------!---------!"); - } catch (KeyStoreException | CertificateException | UnrecoverableKeyException e) { - Log.error("WebServer: SSL Certificate loading Failed."); - Log.toLog(this.getClass().getName(), e); +// protocolUpdateServer.start(); } - Log.debug("Start Successful: " + startSuccessful); - - if (!startSuccessful) { - return; // TODO Http Server - } - - Log.debug("Create server context"); HttpContext context = server.createContext("/", new HttpHandler() { @Override - public void handle(HttpExchange xghng) throws IOException { - HttpsExchange exchange = (HttpsExchange) xghng; + public void handle(HttpExchange xchange) throws IOException { + OutputStream os = null; try { + HttpsExchange exchange = (HttpsExchange) xchange; URI uri = exchange.getRequestURI(); String target = uri.toString(); - Response response = getResponse(target); + + WebUser user = null; + if (usingHttps) { + user = getUser(exchange.getRequestHeaders()); + + // Prompt authorization + if (user == null) { + Headers responseHeaders = exchange.getResponseHeaders(); + responseHeaders.set("WWW-Authenticate", "Basic realm=\"/\";"); + } + } + Response response = getResponse(target, user); + String content = response.getContent(); exchange.sendResponseHeaders(response.getCode(), content.length()); + os = exchange.getResponseBody(); - OutputStream os = exchange.getResponseBody(); os.write(content.getBytes()); - os.close(); } catch (Exception e) { Log.toLog(this.getClass().getName(), e); throw e; + } finally { + MiscUtils.close(os); + xchange.close(); } } }); - if (startSuccessful) { - context.setAuthenticator(new Authenticator(plugin, "/")); - } server.setExecutor(Executors.newSingleThreadExecutor()); server.start(); @@ -154,68 +155,192 @@ public class WebServer { } } -// if (!request.hasAuthorization()) { -// return new PromptAuthorizationResponse(output); -// } -// try { -// if (!isAuthorized(request)) { -// ForbiddenResponse response403 = new ForbiddenResponse(output); -// String content = "

403 Forbidden - Access Denied

" -// + "

Unauthorized User.
" -// + "Make sure your user has the correct access level.
" -// + "You can use /plan web check to check the permission level.

"; -// response403.setContent(content); -// return response403; -// } + private WebUser getUser(Headers requestHeaders) { + try { + List authorization = requestHeaders.get("Authorization"); + if (Verify.isEmpty(authorization)) { + Log.debug("WebServer: Authorization not Found"); + return null; + } + Log.debug("WebServer: Found Authorization"); + String auth = authorization.get(0); + if (auth.contains("Basic ")) { + auth = auth.split(" ")[1]; + } else { + throw new IllegalArgumentException("Wrong format of Auth"); + } + Base64.Decoder decoder = Base64.getDecoder(); + byte[] decoded = decoder.decode(auth); + String[] userInfo = new String(decoded).split(":"); + if (userInfo.length != 2) { + throw new IllegalArgumentException("User and Password not specified"); + } + String user = userInfo[0]; + String passwordRaw = userInfo[1]; - private Response getResponse(String target) { + SecurityTable securityTable = plugin.getDB().getSecurityTable(); + if (!securityTable.userExists(user)) { + throw new IllegalArgumentException("User Doesn't exist"); + } + + WebUser webUser = securityTable.getSecurityInfo(user); + + boolean correctPass = PassEncryptUtil.verifyPassword(passwordRaw, webUser.getSaltedPassHash()); + if (!correctPass) { + throw new IllegalArgumentException("User and Password do not match"); + } + return webUser; + } catch (IllegalArgumentException e) { + Log.debug("WebServer: " + e.getMessage()); + return null; + } catch (Exception e) { + Log.toLog(this.getClass().getName(), e); + return null; + } + } + + private boolean startHttpsServer() throws IOException { + String keyStorePath = Settings.WEBSERVER_CERTIFICATE_PATH.toString(); + if (!keyStorePath.contains(":")) { + keyStorePath = plugin.getDataFolder() + keyStorePath; + } + char[] storepass = Settings.WEBSERVER_CERTIFICATE_STOREPASS.toString().toCharArray(); + char[] keypass = Settings.WEBSERVER_CERTIFICATE_KEYPASS.toString().toCharArray(); + String alias = Settings.WEBSERVER_CERTIFICATE_ALIAS.toString(); + + boolean startSuccessful = false; + try (FileInputStream fIn = new FileInputStream(keyStorePath)) { + KeyStore keystore = KeyStore.getInstance("JKS"); + + keystore.load(fIn, storepass); + Certificate cert = keystore.getCertificate(alias); + + Log.info("Found Certificate: " + cert.getType()); + + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); + keyManagerFactory.init(keystore, keypass); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); + trustManagerFactory.init(keystore); + + server = HttpsServer.create(new InetSocketAddress(port), 10); + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(keyManagerFactory.getKeyManagers(), null/*trustManagerFactory.getTrustManagers()*/, null); + + ((HttpsServer) server).setHttpsConfigurator(new HttpsConfigurator(sslContext) { + @Override + public void configure(HttpsParameters params) { + SSLEngine engine = sslContext.createSSLEngine(); + + params.setNeedClientAuth(false); + params.setCipherSuites(engine.getEnabledCipherSuites()); + params.setProtocols(engine.getEnabledProtocols()); + + SSLParameters defaultSSLParameters = sslContext.getDefaultSSLParameters(); + params.setSSLParameters(defaultSSLParameters); + } + }); + startSuccessful = true; + } catch (KeyManagementException | NoSuchAlgorithmException e) { + Log.error("WebServer: SSL Context Initialization Failed."); + Log.toLog(this.getClass().getName(), e); + } catch (FileNotFoundException e) { + Log.error("!--------!---------!---------!"); + Log.error("WebServer: SSL Certificate KeyStore File not Found: " + keyStorePath); + Log.error("!--------!---------!---------!"); + Log.info("No Certificate -> Using Http server for Visualization."); + Log.infoColor(ChatColor.YELLOW + "User Authorization Disabled! (Not possible over http)"); + } catch (KeyStoreException | CertificateException | UnrecoverableKeyException e) { + Log.error("WebServer: SSL Certificate loading Failed."); + Log.toLog(this.getClass().getName(), e); + } + return startSuccessful; + } + + private Response getResponse(String target, WebUser user) { + if (usingHttps) { + if (user == null) { + return new PromptAuthorizationResponse(); + } + + if (!isAuthorized(target, user)) { + return forbiddenResponse(); + } + } String[] args = target.split("/"); if (args.length < 2) { - return responseNotFound(null); + return rootPageResponse(user); } + String page = args[1]; switch (page) { case "favicon.ico": - return new RedirectResponse(null, "https://puu.sh/tK0KL/6aa2ba141b.ico"); + return new RedirectResponse("https://puu.sh/tK0KL/6aa2ba141b.ico"); case "players": - return new PlayersPageResponse(null, plugin); + return new PlayersPageResponse(plugin); case "player": - return playerResponse(args, null); + return playerResponse(args); case "server": - return serverResponse(null); + return serverResponse(); default: - return responseNotFound(null); + return notFoundResponse(); } } - private Response serverResponse(OutputStream output) { + private ForbiddenResponse forbiddenResponse() { + ForbiddenResponse response403 = new ForbiddenResponse(); + String content = "

403 Forbidden - Access Denied

" + + "

Unauthorized User.
" + + "Make sure your user has the correct access level.
" + + "You can use /plan web check to check the permission level.

"; + response403.setContent(content); + return response403; + } + + private Response rootPageResponse(WebUser user) { + if (user == null) { + return notFoundResponse(); + } + switch (user.getPermLevel()) { + case 0: + return serverResponse(); + case 1: + return new PlayersPageResponse(plugin); + case 2: + return playerResponse(new String[]{"", user.getName()}); + default: + return forbiddenResponse(); + } + } + + private Response serverResponse() { if (!dataReqHandler.checkIfAnalysisIsCached()) { - return new NotFoundResponse(output, "Analysis data was not cached."); + return new NotFoundResponse("Analysis Data was not cached.
Use /plan analyze to cache the Data."); } - return new AnalysisPageResponse(output, dataReqHandler); + return new AnalysisPageResponse(dataReqHandler); } - private Response playerResponse(String[] args, OutputStream output) { + private Response playerResponse(String[] args) { if (args.length < 3) { - return new NotFoundResponse(output); + return new NotFoundResponse(); } String playerName = args[2].trim(); UUID uuid = UUIDUtility.getUUIDOf(playerName); if (uuid == null) { - return new NotFoundResponse(output, "Player has no UUID"); + return new NotFoundResponse("Player has no UUID"); } if (!dataReqHandler.checkIfCached(uuid)) { - return new NotFoundResponse(output, "Player's data was not cached."); + return new NotFoundResponse("Player's data was not cached.
Use /plan inspect " + playerName + " to cache the Data."); } - return new InspectPageResponse(output, dataReqHandler, uuid); + return new InspectPageResponse(dataReqHandler, uuid); } - private Response responseNotFound(OutputStream output) { - NotFoundResponse response404 = new NotFoundResponse(output); + private Response notFoundResponse() { + NotFoundResponse response404 = new NotFoundResponse(); String content = "

404 Not Found

" + "

Make sure you're accessing a link given by a command, Examples:

" - + "

" + HtmlUtils.getInspectUrl("") + " or
" - + HtmlUtils.getServerAnalysisUrl() + "

"; + + "

" + getProtocol() + HtmlUtils.getInspectUrl("") + " or
" + + getProtocol() + HtmlUtils.getServerAnalysisUrl() + "

"; response404.setContent(content); return response404; } @@ -232,16 +357,55 @@ public class WebServer { */ public void stop() { Log.info(Phrase.WEBSERVER_CLOSE.toString()); - shutdown = true; if (server != null) { server.stop(0); } } /** + * Used to get the handler for Html content requests. + * * @return DataRequestHandler used by the WebServer. */ public DataRequestHandler getDataReqHandler() { return dataReqHandler; } + + private boolean isAuthorized(String target, WebUser user) { + int permLevel = user.getPermLevel(); // Lower number has higher clearance. + int required = getRequiredPermLevel(target, user.getName()); + return permLevel <= required; + } + + private int getRequiredPermLevel(String target, String user) { + String[] t = target.split("/"); + if (t.length < 3) { + return 0; + } + final String wantedUser = t[2].toLowerCase().trim(); + final String theUser = user.trim().toLowerCase(); + if (t[1].equals("players")) { + return 1; + } + if (t[1].equals("player")) { + if (wantedUser.equals(theUser)) { + return 2; + } else { + return 1; + } + } + return 0; + } + + public String getProtocol() { + return usingHttps ? "https" : "http"; + } + + public boolean usingHttps() { + return usingHttps; + } + + public boolean isAuthRequired() { + return usingHttps; + } } diff --git a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/AnalysisPageResponse.java b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/AnalysisPageResponse.java index 5e31e55f1..54dd1e146 100644 --- a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/AnalysisPageResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/AnalysisPageResponse.java @@ -2,16 +2,13 @@ package main.java.com.djrapitops.plan.ui.webserver.response; import main.java.com.djrapitops.plan.ui.html.DataRequestHandler; -import java.io.OutputStream; - /** * @author Rsl1122 * @since 3.5.2 */ public class AnalysisPageResponse extends Response { - public AnalysisPageResponse(OutputStream output, DataRequestHandler h) { - super(output); + public AnalysisPageResponse(DataRequestHandler h) { super.setHeader("HTTP/1.1 200 OK"); super.setContent(h.getAnalysisHtml()); } diff --git a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/ForbiddenResponse.java b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/ForbiddenResponse.java index 188030297..001870539 100644 --- a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/ForbiddenResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/ForbiddenResponse.java @@ -1,15 +1,12 @@ package main.java.com.djrapitops.plan.ui.webserver.response; -import java.io.OutputStream; - /** * @author Rsl1122 * @since 3.5.2 */ public class ForbiddenResponse extends Response { - public ForbiddenResponse(OutputStream output) { - super(output); + public ForbiddenResponse() { super.setHeader("HTTP/1.1 403 Forbidden"); } } diff --git a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/InspectPageResponse.java b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/InspectPageResponse.java index b2052d5c4..ea5a94580 100644 --- a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/InspectPageResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/InspectPageResponse.java @@ -2,7 +2,6 @@ package main.java.com.djrapitops.plan.ui.webserver.response; import main.java.com.djrapitops.plan.ui.html.DataRequestHandler; -import java.io.OutputStream; import java.util.UUID; /** @@ -11,8 +10,7 @@ import java.util.UUID; */ public class InspectPageResponse extends Response { - public InspectPageResponse(OutputStream output, DataRequestHandler h, UUID uuid) { - super(output); + public InspectPageResponse(DataRequestHandler h, UUID uuid) { super.setHeader("HTTP/1.1 200 OK"); super.setContent(h.getInspectHtml(uuid)); } diff --git a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/InternalErrorResponse.java b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/InternalErrorResponse.java index 404386ad0..6585692d8 100644 --- a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/InternalErrorResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/InternalErrorResponse.java @@ -2,16 +2,13 @@ package main.java.com.djrapitops.plan.ui.webserver.response; import main.java.com.djrapitops.plan.ui.html.Html; -import java.io.OutputStream; - /** * @author Rsl1122 * @since 3.5.2 */ public class InternalErrorResponse extends Response { - public InternalErrorResponse(OutputStream output, Throwable e, String cause) { - super(output); + public InternalErrorResponse(Throwable e, String cause) { super.setHeader("HTTP/1.1 500 Internal Error"); StringBuilder content = new StringBuilder(); content.append("

500 Internal Error occurred

"); diff --git a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/JavaScriptResponse.java b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/JavaScriptResponse.java index d5145e901..acb972ecc 100644 --- a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/JavaScriptResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/JavaScriptResponse.java @@ -4,7 +4,6 @@ import main.java.com.djrapitops.plan.Log; import main.java.com.djrapitops.plan.utilities.HtmlUtils; import java.io.FileNotFoundException; -import java.io.OutputStream; /** * @author Rsl1122 @@ -12,8 +11,7 @@ import java.io.OutputStream; */ public class JavaScriptResponse extends Response { - public JavaScriptResponse(OutputStream output, String resource) { - super(output); + public JavaScriptResponse(String resource) { super.setHeader("HTTP/1.1 200 OK"); try { super.setContent(HtmlUtils.getStringFromResource(resource)); diff --git a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/NotFoundResponse.java b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/NotFoundResponse.java index 6ada815e1..aef95ec37 100644 --- a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/NotFoundResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/NotFoundResponse.java @@ -1,21 +1,17 @@ package main.java.com.djrapitops.plan.ui.webserver.response; -import java.io.OutputStream; - /** * @author Rsl1122 * @since 3.5.2 */ public class NotFoundResponse extends Response { - public NotFoundResponse(OutputStream output) { - super(output); + public NotFoundResponse() { super.setHeader("HTTP/1.1 404 Not Found"); super.setContent("

404 Not Found

Page does not exist.

"); } - public NotFoundResponse(OutputStream output, String msg) { - super(output); + public NotFoundResponse(String msg) { super.setHeader("HTTP/1.1 404 Not Found"); super.setContent("

404 Not Found

" + msg + "

"); } diff --git a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/PlayersPageResponse.java b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/PlayersPageResponse.java index dff5d5cbd..6dcb1920e 100644 --- a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/PlayersPageResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/PlayersPageResponse.java @@ -6,7 +6,6 @@ import main.java.com.djrapitops.plan.ui.html.Html; import main.java.com.djrapitops.plan.utilities.HtmlUtils; import main.java.com.djrapitops.plan.utilities.comparators.UserDataNameComparator; -import java.io.OutputStream; import java.util.List; /** @@ -15,8 +14,7 @@ import java.util.List; */ public class PlayersPageResponse extends Response { - public PlayersPageResponse(OutputStream output, Plan plugin) { - super(output); + public PlayersPageResponse(Plan plugin) { super.setHeader("HTTP/1.1 200 OK"); super.setContent(buildContent(plugin.getInspectCache().getCachedUserData())); } diff --git a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/PromptAuthorizationResponse.java b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/PromptAuthorizationResponse.java index ec52159c7..293334866 100644 --- a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/PromptAuthorizationResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/PromptAuthorizationResponse.java @@ -1,17 +1,14 @@ package main.java.com.djrapitops.plan.ui.webserver.response; -import java.io.OutputStream; - /** * @author Rsl1122 * @since 3.5.2 */ public class PromptAuthorizationResponse extends Response { - public PromptAuthorizationResponse(OutputStream output) { - super(output); + public PromptAuthorizationResponse() { super.setHeader("HTTP/1.1 401 Access Denied\r\n" - + "WWW-Authenticate: Basic realm=\"Analysis\";"); + + "WWW-Authenticate: Basic realm=\"/\";"); super.setContent("

401 Unauthorized

Authentication Failed.
" + "- Ensure you have registered a user with /plan register
" + "- Check that the username and password are correct
" diff --git a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/RedirectResponse.java b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/RedirectResponse.java index 9ae5e93ea..4541f3ff1 100644 --- a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/RedirectResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/RedirectResponse.java @@ -1,15 +1,12 @@ package main.java.com.djrapitops.plan.ui.webserver.response; -import java.io.OutputStream; - /** * @author Rsl1122 * @since 3.5.2 */ public class RedirectResponse extends Response { - public RedirectResponse(OutputStream output, String direct) { - super(output); + public RedirectResponse(String direct) { super.setHeader("HTTP/1.1 302 Found"); super.setContent("Location: " + direct); } diff --git a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/Response.java b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/Response.java index 47b02e5bf..01da7e272 100644 --- a/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/Response.java +++ b/Plan/src/main/java/com/djrapitops/plan/ui/webserver/response/Response.java @@ -1,39 +1,18 @@ package main.java.com.djrapitops.plan.ui.webserver.response; -import java.io.IOException; -import java.io.OutputStream; - /** * @author Rsl1122 * @since 3.5.2 */ public abstract class Response { - - private final OutputStream output; - private String header; private String content; /** * Class Constructor. - * - * @param output Website OutputStream to write the response to. */ - public Response(OutputStream output) { - this.output = output; - } - - /** - * Writes the HTML to the OutputStream according to the requested page. - * - * @throws IOException - */ - public void sendStaticResource() throws IOException { - String response = getResponse(); -// Log.debug("Response: " + response); // Responses should not be logged, html content large. - output.write(response.getBytes()); - output.flush(); + public Response() { } public String getResponse() { diff --git a/Plan/src/main/java/com/djrapitops/plan/utilities/HtmlUtils.java b/Plan/src/main/java/com/djrapitops/plan/utilities/HtmlUtils.java index 5113bcef7..ed16a57d7 100644 --- a/Plan/src/main/java/com/djrapitops/plan/utilities/HtmlUtils.java +++ b/Plan/src/main/java/com/djrapitops/plan/utilities/HtmlUtils.java @@ -73,28 +73,43 @@ public class HtmlUtils { * @return */ public static String getServerAnalysisUrlWithProtocol() { - return Settings.LINK_PROTOCOL.toString() + ":" + getServerAnalysisUrl(); + return getProtocol() + ":" + getServerAnalysisUrl(); } /** * @return */ public static String getServerAnalysisUrl() { - int port = Settings.WEBSERVER_PORT.getNumber(); - String ip = Plan.getInstance().getVariable().getIp() + ":" + port; - boolean useAlternativeIP = Settings.SHOW_ALTERNATIVE_IP.isTrue(); - if (useAlternativeIP) { - ip = Settings.ALTERNATIVE_IP.toString().replace("%port%", String.valueOf(port)); - } + String ip = getIP(); return "//" + ip + "/server"; } + /** + * Used to get the WebServer's IP with Port. + * + * @return For example 127.0.0.1:8804 + */ + public static String getIP() { + int port = Settings.WEBSERVER_PORT.getNumber(); + String ip; + if (Settings.SHOW_ALTERNATIVE_IP.isTrue()) { + ip = Settings.ALTERNATIVE_IP.toString().replace("%port%", String.valueOf(port)); + } else { + ip = Plan.getInstance().getVariable().getIp() + ":" + port; + } + return ip; + } + + private static String getProtocol() { + return Settings.EXTERNAL_WEBSERVER.isTrue() ? Settings.LINK_PROTOCOL.toString() : Plan.getInstance().getUiServer().getProtocol(); + } + /** * @param playerName * @return */ public static String getInspectUrlWithProtocol(String playerName) { - return Settings.LINK_PROTOCOL.toString() + ":" + getInspectUrl(playerName); + return getProtocol() + ":" + getInspectUrl(playerName); } /** @@ -102,12 +117,7 @@ public class HtmlUtils { * @return */ public static String getInspectUrl(String playerName) { - int port = Settings.WEBSERVER_PORT.getNumber(); - String ip = Plan.getInstance().getVariable().getIp() + ":" + port; - boolean useAlternativeIP = Settings.SHOW_ALTERNATIVE_IP.isTrue(); - if (useAlternativeIP) { - ip = Settings.ALTERNATIVE_IP.toString().replace("%port%", String.valueOf(port)); - } + String ip = getIP(); return "//" + ip + "/player/" + playerName; } diff --git a/Plan/src/main/resources/config.yml b/Plan/src/main/resources/config.yml index 7b039dea9..330c15ad9 100644 --- a/Plan/src/main/resources/config.yml +++ b/Plan/src/main/resources/config.yml @@ -34,7 +34,8 @@ Settings: InternalIP: 0.0.0.0 ShowAlternativeServerIP: false AlternativeIP: your.ip.here:%port% - LinkProtocol: http + UsingExternalWebServer: false + ExternalWebServerLinkProtocol: http Security: DisplayIPsAndUUIDs: true Certificate: