DB Implementation replacement for ConfigurationWebAPI. Removed WebAPI.

This commit is contained in:
Rsl1122 2018-02-07 14:20:49 +02:00
parent b7ef9f62f3
commit ef6ffdb980
13 changed files with 267 additions and 283 deletions

View File

@ -86,4 +86,6 @@ public interface FetchOperations {
Map<UUID, Map<UUID, List<Session>>> getSessionsInLastMonth() throws DBException;
List<Server> getServers() throws DBException;
List<UUID> getServerUUIDs() throws DBException;
}

View File

@ -29,6 +29,8 @@ public interface TransferOperations {
void storePlayerPluginsTab(UUID player, String encodedHtml) throws DBException;
void storeConfigSettings(String encodedSettingString) throws DBException;
// Get
Map<UUID, String> getEncodedPlayerHtml() throws DBException;
@ -40,4 +42,6 @@ public interface TransferOperations {
Optional<UUID> getServerPlayerIsOnlineOn(UUID playerUUID) throws DBException;
Map<UUID, String> getEncodedPlayerPluginsTabs(UUID playerUUID) throws DBException;
Optional<String> getEncodedConfigSettings() throws DBException;
}

View File

@ -104,4 +104,22 @@ public class SQLTransferOps extends SQLOps implements TransferOperations {
throw SQLErrorUtil.getExceptionFor(e);
}
}
@Override
public void storeConfigSettings(String encodedSettingString) throws DBException {
try {
transferTable.storeConfigSettings(encodedSettingString);
} catch (SQLException e) {
throw SQLErrorUtil.getExceptionFor(e);
}
}
@Override
public Optional<String> getEncodedConfigSettings() throws DBException {
try {
return transferTable.getConfigSettings();
} catch (SQLException e) {
throw SQLErrorUtil.getExceptionFor(e);
}
}
}

View File

@ -60,7 +60,8 @@ public class TransferTable extends Table {
selectStatement = "SELECT * FROM " + tableName +
" WHERE " + columnInfoType + "= ?" +
" AND " + columnExpiry + "> ?";
" AND " + columnExpiry + "> ?" +
" ORDER BY " + columnExpiry + " DESC";
}
@Override
@ -260,4 +261,35 @@ public class TransferTable extends Table {
}
});
}
public void storeConfigSettings(String encodedSettingString) throws SQLException {
execute(new ExecStatement(insertStatement) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, ServerInfo.getServerUUID().toString());
statement.setLong(2, MiscUtils.getTime() + TimeAmount.HOUR.ms());
statement.setString(3, "configSettings");
statement.setString(4, null);
statement.setString(5, encodedSettingString);
}
});
}
public Optional<String> getConfigSettings() throws SQLException {
return query(new QueryStatement<Optional<String>>(selectStatement, 100) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, "configSettings");
statement.setLong(2, MiscUtils.getTime());
}
@Override
public Optional<String> processResults(ResultSet set) throws SQLException {
if (set.next()) {
return Optional.ofNullable(set.getString(columnContent));
}
return Optional.empty();
}
});
}
}

View File

@ -84,7 +84,7 @@ public class ConnectionOut {
// This allows connecting to connections with invalid certificate
// Drawback: MitM attack possible between connections to servers that are not local.
// Scope: WebAPI transmissions
// Scope: InfoRequest transmissions
// Risk: Attacker sets up a server between Bungee and Bukkit WebServers
// - Negotiates SSL Handshake with both servers
// - Receives the SSL encrypted data, but decrypts it in the MitM server.
@ -93,7 +93,7 @@ public class ConnectionOut {
// Mitigating factors:
// - If Server owner has access to all routing done on the domain (IP/Address)
// - If Direct IPs are used to transfer between servers
// Alternative solution: WebAPI run only on HTTP, HTTP can be read during transmission,
// Alternative solution: InfoRequests run only on HTTP, HTTP can be read during transmission,
// would require running two WebServers when HTTPS is used.
httpsConn.setSSLSocketFactory(getRelaxedSocketFactory());
}

View File

@ -18,7 +18,7 @@ import java.util.Optional;
import java.util.UUID;
/**
* Manages the Server information required for Bungee-Bukkit WebAPI connection.
* Manages the Server UUID for Bukkit servers.
* <p>
* Also manages Server ID required for MySQL database independence.
*

View File

@ -4,7 +4,7 @@
*/
package com.djrapitops.plan.system.settings;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.system.settings.config.ConfigSystem;
import com.djrapitops.plugin.api.config.Config;
import com.djrapitops.plugin.api.utility.log.Log;
@ -24,7 +24,7 @@ import java.util.UUID;
*/
public class ServerSpecificSettings {
public static void updateSettings(Plan plugin, Map<String, String> settings) {
public static void updateSettings(Map<String, String> settings) {
Log.debug("Checking new settings..");
Config config = ConfigSystem.getConfig();
@ -52,12 +52,12 @@ public class ServerSpecificSettings {
try {
config.save();
} catch (IOException e) {
Log.toLog("ServerSpecificSettings / ConfigSave", e);
Log.toLog(ServerSpecificSettings.class, e);
}
Log.info("----------------------------------");
Log.info("The Received Bungee Settings changed the config values, restarting Plan..");
Log.info("----------------------------------");
plugin.reloadPlugin(true);
PlanPlugin.getInstance().reloadPlugin(true);
} else {
Log.debug("Settings up to date");
}
@ -73,6 +73,7 @@ public class ServerSpecificSettings {
} else if ("false".equalsIgnoreCase(value)) {
return false;
}
// Value is a string
return value;
}

View File

@ -4,7 +4,9 @@
*/
package com.djrapitops.plan.system.settings.config;
import com.djrapitops.plan.api.exceptions.EnableException;
import com.djrapitops.plan.system.file.FileSystem;
import com.djrapitops.plan.system.settings.network.NetworkSettings;
import java.io.IOException;
@ -21,4 +23,10 @@ public class BukkitConfigSystem extends ConfigSystem {
protected void copyDefaults() throws IOException {
config.copyDefaults(FileSystem.readFromResource("config.yml"));
}
@Override
public void enable() throws EnableException {
super.enable();
NetworkSettings.loadSettingsFromDB();
}
}

View File

@ -4,7 +4,9 @@
*/
package com.djrapitops.plan.system.settings.config;
import com.djrapitops.plan.api.exceptions.EnableException;
import com.djrapitops.plan.system.file.FileSystem;
import com.djrapitops.plan.system.settings.network.NetworkSettings;
import java.io.IOException;
@ -21,4 +23,10 @@ public class BungeeConfigSystem extends ConfigSystem {
protected void copyDefaults() throws IOException {
config.copyDefaults(FileSystem.readFromResource("bungeeconfig.yml"));
}
@Override
public void enable() throws EnableException {
super.enable();
NetworkSettings.placeSettingsToDB();
}
}

View File

@ -0,0 +1,185 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package com.djrapitops.plan.system.settings.network;
import com.djrapitops.plan.api.exceptions.connection.UnsupportedTransferDatabaseException;
import com.djrapitops.plan.api.exceptions.database.DBException;
import com.djrapitops.plan.system.database.databases.Database;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.processing.Processor;
import com.djrapitops.plan.system.settings.ServerSpecificSettings;
import com.djrapitops.plan.system.settings.Settings;
import com.djrapitops.plan.utilities.Base64Util;
import com.djrapitops.plugin.api.Check;
import com.djrapitops.plugin.api.utility.log.Log;
import com.djrapitops.plugin.utilities.Verify;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
/**
* Class for managing Config setting transfer from Bungee to Bukkit servers.
*
* @author Rsl1122
*/
public class NetworkSettings {
private static final String SPLIT = ";;SETTING;;";
private static final String VAL_SPLIT = ";;VALUE;;";
private NetworkSettings() {
/* Hides Constructor */
}
public static void loadSettingsFromDB() {
if (Check.isBungeeAvailable()) {
return;
}
if (Settings.BUNGEE_OVERRIDE_STANDALONE_MODE.isTrue() || Settings.BUNGEE_COPY_CONFIG.isFalse()) {
return;
}
Processor.queue(() -> {
try {
new NetworkSettings().loadFromDatabase();
} catch (DBException | UnsupportedTransferDatabaseException e) {
Log.toLog(NetworkSettings.class, e);
}
});
}
public static void placeSettingsToDB() {
if (!Check.isBungeeAvailable()) {
return;
}
Log.debug("Saving Config settings to database.");
Processor.queue(() -> {
try {
new NetworkSettings().placeToDatabase();
} catch (DBException | UnsupportedTransferDatabaseException e) {
Log.toLog(NetworkSettings.class, e);
}
});
}
private void loadFromDatabase() throws DBException, UnsupportedTransferDatabaseException {
Optional<String> encodedConfigSettings = Database.getActive().transfer().getEncodedConfigSettings();
if (!encodedConfigSettings.isPresent()) {
Log.debug("No Config settings in database.");
return;
}
String configSettings = Base64Util.decode(encodedConfigSettings.get());
Map<String, String> pathValueMap = getPathsAndValues(configSettings);
ServerSpecificSettings.updateSettings(pathValueMap);
}
private Map<String, String> getPathsAndValues(String configSettings) {
Map<String, String> pathValueMap = new HashMap<>();
String[] settings = configSettings.split(SPLIT);
UUID thisServerUUID = ServerInfo.getServerUUID();
for (String settingAndVal : settings) {
String[] split = settingAndVal.split(VAL_SPLIT);
String setting = split[0];
String[] pathSplit = setting.split(":");
String path;
if (pathSplit.length == 2) {
UUID serverUUID = UUID.fromString(pathSplit[0]);
if (!thisServerUUID.equals(serverUUID)) {
continue;
}
path = pathSplit[1];
} else {
path = setting;
}
String value = split[1];
pathValueMap.put(path, value);
}
return pathValueMap;
}
private void placeToDatabase() throws DBException, UnsupportedTransferDatabaseException {
Map<String, Object> configValues = getConfigValues();
StringBuilder transferBuilder = new StringBuilder();
int size = configValues.size();
int i = 0;
for (Map.Entry<String, Object> entry : configValues.entrySet()) {
String path = entry.getKey();
String value = entry.getValue().toString();
transferBuilder.append(path).append(VAL_SPLIT).append(value);
if (i < size - 1) {
transferBuilder.append(SPLIT);
}
i++;
}
String base64 = Base64Util.encode(transferBuilder.toString());
Database.getActive().transfer().storeConfigSettings(base64);
}
private Map<String, Object> getConfigValues() throws DBException {
Map<String, Object> configValues = new HashMap<>();
addConfigValue(configValues, Settings.DB_TYPE, "mysql");
Settings[] sameStrings = new Settings[]{
Settings.DB_HOST, Settings.DB_USER, Settings.DB_PASS,
Settings.DB_DATABASE, Settings.FORMAT_DECIMALS, Settings.FORMAT_SECONDS,
Settings.FORMAT_DAY, Settings.FORMAT_DAYS, Settings.FORMAT_HOURS,
Settings.FORMAT_MINUTES, Settings.FORMAT_MONTHS, Settings.FORMAT_MONTH,
Settings.FORMAT_YEAR, Settings.FORMAT_YEARS, Settings.FORMAT_ZERO_SECONDS
};
for (Settings setting : sameStrings) {
addConfigValue(configValues, setting, setting.toString());
}
addConfigValue(configValues, Settings.DB_PORT, Settings.DB_PORT.getNumber());
addServerSpecificValues(configValues);
return configValues;
}
private void addConfigValue(Map<String, Object> configValues, Settings setting, Object value) {
if (value != null) {
configValues.put(setting.getPath(), value);
}
}
private void addConfigValue(Map<String, Object> configValues, UUID serverUUID, Settings setting, Object value) {
if (value != null) {
configValues.put(serverUUID + ":" + setting.getPath(), value);
}
}
private void addServerSpecificValues(Map<String, Object> configValues) throws DBException {
ServerSpecificSettings settings = Settings.serverSpecific();
for (UUID serverUUID : Database.getActive().fetch().getServerUUIDs()) {
String theme = settings.getString(serverUUID, Settings.THEME_BASE);
Integer port = settings.getInt(serverUUID, Settings.WEBSERVER_PORT);
String name = settings.getString(serverUUID, Settings.SERVER_NAME);
if (!Verify.isEmpty(theme)) {
addConfigValue(configValues, serverUUID, Settings.THEME_BASE, theme);
}
if (port != null && port != 0) {
addConfigValue(configValues, serverUUID, Settings.WEBSERVER_PORT, port);
}
if (!Verify.isEmpty(name)) {
addConfigValue(configValues, serverUUID, Settings.SERVER_NAME, name);
}
}
}
}

View File

@ -1,158 +0,0 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package com.djrapitops.plan.system.webserver.webapi;
import com.djrapitops.plan.api.exceptions.connection.*;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.webserver.response.Response;
import com.djrapitops.plan.system.webserver.response.api.BadRequestResponse;
import com.djrapitops.plan.system.webserver.response.api.SuccessResponse;
import com.djrapitops.plan.system.webserver.response.cache.PageId;
import com.djrapitops.plan.system.webserver.response.cache.ResponseCache;
import com.djrapitops.plugin.api.utility.log.Log;
import com.djrapitops.plugin.utilities.Verify;
import javax.net.ssl.*;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
/**
* @author Rsl1122
*/
@Deprecated
public abstract class WebAPI {
private static TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
//No need to implement.
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
//No need to implement.
}
}
};
private Map<String, String> variables;
public WebAPI() {
this.variables = new HashMap<>();
}
public void sendRequest(String address) throws WebException {
Verify.nullCheck(address);
try {
URL url = new URL(address + "/api/" + this.getClass().getSimpleName().toLowerCase());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (address.startsWith("https")) {
HttpsURLConnection httpsConn = (HttpsURLConnection) connection;
// Disables unsigned certificate & hostname check, because we're trusting the user given certificate.
// This allows https connections internally to local ports.
httpsConn.setHostnameVerifier((hostname, session) -> true);
// This allows connecting to connections with invalid certificate
// Drawback: MitM attack possible between connections to servers that are not local.
// Scope: WebAPI transmissions
// Risk: Attacker sets up a server between Bungee and Bukkit WebServers
// - Negotiates SSL Handshake with both servers
// - Receives the SSL encrypted data, but decrypts it in the MitM server.
// -> Access to valid ServerUUID for POST requests
// -> Access to sending Html to the (Bungee) WebServer
// Mitigating factors:
// - If Server owner has access to all routing done on the domain (IP/Address)
// - If Direct IPs are used to transfer between servers
// Alternative solution: WebAPI run only on HTTP, HTTP can be read during transmission,
// would require running two WebServers when HTTPS is used.
httpsConn.setSSLSocketFactory(getRelaxedSocketFactory());
}
connection.setConnectTimeout(10000);
connection.setDoOutput(true);
connection.setInstanceFollowRedirects(false);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("charset", "UTF-8");
String parameters = parseVariables();
connection.setRequestProperty("Content-Length", Integer.toString(parameters.length()));
byte[] toSend = parameters.getBytes();
connection.setUseCaches(false);
Log.debug("Sending WebAPI Request: " + this.getClass().getSimpleName() + " to " + address);
try (DataOutputStream out = new DataOutputStream(connection.getOutputStream())) {
out.write(toSend);
}
int responseCode = connection.getResponseCode();
Log.debug("Response: " + responseCode);
switch (responseCode) {
case 200:
return;
case 400:
throw new WebFailException("Bad Request: " + url.toString() + "|" + parameters);
case 403:
throw new ForbiddenException(url.toString());
case 404:
throw new NotFoundException("");
case 500:
throw new InternalErrorException();
default:
throw new WebException(url.toString() + "| Wrong response code " + responseCode);
}
} catch (SocketTimeoutException e) {
throw new ConnectionFailException("Connection timed out after 10 seconds.", e);
} catch (NoSuchAlgorithmException | KeyManagementException | IOException e) {
throw new ConnectionFailException("API connection failed. address: " + address, e);
}
}
protected void addVariable(String key, String value) {
variables.put(key, value);
}
private SSLSocketFactory getRelaxedSocketFactory() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
return sc.getSocketFactory();
}
protected Response success() {
return ResponseCache.loadResponse(PageId.TRUE.id(), SuccessResponse::new);
}
protected Response badRequest(String error) {
return ResponseCache.loadResponse(PageId.ERROR.of(error), () -> new BadRequestResponse(error));
}
private String parseVariables() {
StringBuilder parameters = new StringBuilder();
String serverUUID = ServerInfo.getServerUUID().toString();
parameters.append("sender=").append(serverUUID);
for (Map.Entry<String, String> entry : variables.entrySet()) {
parameters.append(";&variable;").append(entry.getKey()).append("=").append(entry.getValue());
}
return parameters.toString();
}
}

View File

@ -1,116 +0,0 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package com.djrapitops.plan.system.webserver.webapi.bukkit;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.api.exceptions.connection.WebException;
import com.djrapitops.plan.system.settings.ServerSpecificSettings;
import com.djrapitops.plan.system.settings.Settings;
import com.djrapitops.plan.system.settings.config.ConfigSystem;
import com.djrapitops.plan.system.webserver.WebServer;
import com.djrapitops.plan.system.webserver.response.Response;
import com.djrapitops.plan.system.webserver.webapi.WebAPI;
import com.djrapitops.plugin.api.Check;
import com.djrapitops.plugin.api.utility.log.Log;
import com.djrapitops.plugin.utilities.Verify;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author Fuzzlemann
*/
@Deprecated
public class ConfigurationWebAPI extends WebAPI {
public Response onRequest(PlanPlugin plugin, Map<String, String> variables) {
if (!Check.isBukkitAvailable()) {
Log.debug("Called a wrong server type");
return badRequest("Called a Bungee Server");
}
if (Settings.BUNGEE_COPY_CONFIG.isFalse() || Settings.BUNGEE_OVERRIDE_STANDALONE_MODE.isTrue()) {
Log.info("Bungee Config settings overridden on this server.");
Log.debug(ConfigSystem.getConfig().getConfigNode("Plugin.Bungee-Override").getChildren().toString());
return success();
}
ServerSpecificSettings.updateSettings((Plan) plugin, variables);
return success();
}
@Override
public void sendRequest(String address) {
throw new IllegalStateException("Wrong method call for this WebAPI, call sendRequest(String, UUID, UUID) instead.");
}
public void sendRequest(String address, UUID serverUUID, String accessKey) throws WebException {
if (accessKey != null) {
addVariable("accessKey", accessKey);
}
addVariable("webAddress", WebServer.getInstance().getAccessAddress());
sendRequest(address, serverUUID);
}
public void sendRequest(String address, UUID serverUUID) throws WebException {
Map<String, Object> configValues = getConfigValues(serverUUID);
for (Map.Entry<String, Object> entry : configValues.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (!Verify.notNull(key, value)) {
continue;
}
addVariable(key, value.toString());
}
super.sendRequest(address);
}
private void addConfigValue(Map<String, Object> configValues, Settings setting, Object value) {
if (value != null) {
configValues.put(setting.getPath(), value);
}
}
private Map<String, Object> getConfigValues(UUID serverUUID) throws WebException {
Map<String, Object> configValues = new HashMap<>();
if (!Check.isBungeeAvailable()) {
throw new WebException("Attempted to send config values from Bukkit to Bungee.");
}
addConfigValue(configValues, Settings.DB_TYPE, "mysql");
Settings[] sameStrings = new Settings[]{
Settings.DB_HOST, Settings.DB_USER, Settings.DB_PASS,
Settings.DB_DATABASE, Settings.FORMAT_DECIMALS, Settings.FORMAT_SECONDS,
Settings.FORMAT_DAY, Settings.FORMAT_DAYS, Settings.FORMAT_HOURS,
Settings.FORMAT_MINUTES, Settings.FORMAT_MONTHS, Settings.FORMAT_MONTH,
Settings.FORMAT_YEAR, Settings.FORMAT_YEARS, Settings.FORMAT_ZERO_SECONDS
};
for (Settings setting : sameStrings) {
addConfigValue(configValues, setting, setting.toString());
}
addConfigValue(configValues, Settings.DB_PORT, Settings.DB_PORT.getNumber());
addServerSpecificValues(configValues, serverUUID);
return configValues;
}
private void addServerSpecificValues(Map<String, Object> configValues, UUID serverUUID) {
ServerSpecificSettings settings = Settings.serverSpecific();
String theme = settings.getString(serverUUID, Settings.THEME_BASE);
Integer port = settings.getInt(serverUUID, Settings.WEBSERVER_PORT);
String name = settings.getString(serverUUID, Settings.SERVER_NAME);
if (!Verify.isEmpty(theme)) {
addConfigValue(configValues, Settings.THEME_BASE, theme);
}
if (port != null && port != 0) {
addConfigValue(configValues, Settings.WEBSERVER_PORT, port);
}
if (!Verify.isEmpty(name)) {
addConfigValue(configValues, Settings.SERVER_NAME, name);
}
}
}

View File

@ -35,7 +35,7 @@ WebServer:
Alias: 'alias'
# For those that want to serve Html from their own WebServer instead.
# Set up Html Export (https://github.com/Rsl1122/Plan-PlayerAnalytics/wiki/External-WebServer-Use)
# ATTENTION: On BungeeCord systems it is not possible to disable the WebServer on the plugin due to WebAPI requirements.
# ATTENTION: On BungeeCord systems it is not possible to disable the WebServer on the plugin due to connection requirements.
# If the WebServer is disabled with this setting BungeeCord systems will cease to function.
DisableWebServer: false
ExternalWebServerAddress: "https://www.example.address"