mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-01-10 18:37:57 +01:00
Jetty support (#2132)
- Refactored Webserver request handling code to be easier to read - Adds support for larger range of cipher suites and protocols for HTTPS - Adds support for HTTP/2 and ALPN and as result more concurrent users than before - APIs are fully compatible with previous code Thanks to Kopo for assistance Affects issues: - Close #1987
This commit is contained in:
parent
862815a537
commit
2aa189798d
@ -81,6 +81,9 @@ subprojects {
|
|||||||
commonsCodecVersion = "1.15"
|
commonsCodecVersion = "1.15"
|
||||||
caffeineVersion = "3.1.1"
|
caffeineVersion = "3.1.1"
|
||||||
mysqlVersion = "8.0.29"
|
mysqlVersion = "8.0.29"
|
||||||
|
jettyVersion = "11.0.6"
|
||||||
|
caffeineVersion = "2.9.2"
|
||||||
|
mysqlVersion = "8.0.26"
|
||||||
sqliteVersion = "3.36.0.3"
|
sqliteVersion = "3.36.0.3"
|
||||||
hikariVersion = "5.0.1"
|
hikariVersion = "5.0.1"
|
||||||
slf4jVersion = "1.7.36"
|
slf4jVersion = "1.7.36"
|
||||||
|
@ -51,6 +51,9 @@ dependencies {
|
|||||||
}
|
}
|
||||||
mysqlDriver "mysql:mysql-connector-java:$mysqlVersion"
|
mysqlDriver "mysql:mysql-connector-java:$mysqlVersion"
|
||||||
sqliteDriver "org.xerial:sqlite-jdbc:$sqliteVersion"
|
sqliteDriver "org.xerial:sqlite-jdbc:$sqliteVersion"
|
||||||
|
implementation "org.eclipse.jetty:jetty-server:$jettyVersion"
|
||||||
|
implementation "org.eclipse.jetty:jetty-alpn-java-server:$jettyVersion"
|
||||||
|
implementation "org.eclipse.jetty.http2:http2-server:$jettyVersion"
|
||||||
|
|
||||||
testImplementation project(":api")
|
testImplementation project(":api")
|
||||||
testImplementation "com.google.code.gson:gson:$gsonVersion"
|
testImplementation "com.google.code.gson:gson:$gsonVersion"
|
||||||
@ -166,4 +169,6 @@ shadowJar {
|
|||||||
exclude "org/jayway/**/*"
|
exclude "org/jayway/**/*"
|
||||||
exclude "google/protobuf/**/*"
|
exclude "google/protobuf/**/*"
|
||||||
exclude "jargs/gnu/**/*"
|
exclude "jargs/gnu/**/*"
|
||||||
|
|
||||||
|
mergeServiceFiles()
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.djrapitops.plan.delivery.webserver;
|
package com.djrapitops.plan.delivery.webserver;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||||
import com.djrapitops.plan.identification.Server;
|
import com.djrapitops.plan.identification.Server;
|
||||||
import com.djrapitops.plan.identification.properties.ServerProperties;
|
import com.djrapitops.plan.identification.properties.ServerProperties;
|
||||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.djrapitops.plan.delivery.webserver;
|
package com.djrapitops.plan.delivery.webserver;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||||
|
@ -35,14 +35,7 @@ public class RequestBodyConverter {
|
|||||||
* @return {@link URIQuery}.
|
* @return {@link URIQuery}.
|
||||||
*/
|
*/
|
||||||
public static URIQuery formBody(Request request) {
|
public static URIQuery formBody(Request request) {
|
||||||
if (
|
return new URIQuery(new String(request.getRequestBody(), StandardCharsets.UTF_8));
|
||||||
"POST".equalsIgnoreCase(request.getMethod()) &&
|
|
||||||
"application/x-www-form-urlencoded".equalsIgnoreCase(request.getHeader("Content-type").orElse(""))
|
|
||||||
) {
|
|
||||||
return new URIQuery(new String(request.getRequestBody(), StandardCharsets.UTF_8));
|
|
||||||
} else {
|
|
||||||
return new URIQuery("");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T bodyJson(Request request, Gson gson, Class<T> ofType) {
|
public static <T> T bodyJson(Request request, Gson gson, Class<T> ofType) {
|
||||||
|
@ -1,257 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of Player Analytics (Plan).
|
|
||||||
*
|
|
||||||
* Plan is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Plan is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package com.djrapitops.plan.delivery.webserver;
|
|
||||||
|
|
||||||
import com.djrapitops.plan.delivery.domain.auth.User;
|
|
||||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
|
||||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
|
||||||
import com.djrapitops.plan.delivery.web.resolver.request.URIPath;
|
|
||||||
import com.djrapitops.plan.delivery.web.resolver.request.URIQuery;
|
|
||||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
|
||||||
import com.djrapitops.plan.delivery.webserver.auth.*;
|
|
||||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
|
||||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
|
||||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
|
||||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
|
||||||
import com.djrapitops.plan.settings.locale.Locale;
|
|
||||||
import com.djrapitops.plan.settings.locale.lang.PluginLang;
|
|
||||||
import com.djrapitops.plan.storage.database.DBSystem;
|
|
||||||
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
|
||||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
|
||||||
import com.sun.net.httpserver.Headers;
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
|
||||||
import com.sun.net.httpserver.HttpHandler;
|
|
||||||
import net.playeranalytics.plugin.server.PluginLogger;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.commons.text.TextStringBuilder;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HttpHandler for WebServer request management.
|
|
||||||
*
|
|
||||||
* @author AuroraLS3
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class RequestHandler implements HttpHandler {
|
|
||||||
|
|
||||||
private final Locale locale;
|
|
||||||
private final PlanConfig config;
|
|
||||||
private final DBSystem dbSystem;
|
|
||||||
private final Addresses addresses;
|
|
||||||
private final ResponseResolver responseResolver;
|
|
||||||
private final ResponseFactory responseFactory;
|
|
||||||
private final PluginLogger logger;
|
|
||||||
private final ErrorLogger errorLogger;
|
|
||||||
|
|
||||||
private final ActiveCookieStore activeCookieStore;
|
|
||||||
private final PassBruteForceGuard bruteForceGuard;
|
|
||||||
private List<String> ipWhitelist = null;
|
|
||||||
|
|
||||||
private final AtomicBoolean warnedAboutXForwardedSecurityIssue = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
RequestHandler(
|
|
||||||
Locale locale,
|
|
||||||
PlanConfig config,
|
|
||||||
DBSystem dbSystem,
|
|
||||||
Addresses addresses,
|
|
||||||
ResponseResolver responseResolver,
|
|
||||||
ResponseFactory responseFactory,
|
|
||||||
ActiveCookieStore activeCookieStore,
|
|
||||||
PluginLogger logger,
|
|
||||||
ErrorLogger errorLogger
|
|
||||||
) {
|
|
||||||
this.locale = locale;
|
|
||||||
this.config = config;
|
|
||||||
this.dbSystem = dbSystem;
|
|
||||||
this.addresses = addresses;
|
|
||||||
this.responseResolver = responseResolver;
|
|
||||||
this.responseFactory = responseFactory;
|
|
||||||
this.activeCookieStore = activeCookieStore;
|
|
||||||
this.logger = logger;
|
|
||||||
this.errorLogger = errorLogger;
|
|
||||||
|
|
||||||
bruteForceGuard = new PassBruteForceGuard();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(HttpExchange exchange) {
|
|
||||||
try {
|
|
||||||
Response response = getResponse(exchange);
|
|
||||||
response.getHeaders().putIfAbsent("Access-Control-Allow-Origin", config.get(WebserverSettings.CORS_ALLOW_ORIGIN));
|
|
||||||
response.getHeaders().putIfAbsent("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
||||||
response.getHeaders().putIfAbsent("Access-Control-Allow-Credentials", "true");
|
|
||||||
response.getHeaders().putIfAbsent("X-Robots-Tag", "noindex, nofollow");
|
|
||||||
ResponseSender sender = new ResponseSender(addresses, exchange, response);
|
|
||||||
sender.send();
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (config.isTrue(PluginSettings.DEV_MODE)) {
|
|
||||||
logger.warn("THIS ERROR IS ONLY LOGGED IN DEV MODE:");
|
|
||||||
errorLogger.warn(e, ErrorContext.builder()
|
|
||||||
.whatToDo("THIS ERROR IS ONLY LOGGED IN DEV MODE")
|
|
||||||
.related(exchange.getRequestMethod(), exchange.getRemoteAddress(), exchange.getRequestHeaders(), exchange.getResponseHeaders(), exchange.getRequestURI())
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
exchange.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Response getResponse(HttpExchange exchange) {
|
|
||||||
if (ipWhitelist == null) {
|
|
||||||
ipWhitelist = config.isTrue(WebserverSettings.IP_WHITELIST)
|
|
||||||
? config.get(WebserverSettings.WHITELIST)
|
|
||||||
: Collections.emptyList();
|
|
||||||
}
|
|
||||||
String accessor = getAccessorAddress(exchange);
|
|
||||||
Request request = null;
|
|
||||||
Response response;
|
|
||||||
try {
|
|
||||||
request = buildRequest(exchange);
|
|
||||||
if (bruteForceGuard.shouldPreventRequest(accessor)) {
|
|
||||||
response = responseFactory.failedLoginAttempts403();
|
|
||||||
} else if (!ipWhitelist.isEmpty() && !ipWhitelist.contains(accessor)) {
|
|
||||||
response = responseFactory.ipWhitelist403(accessor);
|
|
||||||
logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_IP_WHITELIST_BLOCK, accessor, exchange.getRequestURI().toString()));
|
|
||||||
} else {
|
|
||||||
response = responseResolver.getResponse(request);
|
|
||||||
}
|
|
||||||
} catch (WebUserAuthException thrownByAuthentication) {
|
|
||||||
FailReason failReason = thrownByAuthentication.getFailReason();
|
|
||||||
if (failReason == FailReason.USER_PASS_MISMATCH) {
|
|
||||||
bruteForceGuard.increaseAttemptCountOnFailedLogin(accessor);
|
|
||||||
response = responseFactory.badRequest(failReason.getReason(), "/auth/login");
|
|
||||||
} else {
|
|
||||||
String from = exchange.getRequestURI().toASCIIString();
|
|
||||||
String directTo = StringUtils.startsWithAny(from, "/auth/", "/login") ? "/login" : "/login?from=." + from;
|
|
||||||
response = Response.builder()
|
|
||||||
.redirectTo(directTo)
|
|
||||||
.setHeader("Set-Cookie", "auth=expired; Path=/; Max-Age=0; SameSite=Lax; Secure;")
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bruteForceGuard.shouldPreventRequest(accessor)) {
|
|
||||||
response = responseFactory.failedLoginAttempts403();
|
|
||||||
}
|
|
||||||
if (response.getCode() != 401 // Not failed
|
|
||||||
&& response.getCode() != 403 // Not blocked
|
|
||||||
&& request != null && request.getUser().isPresent() // Logged in
|
|
||||||
) {
|
|
||||||
bruteForceGuard.resetAttemptCount(accessor);
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getAccessorAddress(HttpExchange exchange) {
|
|
||||||
if (config.isTrue(WebserverSettings.IP_WHITELIST_X_FORWARDED)) {
|
|
||||||
String header = exchange.getRequestHeaders().getFirst("X-Forwarded-For");
|
|
||||||
if (header == null) {
|
|
||||||
warnAboutXForwardedForSecurityIssue();
|
|
||||||
} else {
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return exchange.getRemoteAddress().getAddress().getHostAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void warnAboutXForwardedForSecurityIssue() {
|
|
||||||
if (!warnedAboutXForwardedSecurityIssue.get()) {
|
|
||||||
logger.warn("Security Vulnerability due to misconfiguration: X-Forwarded-For header was not present in a request & '" +
|
|
||||||
WebserverSettings.IP_WHITELIST_X_FORWARDED.getPath() + "' is 'true'!");
|
|
||||||
logger.warn("This could mean non-reverse-proxy access is not blocked & someone can use IP Spoofing to bypass security!");
|
|
||||||
logger.warn("Make sure you can only access Plan panel from your reverse-proxy or disable this setting.");
|
|
||||||
}
|
|
||||||
warnedAboutXForwardedSecurityIssue.set(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Request buildRequest(HttpExchange exchange) {
|
|
||||||
String requestMethod = exchange.getRequestMethod();
|
|
||||||
URIPath path = new URIPath(exchange.getRequestURI().getPath());
|
|
||||||
URIQuery query = new URIQuery(exchange.getRequestURI().getRawQuery());
|
|
||||||
byte[] requestBody = readRequestBody(exchange);
|
|
||||||
WebUser user = getWebUser(exchange);
|
|
||||||
Map<String, String> headers = getRequestHeaders(exchange);
|
|
||||||
return new Request(requestMethod, path, query, user, headers, requestBody);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] readRequestBody(HttpExchange exchange) {
|
|
||||||
try (ByteArrayOutputStream buf = new ByteArrayOutputStream(512)) {
|
|
||||||
int b;
|
|
||||||
while ((b = exchange.getRequestBody().read()) != -1) {
|
|
||||||
buf.write((byte) b);
|
|
||||||
}
|
|
||||||
return buf.toByteArray();
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
// requestBody stays empty
|
|
||||||
return new byte[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private WebUser getWebUser(HttpExchange exchange) {
|
|
||||||
return getAuthentication(exchange.getRequestHeaders())
|
|
||||||
.map(Authentication::getUser) // Can throw WebUserAuthException
|
|
||||||
.map(User::toWebUser)
|
|
||||||
.orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, String> getRequestHeaders(HttpExchange exchange) {
|
|
||||||
Map<String, String> headers = new HashMap<>();
|
|
||||||
for (Map.Entry<String, List<String>> e : exchange.getRequestHeaders().entrySet()) {
|
|
||||||
List<String> value = e.getValue();
|
|
||||||
headers.put(e.getKey(), new TextStringBuilder().appendWithSeparators(value, ";").build());
|
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<Authentication> getAuthentication(Headers requestHeaders) {
|
|
||||||
if (config.isTrue(WebserverSettings.DISABLED_AUTHENTICATION)) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> cookies = requestHeaders.get("Cookie");
|
|
||||||
if (cookies != null && !cookies.isEmpty()) {
|
|
||||||
for (String cookie : new TextStringBuilder().appendWithSeparators(cookies, ";").build().split(";")) {
|
|
||||||
String[] split = cookie.trim().split("=", 2);
|
|
||||||
String name = split[0];
|
|
||||||
String value = split[1];
|
|
||||||
if ("auth".equals(name)) {
|
|
||||||
return Optional.of(new CookieAuthentication(activeCookieStore, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> authorization = requestHeaders.get("Authorization");
|
|
||||||
if (authorization == null || authorization.isEmpty()) return Optional.empty();
|
|
||||||
|
|
||||||
String authLine = authorization.get(0);
|
|
||||||
if (StringUtils.contains(authLine, "Basic ")) {
|
|
||||||
return Optional.of(new BasicAuthentication(StringUtils.split(authLine, ' ')[1], dbSystem.getDatabase()));
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResponseResolver getResponseResolver() {
|
|
||||||
return responseResolver;
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,6 +26,7 @@ import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
|
|||||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||||
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||||
import com.djrapitops.plan.delivery.webserver.resolver.*;
|
import com.djrapitops.plan.delivery.webserver.resolver.*;
|
||||||
import com.djrapitops.plan.delivery.webserver.resolver.auth.*;
|
import com.djrapitops.plan.delivery.webserver.resolver.auth.*;
|
||||||
import com.djrapitops.plan.delivery.webserver.resolver.json.RootJSONResolver;
|
import com.djrapitops.plan.delivery.webserver.resolver.json.RootJSONResolver;
|
||||||
|
@ -19,6 +19,7 @@ package com.djrapitops.plan.delivery.webserver;
|
|||||||
import com.djrapitops.plan.SubSystem;
|
import com.djrapitops.plan.SubSystem;
|
||||||
import com.djrapitops.plan.delivery.web.ResourceService;
|
import com.djrapitops.plan.delivery.web.ResourceService;
|
||||||
import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieStore;
|
import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieStore;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.auth;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||||
|
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class AllowedIpList {
|
||||||
|
|
||||||
|
private final PlanConfig config;
|
||||||
|
private final AtomicReference<List<String>> allowList = new AtomicReference<>(null);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AllowedIpList(PlanConfig config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void prepare() {
|
||||||
|
if (allowList.get() == null) {
|
||||||
|
allowList.set(config.isTrue(WebserverSettings.IP_WHITELIST)
|
||||||
|
? config.get(WebserverSettings.WHITELIST)
|
||||||
|
: Collections.emptyList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllowed(String accessAddress) {
|
||||||
|
prepare();
|
||||||
|
|
||||||
|
List<String> ips = allowList.get();
|
||||||
|
|
||||||
|
return ips.isEmpty() || ips.contains(accessAddress);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.auth;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.InternalRequest;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class AuthenticationExtractor {
|
||||||
|
|
||||||
|
private final ActiveCookieStore activeCookieStore;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AuthenticationExtractor(ActiveCookieStore activeCookieStore) {
|
||||||
|
this.activeCookieStore = activeCookieStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Authentication> extractAuthentication(InternalRequest internalRequest) {
|
||||||
|
return getCookieAuthentication(internalRequest.getCookies());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Authentication> getCookieAuthentication(List<Cookie> cookies) {
|
||||||
|
for (Cookie cookie : cookies) {
|
||||||
|
if ("auth".equals(cookie.getName())) {
|
||||||
|
return Optional.of(new CookieAuthentication(activeCookieStore, cookie.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of Player Analytics (Plan).
|
|
||||||
*
|
|
||||||
* Plan is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Plan is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package com.djrapitops.plan.delivery.webserver.auth;
|
|
||||||
|
|
||||||
import com.djrapitops.plan.delivery.domain.auth.User;
|
|
||||||
import com.djrapitops.plan.exceptions.PassEncryptException;
|
|
||||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
|
||||||
import com.djrapitops.plan.exceptions.database.DBOpException;
|
|
||||||
import com.djrapitops.plan.storage.database.Database;
|
|
||||||
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
|
|
||||||
import com.djrapitops.plan.utilities.Base64Util;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authentication handling for Basic Auth.
|
|
||||||
* <p>
|
|
||||||
* Basic access authentication (Wikipedia):
|
|
||||||
* https://en.wikipedia.org/wiki/Basic_access_authentication
|
|
||||||
*
|
|
||||||
* @author AuroraLS3
|
|
||||||
*/
|
|
||||||
public class BasicAuthentication implements Authentication {
|
|
||||||
|
|
||||||
private final String authenticationString;
|
|
||||||
private final Database database;
|
|
||||||
|
|
||||||
public BasicAuthentication(String authenticationString, Database database) {
|
|
||||||
this.authenticationString = authenticationString;
|
|
||||||
this.database = database;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User getUser() {
|
|
||||||
String decoded = Base64Util.decode(authenticationString);
|
|
||||||
|
|
||||||
String[] userInfo = StringUtils.split(decoded, ':');
|
|
||||||
if (userInfo.length != 2) {
|
|
||||||
throw new WebUserAuthException(FailReason.USER_AND_PASS_NOT_SPECIFIED, Arrays.toString(userInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
String username = userInfo[0];
|
|
||||||
String passwordRaw = userInfo[1];
|
|
||||||
|
|
||||||
Database.State dbState = database.getState();
|
|
||||||
if (dbState != Database.State.OPEN) {
|
|
||||||
throw new WebUserAuthException(FailReason.DATABASE_NOT_OPEN, "State was: " + dbState.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
User user = database.query(WebUserQueries.fetchUser(username))
|
|
||||||
.orElseThrow(() -> new WebUserAuthException(FailReason.USER_DOES_NOT_EXIST, username));
|
|
||||||
|
|
||||||
boolean correctPass = user.doesPasswordMatch(passwordRaw);
|
|
||||||
if (!correctPass) {
|
|
||||||
throw new WebUserAuthException(FailReason.USER_PASS_MISMATCH, username);
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
} catch (DBOpException | PassEncryptException e) {
|
|
||||||
throw new WebUserAuthException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.auth;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
public class Cookie {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
public Cookie(String rawRepresentation) {
|
||||||
|
String[] split = StringUtils.split(rawRepresentation, "=", 2);
|
||||||
|
name = split[0];
|
||||||
|
value = split[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cookie(String name, String value) {
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.configuration;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.webserver.auth.AllowedIpList;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.AccessAddressPolicy;
|
||||||
|
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||||
|
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||||
|
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.InvalidPathException;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class WebserverConfiguration {
|
||||||
|
|
||||||
|
private final PlanFiles files;
|
||||||
|
private final PlanConfig config;
|
||||||
|
private final AllowedIpList allowedIpList;
|
||||||
|
private final WebserverLogMessages webserverLogMessages;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public WebserverConfiguration(PlanFiles files, PlanConfig config, AllowedIpList allowedIpList, WebserverLogMessages webserverLogMessages) {
|
||||||
|
this.files = files;
|
||||||
|
this.config = config;
|
||||||
|
this.allowedIpList = allowedIpList;
|
||||||
|
this.webserverLogMessages = webserverLogMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebserverLogMessages getWebserverLogMessages() {
|
||||||
|
return webserverLogMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAuthenticationDisabled() {
|
||||||
|
return config.isTrue(WebserverSettings.DISABLED_AUTHENTICATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAuthenticationEnabled() {
|
||||||
|
return config.isFalse(WebserverSettings.DISABLED_AUTHENTICATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccessAddressPolicy getAccessAddressPolicy() {
|
||||||
|
return config.isTrue(WebserverSettings.IP_USE_X_FORWARDED_FOR)
|
||||||
|
? AccessAddressPolicy.X_FORWARDED_FOR_HEADER
|
||||||
|
: AccessAddressPolicy.SOCKET_IP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AllowedIpList getAllowedIpList() {
|
||||||
|
return allowedIpList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAllowedCorsOrigin() {
|
||||||
|
return config.get(WebserverSettings.CORS_ALLOW_ORIGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return config.get(WebserverSettings.PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWebserverDisabled() {
|
||||||
|
return config.isTrue(WebserverSettings.DISABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKeyStorePath() {
|
||||||
|
String keyStorePath = config.get(WebserverSettings.CERTIFICATE_PATH);
|
||||||
|
|
||||||
|
if ("proxy".equalsIgnoreCase(keyStorePath)) {
|
||||||
|
return keyStorePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!Paths.get(keyStorePath).isAbsolute()) {
|
||||||
|
keyStorePath = files.getDataFolder() + File.separator + keyStorePath;
|
||||||
|
}
|
||||||
|
} catch (InvalidPathException e) {
|
||||||
|
webserverLogMessages.keystoreNotFoundError(e, keyStorePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyStorePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKeyStorePassword() {
|
||||||
|
return config.get(WebserverSettings.CERTIFICATE_STOREPASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKeyManagerPassword() {
|
||||||
|
return config.get(WebserverSettings.CERTIFICATE_KEYPASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlias() {
|
||||||
|
return config.get(WebserverSettings.CERTIFICATE_ALIAS);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.configuration;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.webserver.Addresses;
|
||||||
|
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||||
|
import com.djrapitops.plan.settings.locale.Locale;
|
||||||
|
import com.djrapitops.plan.settings.locale.lang.PluginLang;
|
||||||
|
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||||
|
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||||
|
import net.playeranalytics.plugin.server.PluginLogger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.nio.file.InvalidPathException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class WebserverLogMessages {
|
||||||
|
|
||||||
|
private final PluginLogger logger;
|
||||||
|
private final ErrorLogger errorLogger;
|
||||||
|
private final Locale locale;
|
||||||
|
private final Addresses addresses;
|
||||||
|
|
||||||
|
private final AtomicLong warnedAboutXForwardedSecurityIssue = new AtomicLong(0L);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public WebserverLogMessages(PluginLogger logger, ErrorLogger errorLogger, Locale locale, Addresses addresses) {
|
||||||
|
this.logger = logger;
|
||||||
|
this.errorLogger = errorLogger;
|
||||||
|
this.locale = locale;
|
||||||
|
this.addresses = addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void warnAboutXForwardedForSecurityIssue() {
|
||||||
|
if (System.currentTimeMillis() - warnedAboutXForwardedSecurityIssue.get() > TimeUnit.MINUTES.toMillis(2L)) {
|
||||||
|
logger.warn("Security Vulnerability due to misconfiguration: X-Forwarded-For header was not present in a request & '" +
|
||||||
|
WebserverSettings.IP_USE_X_FORWARDED_FOR.getPath() + "' is 'true'!");
|
||||||
|
logger.warn("This could mean non-reverse-proxy access is not blocked & someone can use IP Spoofing to bypass security!");
|
||||||
|
logger.warn("Make sure you can only access Plan panel from your reverse-proxy or disable this setting.");
|
||||||
|
warnedAboutXForwardedSecurityIssue.set(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void warnAboutWhitelistBlock(String accessAddress, String requestedURIString) {
|
||||||
|
logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_IP_WHITELIST_BLOCK, accessAddress, requestedURIString));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void infoWebserverEnabled(int port) {
|
||||||
|
String address = addresses.getAccessAddress().orElse(addresses.getFallbackLocalhostAddress());
|
||||||
|
logger.info(locale.getString(PluginLang.ENABLED_WEB_SERVER, port, address));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void warnWebserverDisabledByConfig() {
|
||||||
|
logger.warn(locale.getString(PluginLang.ENABLE_NOTIFY_WEB_SERVER_DISABLED));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void keystoreNotFoundError(InvalidPathException error, String keyStorePath) {
|
||||||
|
String errorMessage = error.getMessage();
|
||||||
|
logger.error("WebServer: Could not find Keystore: " + errorMessage);
|
||||||
|
errorLogger.error(error, ErrorContext.builder()
|
||||||
|
.whatToDo(errorMessage + ", Fix this path to point to a valid keystore file: " + keyStorePath)
|
||||||
|
.related(keyStorePath).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void authenticationNotPossible() {
|
||||||
|
logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_HTTP));
|
||||||
|
logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_HTTP_USER_AUTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void authenticationUsingProxy() {
|
||||||
|
logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_USING_PROXY_MODE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void invalidCertificate() {
|
||||||
|
logger.warn(locale.getString(PluginLang.WEB_SERVER_FAIL_STORE_LOAD));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void keystoreFileNotFound() {
|
||||||
|
logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_NO_CERT_FILE));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.http;
|
||||||
|
|
||||||
|
public enum AccessAddressPolicy {
|
||||||
|
|
||||||
|
SOCKET_IP,
|
||||||
|
X_FORWARDED_FOR_HEADER
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.http;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.domain.auth.User;
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.auth.Authentication;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.auth.AuthenticationExtractor;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.auth.Cookie;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a HTTP request.
|
||||||
|
*
|
||||||
|
* @see com.djrapitops.plan.delivery.web.resolver.request.Request for API based request, as this interface is for internal use.
|
||||||
|
*/
|
||||||
|
public interface InternalRequest {
|
||||||
|
|
||||||
|
default String getAccessAddress(WebserverConfiguration webserverConfiguration) {
|
||||||
|
AccessAddressPolicy accessAddressPolicy = webserverConfiguration.getAccessAddressPolicy();
|
||||||
|
if (accessAddressPolicy == AccessAddressPolicy.X_FORWARDED_FOR_HEADER) {
|
||||||
|
String fromHeader = getAccessAddressFromHeader();
|
||||||
|
if (fromHeader == null) {
|
||||||
|
webserverConfiguration.getWebserverLogMessages().warnAboutXForwardedForSecurityIssue();
|
||||||
|
return getAccessAddressFromSocketIp();
|
||||||
|
} else {
|
||||||
|
return fromHeader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getAccessAddressFromSocketIp();
|
||||||
|
}
|
||||||
|
|
||||||
|
Request toRequest();
|
||||||
|
|
||||||
|
List<Cookie> getCookies();
|
||||||
|
|
||||||
|
String getAccessAddressFromSocketIp();
|
||||||
|
|
||||||
|
String getAccessAddressFromHeader();
|
||||||
|
|
||||||
|
String getRequestedURIString();
|
||||||
|
|
||||||
|
default WebUser getWebUser(WebserverConfiguration webserverConfiguration, AuthenticationExtractor authenticationExtractor) {
|
||||||
|
return getAuthentication(webserverConfiguration, authenticationExtractor)
|
||||||
|
.map(Authentication::getUser) // Can throw WebUserAuthException
|
||||||
|
.map(User::toWebUser)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
default Optional<Authentication> getAuthentication(WebserverConfiguration webserverConfiguration, AuthenticationExtractor authenticationExtractor) {
|
||||||
|
if (webserverConfiguration.isAuthenticationDisabled()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return authenticationExtractor.extractAuthentication(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.http;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.request.URIPath;
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.request.URIQuery;
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.auth.AuthenticationExtractor;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.auth.Cookie;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.apache.commons.text.TextStringBuilder;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Spliterators;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
public class JettyInternalRequest implements InternalRequest {
|
||||||
|
|
||||||
|
private final Request baseRequest;
|
||||||
|
private final HttpServletRequest request;
|
||||||
|
private final WebserverConfiguration webserverConfiguration;
|
||||||
|
private final AuthenticationExtractor authenticationExtractor;
|
||||||
|
|
||||||
|
public JettyInternalRequest(Request baseRequest, HttpServletRequest request, WebserverConfiguration webserverConfiguration, AuthenticationExtractor authenticationExtractor) {
|
||||||
|
this.baseRequest = baseRequest;
|
||||||
|
this.request = request;
|
||||||
|
this.webserverConfiguration = webserverConfiguration;
|
||||||
|
this.authenticationExtractor = authenticationExtractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAccessAddressFromSocketIp() {
|
||||||
|
return baseRequest.getRemoteAddr();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAccessAddressFromHeader() {
|
||||||
|
return baseRequest.getHeader(HttpHeader.X_FORWARDED_FOR.asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public com.djrapitops.plan.delivery.web.resolver.request.Request toRequest() {
|
||||||
|
String requestMethod = baseRequest.getMethod();
|
||||||
|
URIPath path = new URIPath(baseRequest.getHttpURI().getDecodedPath());
|
||||||
|
URIQuery query = new URIQuery(baseRequest.getHttpURI().getQuery());
|
||||||
|
byte[] requestBody = readRequestBody();
|
||||||
|
WebUser user = getWebUser(webserverConfiguration, authenticationExtractor);
|
||||||
|
Map<String, String> headers = getRequestHeaders();
|
||||||
|
return new com.djrapitops.plan.delivery.web.resolver.request.Request(requestMethod, path, query, user, headers, requestBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> getRequestHeaders() {
|
||||||
|
return streamHeaderNames()
|
||||||
|
.collect(Collectors.toMap(Function.identity(), baseRequest::getHeader,
|
||||||
|
(one, two) -> one + ';' + two));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream<String> streamHeaderNames() {
|
||||||
|
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(baseRequest.getHeaderNames().asIterator(), 0), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readRequestBody() {
|
||||||
|
try (BufferedReader reader = request.getReader();
|
||||||
|
ByteArrayOutputStream buf = new ByteArrayOutputStream(512)) {
|
||||||
|
int b;
|
||||||
|
while ((b = reader.read()) != -1) {
|
||||||
|
buf.write((byte) b);
|
||||||
|
}
|
||||||
|
return buf.toByteArray();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
// requestBody stays empty
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Cookie> getCookies() {
|
||||||
|
List<String> textCookies = getCookieHeaders();
|
||||||
|
List<Cookie> cookies = new ArrayList<>();
|
||||||
|
if (!textCookies.isEmpty()) {
|
||||||
|
String[] separated = new TextStringBuilder().appendWithSeparators(textCookies, ";").build().split(";");
|
||||||
|
for (String textCookie : separated) {
|
||||||
|
cookies.add(new Cookie(textCookie));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getCookieHeaders() {
|
||||||
|
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(request.getHeaders(HttpHeader.COOKIE.asString()).asIterator(), 0), false)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRequestedURIString() {
|
||||||
|
return baseRequest.getRequestURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "JettyInternalRequest{" +
|
||||||
|
"baseRequest=" + baseRequest +
|
||||||
|
", request=" + request +
|
||||||
|
", webserverConfiguration=" + webserverConfiguration +
|
||||||
|
", authenticationExtractor=" + authenticationExtractor +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.http;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.Addresses;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.auth.AuthenticationExtractor;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration;
|
||||||
|
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||||
|
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||||
|
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||||
|
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import net.playeranalytics.plugin.server.PluginLogger;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class JettyRequestHandler extends AbstractHandler {
|
||||||
|
|
||||||
|
private final WebserverConfiguration webserverConfiguration;
|
||||||
|
private final AuthenticationExtractor authenticationExtractor;
|
||||||
|
private final Addresses addresses;
|
||||||
|
private final RequestHandler requestHandler;
|
||||||
|
private final PlanConfig config;
|
||||||
|
private final PluginLogger logger;
|
||||||
|
private final ErrorLogger errorLogger;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public JettyRequestHandler(WebserverConfiguration webserverConfiguration, AuthenticationExtractor authenticationExtractor, Addresses addresses, RequestHandler requestHandler, PlanConfig config, PluginLogger logger, ErrorLogger errorLogger) {
|
||||||
|
this.webserverConfiguration = webserverConfiguration;
|
||||||
|
this.authenticationExtractor = authenticationExtractor;
|
||||||
|
this.addresses = addresses;
|
||||||
|
this.requestHandler = requestHandler;
|
||||||
|
this.config = config;
|
||||||
|
this.logger = logger;
|
||||||
|
this.errorLogger = errorLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException {
|
||||||
|
try {
|
||||||
|
InternalRequest internalRequest = new JettyInternalRequest(baseRequest, servletRequest, webserverConfiguration, authenticationExtractor);
|
||||||
|
Response response = requestHandler.getResponse(internalRequest);
|
||||||
|
new JettyResponseSender(response, servletRequest, servletResponse, addresses).send();
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (config.isTrue(PluginSettings.DEV_MODE)) {
|
||||||
|
logger.warn("THIS ERROR IS ONLY LOGGED IN DEV MODE:");
|
||||||
|
errorLogger.warn(e, ErrorContext.builder()
|
||||||
|
.whatToDo("THIS ERROR IS ONLY LOGGED IN DEV MODE")
|
||||||
|
.related(baseRequest.getMethod(), baseRequest.getRemoteAddr(), target, baseRequest.getRequestURI())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.http;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.Addresses;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
|
public class JettyResponseSender {
|
||||||
|
|
||||||
|
private final Response response;
|
||||||
|
private final HttpServletRequest servletRequest;
|
||||||
|
private final HttpServletResponse servletResponse;
|
||||||
|
private final Addresses addresses;
|
||||||
|
|
||||||
|
public JettyResponseSender(Response response, HttpServletRequest servletRequest, HttpServletResponse servletResponse, Addresses addresses) {
|
||||||
|
this.response = response;
|
||||||
|
this.servletRequest = servletRequest;
|
||||||
|
this.servletResponse = servletResponse;
|
||||||
|
this.addresses = addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send() throws IOException {
|
||||||
|
setResponseHeaders();
|
||||||
|
if ("HEAD".equals(servletRequest.getMethod()) || response.getCode() == 204) {
|
||||||
|
sendHeadResponse();
|
||||||
|
} else if ("bytes".equalsIgnoreCase(response.getHeaders().get(HttpHeader.ACCEPT_RANGES.asString()))) {
|
||||||
|
sendRawBytes();
|
||||||
|
} else {
|
||||||
|
sendCompressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendHeadResponse() throws IOException {
|
||||||
|
try {
|
||||||
|
response.getHeaders().remove(HttpHeader.CONTENT_LENGTH.asString());
|
||||||
|
beginSend();
|
||||||
|
} finally {
|
||||||
|
servletResponse.getOutputStream().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setResponseHeaders() {
|
||||||
|
Map<String, String> responseHeaders = response.getHeaders();
|
||||||
|
correctRedirect(responseHeaders);
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> header : responseHeaders.entrySet()) {
|
||||||
|
servletResponse.setHeader(header.getKey(), header.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void correctRedirect(Map<String, String> responseHeaders) {
|
||||||
|
String redirect = responseHeaders.get("Location");
|
||||||
|
if (redirect != null) {
|
||||||
|
if (redirect.startsWith("http") || !redirect.startsWith("/")) return;
|
||||||
|
addresses.getAccessAddress().ifPresent(address -> responseHeaders.put("Location", address + redirect));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendCompressed() throws IOException {
|
||||||
|
servletResponse.setHeader(HttpHeader.CONTENT_ENCODING.asString(), "gzip");
|
||||||
|
beginSend();
|
||||||
|
try (OutputStream out = new GZIPOutputStream(servletResponse.getOutputStream())) {
|
||||||
|
send(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void beginSend() throws IOException {
|
||||||
|
String length = response.getHeaders().get(HttpHeader.CONTENT_LENGTH.asString());
|
||||||
|
if (length == null || "0".equals(length) || response.getCode() == 204 || "HEAD".equals(servletRequest.getMethod())) {
|
||||||
|
servletResponse.setHeader(HttpHeader.CONTENT_LENGTH.asString(), null);
|
||||||
|
}
|
||||||
|
// Return a content length of -1 for HTTP code 204 (No content)
|
||||||
|
// and HEAD requests to avoid warning messages.
|
||||||
|
servletResponse.setStatus(response.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendRawBytes() throws IOException {
|
||||||
|
beginSend();
|
||||||
|
try (OutputStream out = servletResponse.getOutputStream()) {
|
||||||
|
send(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(OutputStream out) throws IOException {
|
||||||
|
try (
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(response.getBytes())
|
||||||
|
) {
|
||||||
|
byte[] buffer = new byte[2048];
|
||||||
|
int count;
|
||||||
|
while ((count = bis.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,222 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.http;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.webserver.ResponseResolver;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.configuration.WebserverLogMessages;
|
||||||
|
import com.djrapitops.plan.exceptions.EnableException;
|
||||||
|
import net.playeranalytics.plugin.server.PluginLogger;
|
||||||
|
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
|
||||||
|
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
|
||||||
|
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.*;
|
||||||
|
import org.eclipse.jetty.server.handler.HandlerList;
|
||||||
|
import org.eclipse.jetty.server.handler.SecuredRedirectHandler;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class JettyWebserver implements WebServer {
|
||||||
|
|
||||||
|
private final PluginLogger logger;
|
||||||
|
private final WebserverConfiguration webserverConfiguration;
|
||||||
|
private final LegacyJettySSLContextLoader legacyJettySSLContextLoader;
|
||||||
|
private final JettyRequestHandler jettyRequestHandler;
|
||||||
|
private final ResponseResolver responseResolver;
|
||||||
|
private final WebserverLogMessages webserverLogMessages;
|
||||||
|
|
||||||
|
private int port;
|
||||||
|
private boolean usingHttps;
|
||||||
|
private Server webserver;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public JettyWebserver(PluginLogger logger, WebserverConfiguration webserverConfiguration, LegacyJettySSLContextLoader legacyJettySSLContextLoader, JettyRequestHandler jettyRequestHandler, ResponseResolver responseResolver) {
|
||||||
|
this.logger = logger;
|
||||||
|
this.webserverConfiguration = webserverConfiguration;
|
||||||
|
webserverLogMessages = webserverConfiguration.getWebserverLogMessages();
|
||||||
|
this.legacyJettySSLContextLoader = legacyJettySSLContextLoader;
|
||||||
|
this.jettyRequestHandler = jettyRequestHandler;
|
||||||
|
this.responseResolver = responseResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
if (isEnabled()) return;
|
||||||
|
|
||||||
|
if (webserverConfiguration.isWebserverDisabled()) {
|
||||||
|
webserverLogMessages.warnWebserverDisabledByConfig();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
webserver = new Server();
|
||||||
|
|
||||||
|
this.port = webserverConfiguration.getPort();
|
||||||
|
|
||||||
|
HttpConfiguration configuration = new HttpConfiguration();
|
||||||
|
Optional<SslContextFactory.Server> sslContext = getSslContextFactory();
|
||||||
|
sslContext.ifPresent(ssl -> {
|
||||||
|
configuration.setSecureScheme("https");
|
||||||
|
configuration.setSecurePort(port);
|
||||||
|
|
||||||
|
SecureRequestCustomizer serverNameIdentifierCheckSkipper = new SecureRequestCustomizer();
|
||||||
|
serverNameIdentifierCheckSkipper.setSniHostCheck(false);
|
||||||
|
serverNameIdentifierCheckSkipper.setSniRequired(false);
|
||||||
|
configuration.addCustomizer(serverNameIdentifierCheckSkipper);
|
||||||
|
|
||||||
|
usingHttps = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
HttpConnectionFactory httpConnector = new HttpConnectionFactory(configuration);
|
||||||
|
|
||||||
|
HTTP2CServerConnectionFactory http2CConnector = new HTTP2CServerConnectionFactory(configuration);
|
||||||
|
http2CConnector.setConnectProtocolEnabled(true);
|
||||||
|
|
||||||
|
|
||||||
|
ServerConnector connector = sslContext
|
||||||
|
.map(sslContextFactory -> {
|
||||||
|
HTTP2ServerConnectionFactory http2Connector = new HTTP2ServerConnectionFactory(configuration);
|
||||||
|
http2Connector.setConnectProtocolEnabled(true);
|
||||||
|
ALPNServerConnectionFactory alpn = getAlpnServerConnectionFactory(httpConnector.getProtocol());
|
||||||
|
|
||||||
|
return new ServerConnector(webserver, sslContextFactory, alpn, httpConnector, http2Connector, http2CConnector);
|
||||||
|
})
|
||||||
|
.orElseGet(() -> {
|
||||||
|
webserverLogMessages.authenticationNotPossible();
|
||||||
|
return new ServerConnector(webserver, httpConnector, http2CConnector);
|
||||||
|
});
|
||||||
|
|
||||||
|
connector.setPort(port);
|
||||||
|
webserver.addConnector(connector);
|
||||||
|
|
||||||
|
if (usingHttps) {
|
||||||
|
webserver.setHandler(new HandlerList(new SecuredRedirectHandler(), jettyRequestHandler));
|
||||||
|
} else {
|
||||||
|
webserver.setHandler(jettyRequestHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
webserver.start();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new EnableException("Failed to start Jetty webserver: " + e.toString(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
webserverLogMessages.infoWebserverEnabled(getPort());
|
||||||
|
|
||||||
|
responseResolver.registerPages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ALPNServerConnectionFactory getAlpnServerConnectionFactory(String protocol) {
|
||||||
|
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
try {
|
||||||
|
ClassLoader pluginClassLoader = getClass().getClassLoader();
|
||||||
|
// Jetty uses Thread context classloader, so we need to change to plugin classloader where the ALPNProcessor is.
|
||||||
|
Thread.currentThread().setContextClassLoader(pluginClassLoader);
|
||||||
|
|
||||||
|
Class.forName("org.eclipse.jetty.alpn.java.server.JDK9ServerALPNProcessor");
|
||||||
|
// ALPN is protocol upgrade protocol required for upgrading http 1.1 connections to 2
|
||||||
|
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory("h2", "h2c", "http/1.1");
|
||||||
|
alpn.setDefaultProtocol(protocol);
|
||||||
|
|
||||||
|
return alpn;
|
||||||
|
} catch (ClassNotFoundException ignored) {
|
||||||
|
logger.warn("JDK9ServerALPNProcessor not found. ALPN is not available.");
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
Thread.currentThread().setContextClassLoader(contextClassLoader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<SslContextFactory.Server> getSslContextFactory() {
|
||||||
|
String keyStorePath = webserverConfiguration.getKeyStorePath();
|
||||||
|
if ("proxy".equals(keyStorePath)) {
|
||||||
|
webserverLogMessages.authenticationUsingProxy();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!new File(keyStorePath).exists()) {
|
||||||
|
webserverLogMessages.keystoreFileNotFound();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
String storepass = webserverConfiguration.getKeyStorePassword();
|
||||||
|
String keypass = webserverConfiguration.getKeyManagerPassword();
|
||||||
|
String alias = webserverConfiguration.getAlias();
|
||||||
|
|
||||||
|
if (keyStorePath.endsWith(".jks") && "DefaultPlanCert".equals(alias)) {
|
||||||
|
logger.warn("You're using self-signed PlanCert.jks certificate included with Plan.jar (Considered legacy since 5.5), it has expired and can cause issues.");
|
||||||
|
logger.info("Create new self-signed certificate using openssl:");
|
||||||
|
logger.info(" openssl req -x509 -newkey rsa:4096 -keyout myKey.pem -out cert.pem -days 3650");
|
||||||
|
logger.info(" openssl pkcs12 -export -out keyStore.p12 -inkey myKey.pem -in cert.pem -name alias -passout pass:<password> -passin pass:<password>");
|
||||||
|
logger.info("Then change config settings to match.");
|
||||||
|
logger.info(" SSL_certificate:");
|
||||||
|
logger.info(" KeyStore_path: keyStore.p12");
|
||||||
|
logger.info(" Key_pass: <password>");
|
||||||
|
logger.info(" Store_pass: <password>");
|
||||||
|
logger.info(" Alias: alias");
|
||||||
|
return legacyJettySSLContextLoader.load(keyStorePath, storepass, keypass, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||||
|
sslContextFactory.setSniRequired(false);
|
||||||
|
|
||||||
|
sslContextFactory.setKeyStorePath(keyStorePath);
|
||||||
|
sslContextFactory.setKeyStorePassword(storepass);
|
||||||
|
sslContextFactory.setKeyManagerPassword(keypass);
|
||||||
|
sslContextFactory.setCertAlias(alias);
|
||||||
|
|
||||||
|
return Optional.of(sslContextFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return webserver != null && (webserver.isStarting() || webserver.isStarted());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disable() {
|
||||||
|
try {
|
||||||
|
if (webserver != null) webserver.stop();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProtocol() {
|
||||||
|
return isUsingHTTPS() ? "https" : "http";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUsingHTTPS() {
|
||||||
|
return usingHttps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAuthRequired() {
|
||||||
|
return isUsingHTTPS() && webserverConfiguration.isAuthenticationEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.http;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.settings.locale.Locale;
|
||||||
|
import com.djrapitops.plan.settings.locale.lang.PluginLang;
|
||||||
|
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||||
|
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||||
|
import net.playeranalytics.plugin.server.PluginLogger;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class LegacyJettySSLContextLoader {
|
||||||
|
|
||||||
|
private final Locale locale;
|
||||||
|
private final PluginLogger logger;
|
||||||
|
private final ErrorLogger errorLogger;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public LegacyJettySSLContextLoader(Locale locale, PluginLogger logger, ErrorLogger errorLogger) {
|
||||||
|
this.locale = locale;
|
||||||
|
this.logger = logger;
|
||||||
|
this.errorLogger = errorLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<SslContextFactory.Server> load(String keyStorePath, String storepass, String keypass, String alias) {
|
||||||
|
String keyStoreKind = keyStorePath.endsWith(".p12") ? "PKCS12" : "JKS";
|
||||||
|
try (FileInputStream fIn = new FileInputStream(keyStorePath)) {
|
||||||
|
KeyStore keystore = KeyStore.getInstance(keyStoreKind);
|
||||||
|
|
||||||
|
keystore.load(fIn, storepass.toCharArray());
|
||||||
|
Certificate cert = keystore.getCertificate(alias);
|
||||||
|
|
||||||
|
if (cert == null) {
|
||||||
|
throw new IllegalStateException("Alias: '" + alias + "' was not found in file " + keyStorePath + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Certificate: " + cert.getType());
|
||||||
|
|
||||||
|
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
|
||||||
|
keyManagerFactory.init(keystore, keypass.toCharArray());
|
||||||
|
|
||||||
|
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
|
||||||
|
trustManagerFactory.init(keystore);
|
||||||
|
|
||||||
|
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
|
||||||
|
sslContext.init(keyManagerFactory.getKeyManagers(), null/*trustManagerFactory.getTrustManagers()*/, null);
|
||||||
|
|
||||||
|
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||||
|
sslContextFactory.setSslContext(sslContext);
|
||||||
|
|
||||||
|
return Optional.of(sslContextFactory);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
logger.error(e.getMessage());
|
||||||
|
} catch (KeyManagementException | NoSuchAlgorithmException e) {
|
||||||
|
logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_SSL_CONTEXT));
|
||||||
|
errorLogger.error(e, ErrorContext.builder().related(keyStoreKind).build());
|
||||||
|
} catch (EOFException e) {
|
||||||
|
logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_EMPTY_FILE));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_NO_CERT_FILE, keyStorePath));
|
||||||
|
logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_HTTP));
|
||||||
|
} catch (IOException e) {
|
||||||
|
errorLogger.error(e, ErrorContext.builder().related(keyStorePath).build());
|
||||||
|
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException e) {
|
||||||
|
logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_STORE_LOAD));
|
||||||
|
errorLogger.error(e, ErrorContext.builder()
|
||||||
|
.whatToDo("Make sure the Certificate settings are correct / You can try remaking the keystore without -passin or -passout parameters.")
|
||||||
|
.related(keyStorePath).build());
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.http;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.PassBruteForceGuard;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.ResponseResolver;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration;
|
||||||
|
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class RequestHandler {
|
||||||
|
|
||||||
|
private final WebserverConfiguration webserverConfiguration;
|
||||||
|
private final ResponseFactory responseFactory;
|
||||||
|
private final ResponseResolver responseResolver;
|
||||||
|
|
||||||
|
private final PassBruteForceGuard bruteForceGuard;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RequestHandler(WebserverConfiguration webserverConfiguration, ResponseFactory responseFactory, ResponseResolver responseResolver) {
|
||||||
|
this.webserverConfiguration = webserverConfiguration;
|
||||||
|
this.responseFactory = responseFactory;
|
||||||
|
this.responseResolver = responseResolver;
|
||||||
|
|
||||||
|
bruteForceGuard = new PassBruteForceGuard();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response getResponse(InternalRequest internalRequest) {
|
||||||
|
String accessAddress = internalRequest.getAccessAddress(webserverConfiguration);
|
||||||
|
|
||||||
|
if (bruteForceGuard.shouldPreventRequest(accessAddress)) {
|
||||||
|
return responseFactory.failedLoginAttempts403();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!webserverConfiguration.getAllowedIpList().isAllowed(accessAddress)) {
|
||||||
|
webserverConfiguration.getWebserverLogMessages()
|
||||||
|
.warnAboutWhitelistBlock(accessAddress, internalRequest.getRequestedURIString());
|
||||||
|
return responseFactory.ipWhitelist403(accessAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
Response response = attemptToResolve(internalRequest);
|
||||||
|
|
||||||
|
response.getHeaders().putIfAbsent("Access-Control-Allow-Origin", webserverConfiguration.getAllowedCorsOrigin());
|
||||||
|
response.getHeaders().putIfAbsent("Access-Control-Allow-Methods", "GET, OPTIONS");
|
||||||
|
response.getHeaders().putIfAbsent("Access-Control-Allow-Credentials", "true");
|
||||||
|
response.getHeaders().putIfAbsent("X-Robots-Tag", "noindex, nofollow");
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response attemptToResolve(InternalRequest internalRequest) {
|
||||||
|
String accessAddress = internalRequest.getAccessAddress(webserverConfiguration);
|
||||||
|
try {
|
||||||
|
Request request = internalRequest.toRequest();
|
||||||
|
Response response = protocolUpgradeResponse(request)
|
||||||
|
.orElseGet(() -> responseResolver.getResponse(request));
|
||||||
|
request.getUser().ifPresent(user -> processSuccessfulLogin(response.getCode(), accessAddress));
|
||||||
|
return response;
|
||||||
|
} catch (WebUserAuthException thrownByAuthentication) {
|
||||||
|
return processFailedAuthentication(internalRequest, accessAddress, thrownByAuthentication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Response> protocolUpgradeResponse(Request request) {
|
||||||
|
Optional<String> upgrade = request.getHeader(HttpHeader.UPGRADE.asString());
|
||||||
|
if (upgrade.isPresent()) {
|
||||||
|
String value = upgrade.get();
|
||||||
|
if ("h2c".equals(value) || "h2".equals(value)) {
|
||||||
|
return Optional.of(Response.builder()
|
||||||
|
.setStatus(101)
|
||||||
|
.setHeader("Connection", HttpHeader.UPGRADE.asString())
|
||||||
|
.setHeader(HttpHeader.UPGRADE.asString(), value)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response processFailedAuthentication(InternalRequest internalRequest, String accessAddress, WebUserAuthException thrownByAuthentication) {
|
||||||
|
FailReason failReason = thrownByAuthentication.getFailReason();
|
||||||
|
if (failReason == FailReason.USER_PASS_MISMATCH) {
|
||||||
|
return processWrongPassword(accessAddress, failReason);
|
||||||
|
} else {
|
||||||
|
String from = internalRequest.getRequestedURIString();
|
||||||
|
String directTo = StringUtils.startsWithAny(from, "/auth/", "/login") ? "/login" : "/login?from=." + from;
|
||||||
|
return Response.builder()
|
||||||
|
.redirectTo(directTo)
|
||||||
|
.setHeader("Set-Cookie", "auth=expired; Path=/; Max-Age=0; SameSite=Lax; Secure;")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response processWrongPassword(String accessAddress, FailReason failReason) {
|
||||||
|
bruteForceGuard.increaseAttemptCountOnFailedLogin(accessAddress);
|
||||||
|
if (bruteForceGuard.shouldPreventRequest(accessAddress)) {
|
||||||
|
return responseFactory.failedLoginAttempts403();
|
||||||
|
} else {
|
||||||
|
return responseFactory.badRequest(failReason.getReason(), "/auth/login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processSuccessfulLogin(int responseCode, String accessAddress) {
|
||||||
|
boolean successfulLogin = responseCode != 401;
|
||||||
|
boolean notForbidden = responseCode != 403;
|
||||||
|
if (successfulLogin && notForbidden) {
|
||||||
|
bruteForceGuard.resetAttemptCount(accessAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.http;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.request.URIPath;
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.request.URIQuery;
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.auth.AuthenticationExtractor;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.auth.Cookie;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration;
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import org.apache.commons.text.TextStringBuilder;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class SunInternalRequest implements InternalRequest {
|
||||||
|
|
||||||
|
private final HttpExchange exchange;
|
||||||
|
private final WebserverConfiguration webserverConfiguration;
|
||||||
|
private final AuthenticationExtractor authenticationExtractor;
|
||||||
|
|
||||||
|
public SunInternalRequest(HttpExchange exchange, WebserverConfiguration webserverConfiguration, AuthenticationExtractor authenticationExtractor) {
|
||||||
|
this.exchange = exchange;
|
||||||
|
this.webserverConfiguration = webserverConfiguration;
|
||||||
|
this.authenticationExtractor = authenticationExtractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAccessAddressFromSocketIp() {
|
||||||
|
return exchange.getRemoteAddress().getAddress().getHostAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAccessAddressFromHeader() {
|
||||||
|
return exchange.getRequestHeaders().getFirst("X-Forwarded-For");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRequestedURIString() {
|
||||||
|
return exchange.getRequestURI().toASCIIString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Request toRequest() {
|
||||||
|
return buildRequest(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Request buildRequest(HttpExchange exchange) {
|
||||||
|
String requestMethod = exchange.getRequestMethod();
|
||||||
|
URIPath path = new URIPath(exchange.getRequestURI().getPath());
|
||||||
|
URIQuery query = new URIQuery(exchange.getRequestURI().getRawQuery());
|
||||||
|
byte[] requestBody = readRequestBody(exchange);
|
||||||
|
WebUser user = getWebUser(webserverConfiguration, authenticationExtractor);
|
||||||
|
Map<String, String> headers = getRequestHeaders(exchange);
|
||||||
|
return new Request(requestMethod, path, query, user, headers, requestBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readRequestBody(HttpExchange exchange) {
|
||||||
|
try (ByteArrayOutputStream buf = new ByteArrayOutputStream(512)) {
|
||||||
|
int b;
|
||||||
|
while ((b = exchange.getRequestBody().read()) != -1) {
|
||||||
|
buf.write((byte) b);
|
||||||
|
}
|
||||||
|
return buf.toByteArray();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
// requestBody stays empty
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> getRequestHeaders(HttpExchange exchange) {
|
||||||
|
Map<String, String> headers = new HashMap<>();
|
||||||
|
for (Map.Entry<String, List<String>> e : exchange.getRequestHeaders().entrySet()) {
|
||||||
|
List<String> value = e.getValue();
|
||||||
|
headers.put(e.getKey(), new TextStringBuilder().appendWithSeparators(value, ";").build());
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Cookie> getCookies() {
|
||||||
|
List<String> textCookies = exchange.getRequestHeaders().get("Cookie");
|
||||||
|
List<Cookie> cookies = new ArrayList<>();
|
||||||
|
if (textCookies != null && !textCookies.isEmpty()) {
|
||||||
|
String[] separated = new TextStringBuilder().appendWithSeparators(textCookies, ";").build().split(";");
|
||||||
|
for (String textCookie : separated) {
|
||||||
|
cookies.add(new Cookie(textCookie));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.http;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.Addresses;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.ResponseResolver;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.auth.AuthenticationExtractor;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration;
|
||||||
|
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||||
|
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||||
|
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||||
|
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
import net.playeranalytics.plugin.server.PluginLogger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpHandler for WebServer request management.
|
||||||
|
*
|
||||||
|
* @author AuroraLS3
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class SunRequestHandler implements HttpHandler {
|
||||||
|
|
||||||
|
private final PlanConfig config;
|
||||||
|
private final Addresses addresses;
|
||||||
|
private final WebserverConfiguration webserverConfiguration;
|
||||||
|
private final AuthenticationExtractor authenticationExtractor;
|
||||||
|
private final ResponseResolver responseResolver;
|
||||||
|
private final RequestHandler requestHandler;
|
||||||
|
private final PluginLogger logger;
|
||||||
|
private final ErrorLogger errorLogger;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SunRequestHandler(
|
||||||
|
PlanConfig config,
|
||||||
|
Addresses addresses,
|
||||||
|
WebserverConfiguration webserverConfiguration,
|
||||||
|
AuthenticationExtractor authenticationExtractor,
|
||||||
|
ResponseResolver responseResolver,
|
||||||
|
RequestHandler requestHandler,
|
||||||
|
PluginLogger logger,
|
||||||
|
ErrorLogger errorLogger
|
||||||
|
) {
|
||||||
|
this.config = config;
|
||||||
|
this.addresses = addresses;
|
||||||
|
this.webserverConfiguration = webserverConfiguration;
|
||||||
|
this.authenticationExtractor = authenticationExtractor;
|
||||||
|
this.responseResolver = responseResolver;
|
||||||
|
this.requestHandler = requestHandler;
|
||||||
|
this.logger = logger;
|
||||||
|
this.errorLogger = errorLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(HttpExchange exchange) {
|
||||||
|
try {
|
||||||
|
InternalRequest internalRequest = new SunInternalRequest(exchange, webserverConfiguration, authenticationExtractor);
|
||||||
|
Response response = requestHandler.getResponse(internalRequest);
|
||||||
|
new SunResponseSender(addresses, exchange, response).send();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (config.isTrue(PluginSettings.DEV_MODE)) {
|
||||||
|
logger.warn("THIS ERROR IS ONLY LOGGED IN DEV MODE:");
|
||||||
|
errorLogger.warn(e, ErrorContext.builder()
|
||||||
|
.whatToDo("THIS ERROR IS ONLY LOGGED IN DEV MODE")
|
||||||
|
.related(exchange.getRequestMethod(), exchange.getRemoteAddress(), exchange.getRequestHeaders(), exchange.getResponseHeaders(), exchange.getRequestURI())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
exchange.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseResolver getResponseResolver() {
|
||||||
|
return responseResolver;
|
||||||
|
}
|
||||||
|
}
|
@ -14,9 +14,10 @@
|
|||||||
* You should have received a copy of the GNU Lesser General Public License
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.djrapitops.plan.delivery.webserver;
|
package com.djrapitops.plan.delivery.webserver.http;
|
||||||
|
|
||||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.Addresses;
|
||||||
import com.sun.net.httpserver.Headers;
|
import com.sun.net.httpserver.Headers;
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
|
||||||
@ -31,13 +32,13 @@ import java.util.zip.GZIPOutputStream;
|
|||||||
*
|
*
|
||||||
* @author AuroraLS3
|
* @author AuroraLS3
|
||||||
*/
|
*/
|
||||||
public class ResponseSender {
|
public class SunResponseSender {
|
||||||
|
|
||||||
private final Addresses addresses;
|
private final Addresses addresses;
|
||||||
private final HttpExchange exchange;
|
private final HttpExchange exchange;
|
||||||
private final Response response;
|
private final Response response;
|
||||||
|
|
||||||
public ResponseSender(Addresses addresses, HttpExchange exchange, Response response) {
|
public SunResponseSender(Addresses addresses, HttpExchange exchange, Response response) {
|
||||||
this.addresses = addresses;
|
this.addresses = addresses;
|
||||||
this.exchange = exchange;
|
this.exchange = exchange;
|
||||||
this.response = response;
|
this.response = response;
|
@ -14,9 +14,9 @@
|
|||||||
* You should have received a copy of the GNU Lesser General Public License
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.djrapitops.plan.delivery.webserver;
|
package com.djrapitops.plan.delivery.webserver.http;
|
||||||
|
|
||||||
import com.djrapitops.plan.SubSystem;
|
import com.djrapitops.plan.delivery.webserver.Addresses;
|
||||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||||
@ -49,14 +49,14 @@ import java.util.concurrent.*;
|
|||||||
* @author AuroraLS3
|
* @author AuroraLS3
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class WebServer implements SubSystem {
|
public class SunWebServer implements WebServer {
|
||||||
|
|
||||||
private final Locale locale;
|
private final Locale locale;
|
||||||
private final PlanFiles files;
|
private final PlanFiles files;
|
||||||
private final PlanConfig config;
|
private final PlanConfig config;
|
||||||
|
|
||||||
private final Addresses addresses;
|
private final Addresses addresses;
|
||||||
private final RequestHandler requestHandler;
|
private final SunRequestHandler requestHandler;
|
||||||
|
|
||||||
private final PluginLogger logger;
|
private final PluginLogger logger;
|
||||||
private final ErrorLogger errorLogger;
|
private final ErrorLogger errorLogger;
|
||||||
@ -68,14 +68,14 @@ public class WebServer implements SubSystem {
|
|||||||
private boolean usingHttps = false;
|
private boolean usingHttps = false;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public WebServer(
|
public SunWebServer(
|
||||||
Locale locale,
|
Locale locale,
|
||||||
PlanFiles files,
|
PlanFiles files,
|
||||||
PlanConfig config,
|
PlanConfig config,
|
||||||
Addresses addresses,
|
Addresses addresses,
|
||||||
PluginLogger logger,
|
PluginLogger logger,
|
||||||
ErrorLogger errorLogger,
|
ErrorLogger errorLogger,
|
||||||
RequestHandler requestHandler
|
SunRequestHandler requestHandler
|
||||||
) {
|
) {
|
||||||
this.locale = locale;
|
this.locale = locale;
|
||||||
this.files = files;
|
this.files = files;
|
||||||
@ -258,6 +258,7 @@ public class WebServer implements SubSystem {
|
|||||||
/**
|
/**
|
||||||
* @return if the WebServer is enabled
|
* @return if the WebServer is enabled
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
@ -291,18 +292,22 @@ public class WebServer implements SubSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getProtocol() {
|
public String getProtocol() {
|
||||||
return usingHttps ? "https" : "http";
|
return usingHttps ? "https" : "http";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isUsingHTTPS() {
|
public boolean isUsingHTTPS() {
|
||||||
return usingHttps;
|
return usingHttps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isAuthRequired() {
|
public boolean isAuthRequired() {
|
||||||
return isUsingHTTPS() && config.isFalse(WebserverSettings.DISABLED_AUTHENTICATION);
|
return isUsingHTTPS() && config.isFalse(WebserverSettings.DISABLED_AUTHENTICATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getPort() {
|
public int getPort() {
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.webserver.http;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.SubSystem;
|
||||||
|
|
||||||
|
public interface WebServer extends SubSystem {
|
||||||
|
@Override
|
||||||
|
void enable();
|
||||||
|
|
||||||
|
boolean isEnabled();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void disable();
|
||||||
|
|
||||||
|
String getProtocol();
|
||||||
|
|
||||||
|
boolean isUsingHTTPS();
|
||||||
|
|
||||||
|
boolean isAuthRequired();
|
||||||
|
|
||||||
|
int getPort();
|
||||||
|
}
|
@ -22,8 +22,8 @@ import com.djrapitops.plan.delivery.web.resolver.Response;
|
|||||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||||
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
||||||
import com.djrapitops.plan.delivery.webserver.WebServer;
|
|
||||||
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
||||||
import com.djrapitops.plan.identification.Server;
|
import com.djrapitops.plan.identification.Server;
|
||||||
import com.djrapitops.plan.identification.ServerInfo;
|
import com.djrapitops.plan.identification.ServerInfo;
|
||||||
|
@ -21,7 +21,7 @@ import com.djrapitops.plan.delivery.web.resolver.Response;
|
|||||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||||
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
||||||
import com.djrapitops.plan.delivery.webserver.WebServer;
|
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
@ -21,7 +21,7 @@ import com.djrapitops.plan.delivery.web.resolver.Response;
|
|||||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||||
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
||||||
import com.djrapitops.plan.delivery.webserver.WebServer;
|
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
@ -20,7 +20,7 @@ import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver;
|
|||||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||||
import com.djrapitops.plan.delivery.webserver.WebServer;
|
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||||
import com.djrapitops.plan.utilities.java.Maps;
|
import com.djrapitops.plan.utilities.java.Maps;
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ import com.djrapitops.plan.DataSvc;
|
|||||||
import com.djrapitops.plan.delivery.webserver.cache.JSONFileStorage;
|
import com.djrapitops.plan.delivery.webserver.cache.JSONFileStorage;
|
||||||
import com.djrapitops.plan.delivery.webserver.cache.JSONMemoryStorageShim;
|
import com.djrapitops.plan.delivery.webserver.cache.JSONMemoryStorageShim;
|
||||||
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
|
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.JettyWebserver;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||||
import com.djrapitops.plan.gathering.importing.importers.Importer;
|
import com.djrapitops.plan.gathering.importing.importers.Importer;
|
||||||
import com.djrapitops.plan.settings.config.ExtensionSettings;
|
import com.djrapitops.plan.settings.config.ExtensionSettings;
|
||||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||||
@ -50,6 +52,12 @@ import java.util.function.Predicate;
|
|||||||
@Module
|
@Module
|
||||||
public class SystemObjectProvidingModule {
|
public class SystemObjectProvidingModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
WebServer provideWebserver(JettyWebserver webServer) {
|
||||||
|
return webServer;
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
Gson provideGson() {
|
Gson provideGson() {
|
||||||
|
@ -37,7 +37,7 @@ public class WebserverSettings {
|
|||||||
public static final Setting<String> CERTIFICATE_KEYPASS = new StringSetting("Webserver.Security.SSL_certificate.Key_pass");
|
public static final Setting<String> CERTIFICATE_KEYPASS = new StringSetting("Webserver.Security.SSL_certificate.Key_pass");
|
||||||
public static final Setting<String> CERTIFICATE_STOREPASS = new StringSetting("Webserver.Security.SSL_certificate.Store_pass");
|
public static final Setting<String> CERTIFICATE_STOREPASS = new StringSetting("Webserver.Security.SSL_certificate.Store_pass");
|
||||||
public static final Setting<String> CERTIFICATE_ALIAS = new StringSetting("Webserver.Security.SSL_certificate.Alias");
|
public static final Setting<String> CERTIFICATE_ALIAS = new StringSetting("Webserver.Security.SSL_certificate.Alias");
|
||||||
public static final Setting<Boolean> IP_WHITELIST_X_FORWARDED = new BooleanSetting("Webserver.Security.Use_X-Forwarded-For_Header");
|
public static final Setting<Boolean> IP_USE_X_FORWARDED_FOR = new BooleanSetting("Webserver.Security.Use_X-Forwarded-For_Header");
|
||||||
public static final Setting<Boolean> IP_WHITELIST = new BooleanSetting("Webserver.Security.IP_whitelist");
|
public static final Setting<Boolean> IP_WHITELIST = new BooleanSetting("Webserver.Security.IP_whitelist");
|
||||||
public static final Setting<List<String>> WHITELIST = new StringListSetting("Webserver.Security.IP_whitelist.Whitelist");
|
public static final Setting<List<String>> WHITELIST = new StringListSetting("Webserver.Security.IP_whitelist.Whitelist");
|
||||||
public static final Setting<Boolean> DISABLED = new BooleanSetting("Webserver.Disable_Webserver");
|
public static final Setting<Boolean> DISABLED = new BooleanSetting("Webserver.Disable_Webserver");
|
||||||
|
@ -115,6 +115,9 @@ class AccessControlTest {
|
|||||||
.orElseThrow(AssertionError::new);
|
.orElseThrow(AssertionError::new);
|
||||||
caller.updatePlayerData(TestConstants.PLAYER_ONE_UUID, TestConstants.PLAYER_ONE_NAME);
|
caller.updatePlayerData(TestConstants.PLAYER_ONE_UUID, TestConstants.PLAYER_ONE_NAME);
|
||||||
|
|
||||||
|
assertTrue(system.getWebServerSystem().getWebServer().isUsingHTTPS());
|
||||||
|
assertTrue(system.getWebServerSystem().getWebServer().isAuthRequired());
|
||||||
|
|
||||||
address = "https://localhost:" + TEST_PORT_NUMBER;
|
address = "https://localhost:" + TEST_PORT_NUMBER;
|
||||||
cookieLevel0 = login(address, userLevel0.getUsername());
|
cookieLevel0 = login(address, userLevel0.getUsername());
|
||||||
cookieLevel1 = login(address, userLevel1.getUsername());
|
cookieLevel1 = login(address, userLevel1.getUsername());
|
||||||
|
@ -18,6 +18,7 @@ package com.djrapitops.plan.delivery.webserver;
|
|||||||
|
|
||||||
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
||||||
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
|
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||||
import com.djrapitops.plan.exceptions.connection.ForbiddenException;
|
import com.djrapitops.plan.exceptions.connection.ForbiddenException;
|
||||||
import com.djrapitops.plan.exceptions.connection.WebException;
|
import com.djrapitops.plan.exceptions.connection.WebException;
|
||||||
import org.apache.commons.compress.utils.IOUtils;
|
import org.apache.commons.compress.utils.IOUtils;
|
||||||
|
@ -18,6 +18,7 @@ package com.djrapitops.plan.delivery.webserver;
|
|||||||
|
|
||||||
import com.djrapitops.plan.PlanSystem;
|
import com.djrapitops.plan.PlanSystem;
|
||||||
import com.djrapitops.plan.delivery.domain.auth.User;
|
import com.djrapitops.plan.delivery.domain.auth.User;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||||
import com.djrapitops.plan.storage.database.transactions.commands.RegisterWebUserTransaction;
|
import com.djrapitops.plan.storage.database.transactions.commands.RegisterWebUserTransaction;
|
||||||
|
@ -18,6 +18,7 @@ package com.djrapitops.plan.delivery.webserver;
|
|||||||
|
|
||||||
import com.djrapitops.plan.PlanSystem;
|
import com.djrapitops.plan.PlanSystem;
|
||||||
import com.djrapitops.plan.delivery.domain.auth.User;
|
import com.djrapitops.plan.delivery.domain.auth.User;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||||
import com.djrapitops.plan.settings.config.changes.ConfigUpdater;
|
import com.djrapitops.plan.settings.config.changes.ConfigUpdater;
|
||||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
package utilities.dagger;
|
package utilities.dagger;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.JettyWebserver;
|
||||||
|
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||||
import com.djrapitops.plan.settings.config.ExtensionSettings;
|
import com.djrapitops.plan.settings.config.ExtensionSettings;
|
||||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||||
import com.djrapitops.plan.settings.locale.Locale;
|
import com.djrapitops.plan.settings.locale.Locale;
|
||||||
@ -38,6 +40,12 @@ import java.util.function.Predicate;
|
|||||||
@Module
|
@Module
|
||||||
public class TestSystemObjectProvidingModule {
|
public class TestSystemObjectProvidingModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
WebServer provideWebserver(JettyWebserver webServer) {
|
||||||
|
return webServer;
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
Gson provideGson() {
|
Gson provideGson() {
|
||||||
|
Loading…
Reference in New Issue
Block a user