Start of HttpsServer. #175

This commit is contained in:
Rsl1122 2017-07-28 21:53:24 +03:00
parent 1a71ef12a3
commit 24a360d8f2
7 changed files with 253 additions and 70 deletions

View File

@ -180,6 +180,9 @@ public class Plan extends BukkitPlugin<Plan> {
if (webserverIsEnabled) {
uiServer = new WebSocketServer(this);
uiServer.initServer();
if (!uiServer.isEnabled()) {
Log.error("WebServer was not Initialized.");
}
// Prevent passwords showing up on console.
Bukkit.getLogger().setFilter(new RegisterCommandFilter());
} else if (!hasDataViewCapability) {

View File

@ -49,6 +49,10 @@ public enum Settings {
LOCALE("Settings.Locale"),
WEBSERVER_IP("Settings.WebServer.InternalIP"),
ANALYSIS_EXPORT_PATH("Settings.Analysis.Export.DestinationFolder"),
WEBSERVER_CERTIFICATE_PATH("Settings.WebServer.Security.Certificate.KeyStorePath"),
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"),
//
SERVER_NAME("Customization.ServerName"),

View File

@ -0,0 +1,67 @@
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 IllegalArgumentException, PassEncryptUtil.CannotPerformOperationException, PassEncryptUtil.InvalidHashException, SQLException {
SecurityTable securityTable = plugin.getDB().getSecurityTable();
if (!securityTable.userExists(user)) {
throw new IllegalArgumentException("User Doesn't exist");
}
WebUser securityInfo = securityTable.getSecurityInfo(user);
boolean correctPass = PassEncryptUtil.verifyPassword(passwordRaw, securityInfo.getSaltedPassHash());
if (!correctPass) {
throw new IllegalArgumentException("User and Password do not match");
}
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;
}
}

View File

@ -1,7 +1,7 @@
package main.java.com.djrapitops.plan.ui.webserver;
import com.djrapitops.plugin.task.AbsRunnable;
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;
@ -10,18 +10,21 @@ 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.Benchmark;
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 javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.sql.SQLException;
import java.util.Base64;
import java.util.UUID;
@ -32,13 +35,17 @@ import java.util.UUID;
public class WebSocketServer {
private final int PORT;
private final Plan plugin;
private final DataRequestHandler dataReqHandler;
private boolean enabled = false;
private Socket sslServer;
private ServerSocket server;
private HttpServer server;
private boolean shutdown;
private KeyManagerFactory keyManagerFactory;
private TrustManagerFactory trustManagerFactory;
/**
* Class Constructor.
* <p>
@ -64,43 +71,118 @@ public class WebSocketServer {
Log.info(Phrase.WEBSERVER_INIT.toString());
try {
InetAddress ip = InetAddress.getByName(Settings.WEBSERVER_IP.toString());
// SSLServerSocketFactory ssl = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
// server = ssl.createServerSocket(PORT, 1, ip);
server = new ServerSocket(PORT, 1, ip);
plugin.getRunnableFactory().createNew(new AbsRunnable("WebServerTask") {
@Override
public void run() {
while (!shutdown) {
/*SSL*/
Socket socket = null;
InputStream input = null;
OutputStream output = null;
Request request = null;
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 {
socket = /*(SSLSocket)*/ server.accept();
Log.debug("New Socket Connection: " + socket.getInetAddress());
input = socket.getInputStream();
output = socket.getOutputStream();
request = new Request(input);
Benchmark.start("Webserver Response");
request.parse();
Response response = getResponse(request, output);
Log.debug("Parsed response: " + response.getClass().getSimpleName());
response.sendStaticResource();
} catch (IOException | IllegalArgumentException e) {
} finally {
MiscUtils.close(input, request, output, socket);
Benchmark.stop("Webserver Response");
FileInputStream fIn = new FileInputStream(keyStorePath);
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(fIn, storepass);
Certificate cert = keystore.getCertificate(alias);
Log.info("Found Certificate: " + cert);
keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keystore, keypass);
trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init(keystore);
server = HttpsServer.create(new InetSocketAddress(PORT), 0);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
((HttpsServer) server).setHttpsConfigurator(new HttpsConfigurator(sslContext) {
public void configure(HttpsParameters params) {
try {
SSLContext c = SSLContext.getDefault();
SSLEngine engine = c.createSSLEngine();
params.setNeedClientAuth(false);
params.setCipherSuites(engine.getEnabledCipherSuites());
params.setProtocols(engine.getEnabledProtocols());
SSLParameters defaultSSLParameters = c.getDefaultSSLParameters();
params.setSSLParameters(defaultSSLParameters);
} catch (NoSuchAlgorithmException e) {
Log.error("WebServer: SSL Engine loading Failed.");
Log.toLog(this.getClass().getName(), e);
}
}
this.cancel();
});
startSuccessful = true;
} catch (KeyManagementException e) {
Log.error("WebServer: SSL Context Initialization Failed.");
Log.toLog(this.getClass().getName(), e);
} catch (NoSuchAlgorithmException e) {
Log.error("WebServer: SSL Context Initialization Failed.");
Log.toLog(this.getClass().getName(), e);
} catch (FileNotFoundException e) {
Log.error("WebServer: SSL Certificate KeyStore File not Found: " + keyStorePath);
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException e) {
Log.error("WebServer: SSL Certificate loading Failed.");
Log.toLog(this.getClass().getName(), e);
}
}).runTaskAsynchronously();
if (!startSuccessful) {
return; // TODO Http Server
}
HttpContext analysisPage = server.createContext("/server", serverResponse(null));
HttpContext playersPage = server.createContext("/players", new PlayersPageResponse(null, plugin));
HttpContext inspectPage = server.createContext("/player", new InspectPageResponse(null, dataReqHandler, UUID.randomUUID())); // TODO
if (startSuccessful) {
for (HttpContext c : new HttpContext[]{analysisPage, playersPage, inspectPage}) {
c.setAuthenticator(new Authenticator(plugin, c.getPath()));
}
}
server.start();
// server = new ServerSocket(PORT, 1, ip);
//
// plugin.getRunnableFactory().createNew(new AbsRunnable("WebServerTask") {
// @Override
// public void run() {
// while (!shutdown) {
// /*SSL*/
// Socket socket = null;
// InputStream input = null;
// OutputStream output = null;
// Request request = null;
// try {
// socket = /*(SSLSocket)*/ server.accept();
// Log.debug("New Socket Connection: " + socket.getInetAddress());
// input = socket.getInputStream();
// output = socket.getOutputStream();
// request = new Request(input);
// Benchmark.start("Webserver Response");
// request.parse();
// Response response = getResponse(request, output);
// Log.debug("Parsed response: " + response.getClass().getSimpleName());
// response.sendStaticResource();
// } catch (IOException | IllegalArgumentException e) {
// } finally {
// MiscUtils.close(input, request, output, socket);
// Benchmark.stop("Webserver Response");
// }
// }
// this.cancel();
// }
// }).runTaskAsynchronously();
enabled = true;
Log.info(Phrase.WEBSERVER_RUNNING.parse(server.getLocalPort() + ""));
Log.info(Phrase.WEBSERVER_RUNNING.parse(server.getAddress().getPort() + ""));
} catch (IllegalArgumentException | IllegalStateException | IOException e) {
Log.toLog(this.getClass().getName(), e);
enabled = false;
@ -117,22 +199,22 @@ public class WebSocketServer {
return new RedirectResponse(output, "https://puu.sh/tK0KL/6aa2ba141b.ico");
}
// if (!request.hasAuthorization()) {
// return new PromptAuthorizationResponse(output);
// }
// try {
// if (!isAuthorized(request)) {
// ForbiddenResponse response403 = new ForbiddenResponse(output);
// String content = "<h1>403 Forbidden - Access Denied</h1>"
// + "<p>Unauthorized User.<br>"
// + "Make sure your user has the correct access level.<br>"
// + "You can use /plan web check <username> to check the permission level.</p>";
// response403.setContent(content);
// return response403;
// }
// } catch (IllegalArgumentException e) {
// return new PromptAuthorizationResponse(output);
// }
if (!request.hasAuthorization()) {
return new PromptAuthorizationResponse(output);
}
try {
if (!isAuthorized(request)) {
ForbiddenResponse response403 = new ForbiddenResponse(output);
String content = "<h1>403 Forbidden - Access Denied</h1>"
+ "<p>Unauthorized User.<br>"
+ "Make sure your user has the correct access level.<br>"
+ "You can use /plan web check <username> to check the permission level.</p>";
response403.setContent(content);
return response403;
}
} catch (IllegalArgumentException e) {
return new PromptAuthorizationResponse(output);
}
String req = request.getRequest();
String target = request.getTarget();
if (!req.equals("GET") || target.equals("/")) {
@ -204,12 +286,8 @@ public class WebSocketServer {
public void stop() {
Log.info(Phrase.WEBSERVER_CLOSE.toString());
shutdown = true;
try {
if (server != null) {
server.close();
}
} catch (IOException e) {
Log.toLog(this.getClass().getName(), e);
server.stop(0);
}
}
@ -229,23 +307,28 @@ public class WebSocketServer {
throw new IllegalArgumentException("User and Password not specified");
}
String user = userInfo[0];
String passwordRaw = userInfo[1];
return isAuthorized(user, passwordRaw, request.getTarget());
}
private boolean isAuthorized(String user, String passwordRaw, String target) throws IllegalArgumentException, PassEncryptUtil.CannotPerformOperationException, PassEncryptUtil.InvalidHashException, SQLException {
SecurityTable securityTable = plugin.getDB().getSecurityTable();
if (!securityTable.userExists(user)) {
throw new IllegalArgumentException("User Doesn't exist");
}
WebUser securityInfo = securityTable.getSecurityInfo(user);
String passwordRaw = userInfo[1];
boolean correctPass = PassEncryptUtil.verifyPassword(passwordRaw, securityInfo.getSaltedPassHash());
if (!correctPass) {
throw new IllegalArgumentException("User and Password do not match");
}
int permLevel = securityInfo.getPermLevel(); // Lower number has higher clearance.
int required = getRequiredPermLevel(request, securityInfo.getName());
int required = getRequiredPermLevel(target, securityInfo.getName());
return permLevel <= required;
}
private int getRequiredPermLevel(Request request, String user) {
String target = request.getTarget();
private int getRequiredPermLevel(String target, String user) {
String[] t = target.split("/");
if (t.length < 3) {
return 0;

View File

@ -1,5 +1,8 @@
package main.java.com.djrapitops.plan.ui.webserver.response;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
import java.io.OutputStream;
@ -7,7 +10,8 @@ import java.io.OutputStream;
* @author Rsl1122
* @since 3.5.2
*/
public abstract class Response {
public abstract class Response implements HttpHandler {
private final OutputStream output;
@ -29,14 +33,18 @@ public abstract class Response {
* @throws IOException
*/
public void sendStaticResource() throws IOException {
String response = header + "\r\n"
String response = getResponse();
// Log.debug("Response: " + response); // Responses should not be logged, html content large.
output.write(response.getBytes());
output.flush();
}
public String getResponse() {
return header + "\r\n"
+ "Content-Type: text/html;\r\n"
+ "Content-Length: " + content.length() + "\r\n"
+ "\r\n"
+ content;
// Log.debug("Response: " + response); // Responses should not be logged, html content large.
output.write(response.getBytes());
output.flush();
}
public void setHeader(String header) {
@ -46,4 +54,21 @@ public abstract class Response {
public void setContent(String content) {
this.content = content;
}
@Override
public void handle(HttpExchange exchange) throws IOException {
exchange.sendResponseHeaders(getCode(), content.length());
OutputStream os = exchange.getResponseBody();
os.write(content.getBytes());
os.close();
}
private int getCode() {
if (header == null) {
return 500;
} else {
return Integer.parseInt(header.split(" ")[1]);
}
}
}

View File

@ -1776,8 +1776,6 @@
};
Plotly.plot(CLOROPLETH, data, layout, {showLink: false});
</script>
<script>
</script>
<script>
// Navigation & Refresh time clock
var serverTime = new Date(%currenttime%);
@ -1863,8 +1861,6 @@
document.getElementById('divTime').innerHTML = out;
setTimeout('countUpTimer()', 1000);
}
</script>
</div>
</body>

View File

@ -37,6 +37,11 @@ Settings:
LinkProtocol: http
Security:
DisplayIPsAndUUIDs: true
Certificate:
KeyStorePath: '\SSLCertificate.keystore'
KeyPass: 'default'
StorePass: 'default'
Alias: 'alias'
Customization:
ServerName: 'Plan'