148 lines
6.8 KiB
Java
148 lines
6.8 KiB
Java
/*
|
|
* 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.RateLimitGuard;
|
|
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 com.djrapitops.plan.utilities.dev.Untrusted;
|
|
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;
|
|
private final RateLimitGuard rateLimitGuard;
|
|
private final AccessLogger accessLogger;
|
|
|
|
@Inject
|
|
public RequestHandler(WebserverConfiguration webserverConfiguration, ResponseFactory responseFactory, ResponseResolver responseResolver, RateLimitGuard rateLimitGuard, AccessLogger accessLogger) {
|
|
this.webserverConfiguration = webserverConfiguration;
|
|
this.responseFactory = responseFactory;
|
|
this.responseResolver = responseResolver;
|
|
this.rateLimitGuard = rateLimitGuard;
|
|
this.accessLogger = accessLogger;
|
|
|
|
bruteForceGuard = new PassBruteForceGuard();
|
|
}
|
|
|
|
public Response getResponse(InternalRequest internalRequest) {
|
|
@Untrusted String accessAddress = internalRequest.getAccessAddress(webserverConfiguration);
|
|
|
|
Response response;
|
|
@Untrusted Request request = null;
|
|
if (bruteForceGuard.shouldPreventRequest(accessAddress)) {
|
|
response = responseFactory.failedLoginAttempts403();
|
|
} else if (!webserverConfiguration.getAllowedIpList().isAllowed(accessAddress)) {
|
|
webserverConfiguration.getWebserverLogMessages()
|
|
.warnAboutWhitelistBlock(accessAddress, internalRequest.getRequestedURIString());
|
|
response = responseFactory.ipWhitelist403(accessAddress);
|
|
} else {
|
|
String requestedPath = internalRequest.getRequestedPath();
|
|
rateLimitGuard.increaseAttemptCount(requestedPath, accessAddress);
|
|
if (rateLimitGuard.shouldPreventRequest(accessAddress)) {
|
|
response = responseFactory.failedRateLimit403();
|
|
} else {
|
|
try {
|
|
request = internalRequest.toRequest();
|
|
response = attemptToResolve(request, accessAddress);
|
|
} catch (WebUserAuthException thrownByAuthentication) {
|
|
response = processFailedAuthentication(internalRequest, accessAddress, thrownByAuthentication);
|
|
}
|
|
}
|
|
}
|
|
|
|
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");
|
|
|
|
accessLogger.log(internalRequest, request, response);
|
|
|
|
return response;
|
|
}
|
|
|
|
private Response attemptToResolve(@Untrusted Request request, @Untrusted String accessAddress) {
|
|
Response response = protocolUpgradeResponse(request)
|
|
.orElseGet(() -> responseResolver.getResponse(request));
|
|
request.getUser().ifPresent(user -> processSuccessfulLogin(response.getCode(), accessAddress));
|
|
return response;
|
|
}
|
|
|
|
private Optional<Response> protocolUpgradeResponse(@Untrusted Request request) {
|
|
@Untrusted Optional<String> upgrade = request.getHeader(HttpHeader.UPGRADE.asString());
|
|
if (upgrade.isPresent()) {
|
|
@Untrusted 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, @Untrusted String accessAddress, WebUserAuthException thrownByAuthentication) {
|
|
FailReason failReason = thrownByAuthentication.getFailReason();
|
|
if (failReason == FailReason.USER_PASS_MISMATCH) {
|
|
return processWrongPassword(accessAddress, failReason);
|
|
} else {
|
|
@Untrusted 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(@Untrusted 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, @Untrusted String accessAddress) {
|
|
boolean successfulLogin = responseCode != 401;
|
|
boolean notForbidden = responseCode != 403;
|
|
if (successfulLogin && notForbidden) {
|
|
bruteForceGuard.resetAttemptCount(accessAddress);
|
|
}
|
|
}
|
|
|
|
}
|