Made resource resolution more flexible

- API 5.1-R0.2: Added ResolverService#getResolvers method
- /players/ now redirects to /players
- All error pages now have proper css

Affects issues:
- Fixed #1378
This commit is contained in:
Risto Lahtela 2020-03-28 13:00:34 +02:00
parent 24fc3f665f
commit 45d6bfb840
10 changed files with 86 additions and 30 deletions

View File

@ -7,7 +7,7 @@ dependencies {
compileOnly "com.google.code.gson:gson:$gsonVersion" compileOnly "com.google.code.gson:gson:$gsonVersion"
} }
ext.apiVersion = '5.1-R0.1' ext.apiVersion = '5.1-R0.2'
bintray { bintray {
user = System.getenv('BINTRAY_USER') user = System.getenv('BINTRAY_USER')

View File

@ -67,6 +67,10 @@ enum Capability {
* {@link com.djrapitops.plan.delivery.web.ResolverService} * {@link com.djrapitops.plan.delivery.web.ResolverService}
*/ */
PAGE_EXTENSION_RESOLVERS, PAGE_EXTENSION_RESOLVERS,
/**
* {@link com.djrapitops.plan.delivery.web.ResolverService#getResolvers(String)}
*/
PAGE_EXTENSION_RESOLVERS_LIST,
/** /**
* {@link com.djrapitops.plan.delivery.web.ResourceService} * {@link com.djrapitops.plan.delivery.web.ResourceService}
*/ */

View File

@ -18,6 +18,7 @@ package com.djrapitops.plan.delivery.web;
import com.djrapitops.plan.delivery.web.resolver.Resolver; import com.djrapitops.plan.delivery.web.resolver.Resolver;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -74,6 +75,18 @@ public interface ResolverService {
*/ */
Optional<Resolver> getResolver(String target); Optional<Resolver> getResolver(String target);
/**
* Obtain all Resolvers that match the target.
* <p>
* If first returns Optional.empty next one should be used.
* <p>
* Requires Capability PAGE_EXTENSION_RESOLVERS_LIST.
*
* @param target "/example/target"
* @return List of Resolvers if registered or empty list.
*/
List<Resolver> getResolvers(String target);
class Holder { class Holder {
static ResolverService service; static ResolverService service;

View File

@ -68,6 +68,18 @@ public class ResolverSvc implements ResolverService {
return Optional.empty(); return Optional.empty();
} }
@Override
public List<Resolver> getResolvers(String target) {
List<Resolver> resolvers = new ArrayList<>();
for (Container container : basicResolvers) {
if (container.matcher.test(target)) resolvers.add(container.resolver);
}
for (Container container : regexResolvers) {
if (container.matcher.test(target)) resolvers.add(container.resolver);
}
return resolvers;
}
public Optional<String> getPluginInChargeOf(String target) { public Optional<String> getPluginInChargeOf(String target) {
for (Container container : basicResolvers) { for (Container container : basicResolvers) {
if (container.matcher.test(target)) return Optional.of(container.plugin); if (container.matcher.test(target)) return Optional.of(container.plugin);

View File

@ -35,6 +35,7 @@ import dagger.Lazy;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -100,7 +101,7 @@ public class ResponseResolver {
resolverService.registerResolver(plugin, "/network", serverPageResolver); resolverService.registerResolver(plugin, "/network", serverPageResolver);
resolverService.registerResolver(plugin, "/server", serverPageResolver); resolverService.registerResolver(plugin, "/server", serverPageResolver);
resolverService.registerResolverForMatches(plugin, Pattern.compile("^/$"), rootPageResolver); resolverService.registerResolverForMatches(plugin, Pattern.compile("^/$"), rootPageResolver);
resolverService.registerResolverForMatches(plugin, Pattern.compile("^/(vendor|css|js|img)/.*"), staticResourceResolver); resolverService.registerResolverForMatches(plugin, Pattern.compile("^.*/(vendor|css|js|img)/.*"), staticResourceResolver);
resolverService.registerResolver(plugin, "/v1", rootJSONResolver.getResolver()); resolverService.registerResolver(plugin, "/v1", rootJSONResolver.getResolver());
} }
@ -136,30 +137,33 @@ public class ResponseResolver {
Optional<Authentication> authentication = internalRequest.getAuth(); Optional<Authentication> authentication = internalRequest.getAuth();
Optional<Resolver> foundResolver = resolverService.getResolver(internalRequest.getPath().asString()); List<Resolver> foundResolvers = resolverService.getResolvers(internalRequest.getPath().asString());
if (!foundResolver.isPresent()) return responseFactory.pageNotFound404(); if (foundResolvers.isEmpty()) return responseFactory.pageNotFound404();
Resolver resolver = foundResolver.get(); for (Resolver resolver : foundResolvers) {
Request request = internalRequest.toAPIRequest();
if (resolver.requiresAuth(request)) {
// Get required auth
boolean isAuthRequired = webServer.get().isAuthRequired();
if (isAuthRequired && !authentication.isPresent()) {
if (webServer.get().isUsingHTTPS()) {
return responseFactory.basicAuth();
} else {
return responseFactory.forbidden403();
}
}
Request request = internalRequest.toAPIRequest(); if (!isAuthRequired || resolver.canAccess(request)) {
if (resolver.requiresAuth(request)) { Optional<Response> resolved = resolver.resolve(request);
// Get required auth if (resolved.isPresent()) return resolved.get();
boolean isAuthRequired = webServer.get().isAuthRequired();
if (isAuthRequired && !authentication.isPresent()) {
if (webServer.get().isUsingHTTPS()) {
return responseFactory.basicAuth();
} else { } else {
return responseFactory.forbidden403(); return responseFactory.forbidden403();
} }
}
if (!isAuthRequired || resolver.canAccess(request)) {
return resolver.resolve(request).orElseGet(responseFactory::pageNotFound404);
} else { } else {
return responseFactory.forbidden403(); Optional<Response> resolved = resolver.resolve(request);
if (resolved.isPresent()) return resolved.get();
} }
} else {
return resolver.resolve(request).orElseGet(responseFactory::pageNotFound404);
} }
return responseFactory.pageNotFound404();
} }
} }

View File

@ -16,6 +16,7 @@
*/ */
package com.djrapitops.plan.delivery.webserver.resolver; package com.djrapitops.plan.delivery.webserver.resolver;
import com.djrapitops.plan.delivery.rendering.html.Html;
import com.djrapitops.plan.delivery.web.resolver.Resolver; import com.djrapitops.plan.delivery.web.resolver.Resolver;
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;
@ -23,6 +24,7 @@ import com.djrapitops.plan.delivery.web.resolver.request.URIPath;
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.identification.UUIDUtility; import com.djrapitops.plan.identification.UUIDUtility;
import org.apache.commons.lang3.StringUtils;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -60,17 +62,26 @@ public class PlayerPageResolver implements Resolver {
@Override @Override
public Optional<Response> resolve(Request request) { public Optional<Response> resolve(Request request) {
URIPath path = request.getPath(); URIPath path = request.getPath();
Optional<String> part = path.getPart(1); if (StringUtils.containsAny(path.asString(), "/vendor/", "/js/", "/css/", "/img/")) {
if (!part.isPresent()) return Optional.empty(); return Optional.empty();
}
return path.getPart(1)
.map(playerName -> getResponse(request.getPath(), playerName));
}
String playerName = part.get(); private Response getResponse(URIPath path, String playerName) {
UUID playerUUID = uuidUtility.getUUIDOf(playerName); UUID playerUUID = uuidUtility.getUUIDOf(playerName);
if (playerUUID == null) return Optional.of(responseFactory.uuidNotFound404()); if (playerUUID == null) return responseFactory.uuidNotFound404();
boolean raw = path.getPart(2).map("raw"::equalsIgnoreCase).orElse(false); boolean raw = path.getPart(2).map("raw"::equalsIgnoreCase).orElse(false);
return Optional.of( if (raw) {
raw ? responseFactory.rawPlayerPageResponse(playerUUID) return responseFactory.rawPlayerPageResponse(playerUUID);
: responseFactory.playerPageResponse(playerUUID) }
);
if (path.getPart(2).isPresent()) {
// Redirect /player/Name/ to /player/Name
return responseFactory.redirectResponse("../" + Html.encodeToURL(playerName));
}
return responseFactory.playerPageResponse(playerUUID);
} }
} }

View File

@ -49,6 +49,8 @@ public class PlayersPageResolver implements Resolver {
@Override @Override
public Optional<Response> resolve(Request request) { public Optional<Response> resolve(Request request) {
// Redirect /players/ to /players
if (request.getPath().getPart(1).isPresent()) return Optional.of(responseFactory.redirectResponse("/players"));
return Optional.of(responseFactory.playersPageResponse()); return Optional.of(responseFactory.playersPageResponse());
} }
} }

View File

@ -19,6 +19,7 @@ package com.djrapitops.plan.delivery.webserver.resolver;
import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver; 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.URIPath;
import com.djrapitops.plan.delivery.webserver.ResponseFactory; import com.djrapitops.plan.delivery.webserver.ResponseFactory;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -47,7 +48,7 @@ public class StaticResourceResolver implements NoAuthResolver {
} }
private Response getResponse(Request request) { private Response getResponse(Request request) {
String resource = request.getPath().asString().substring(1); String resource = getPath(request).asString().substring(1);
if (resource.endsWith(".css")) { if (resource.endsWith(".css")) {
return responseFactory.cssResponse(resource); return responseFactory.cssResponse(resource);
} }
@ -62,4 +63,13 @@ public class StaticResourceResolver implements NoAuthResolver {
} }
return null; return null;
} }
private URIPath getPath(Request request) {
URIPath path = request.getPath();
// Remove everything before /vendor /css /js or /img
while (!path.getPart(0).map(part -> part.matches("(vendor|css|js|img)")).orElse(true)) {
path = path.omitFirst();
}
return path;
}
} }

View File

@ -24,7 +24,7 @@ package com.djrapitops.plan.settings.locale.lang;
public enum ErrorPageLang implements Lang { public enum ErrorPageLang implements Lang {
UUID_404("Player UUID was not found in the database."), UUID_404("Player UUID was not found in the database."),
NO_SERVERS_404("No Servers online to perform the request."), NO_SERVERS_404("No Servers online to perform the request."),
NOT_PLAYED_404("Player has not played on this server."), NOT_PLAYED_404("Plan has not seen this player."),
UNKNOWN_PAGE_404("Make sure you're accessing a link given by a command, Examples:</p><p>/player/PlayerName<br>/server/ServerName</p>"), UNKNOWN_PAGE_404("Make sure you're accessing a link given by a command, Examples:</p><p>/player/PlayerName<br>/server/ServerName</p>"),
UNAUTHORIZED_401("Unauthorized"), UNAUTHORIZED_401("Unauthorized"),
AUTHENTICATION_FAILED_401("Authentication Failed."), AUTHENTICATION_FAILED_401("Authentication Failed."),

View File

@ -295,7 +295,7 @@ HTML ERRORS - AUTHENTICATION_FAILED_401 || Authentication Failed.
HTML ERRORS - FORBIDDEN_403 || Forbidden HTML ERRORS - FORBIDDEN_403 || Forbidden
HTML ERRORS - NO_SERVERS_404 || No Servers online to perform the request. HTML ERRORS - NO_SERVERS_404 || No Servers online to perform the request.
HTML ERRORS - NOT_FOUND_404 || Not Found HTML ERRORS - NOT_FOUND_404 || Not Found
HTML ERRORS - NOT_PLAYED_404 || Player has not played on this server. HTML ERRORS - NOT_PLAYED_404 || Plan has not seen this player.
HTML ERRORS - PAGE_NOT_FOUND_404 || Page does not exist. HTML ERRORS - PAGE_NOT_FOUND_404 || Page does not exist.
HTML ERRORS - UNAUTHORIZED_401 || Unauthorized HTML ERRORS - UNAUTHORIZED_401 || Unauthorized
HTML ERRORS - UNKNOWN_PAGE_404 || Make sure you're accessing a link given by a command, Examples:</p><p>/player/PlayerName<br>/server/ServerName</p> HTML ERRORS - UNKNOWN_PAGE_404 || Make sure you're accessing a link given by a command, Examples:</p><p>/player/PlayerName<br>/server/ServerName</p>