diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/QueryJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/QueryJSONResolver.java index 23b82889e..f8d117484 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/QueryJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/QueryJSONResolver.java @@ -139,12 +139,22 @@ public class QueryJSONResolver implements Resolver { } private Response getResponse(@Untrusted Request request) { - Optional cachedResult = checkForCachedResult(request); + Optional user = request.getUser(); + boolean canAccessCache = user.map(u -> u.hasPermission(WebPermission.ACCESS_QUERY)).orElse(true); + Optional cachedResult = canAccessCache ? checkForCachedResult(request) : Optional.empty(); if (cachedResult.isPresent()) return cachedResult.get(); InputQueryDto inputQuery = parseInputQuery(request); @Untrusted List queries = inputQuery.getFilters(); + // Check user has permission for the filter if login is enabled. + if (user.isPresent()) { + Optional errorResponse = checkFilterPermissions(queries, user.get()); + if (errorResponse.isPresent()) { + return errorResponse.get(); + } + } + Filter.Result result = filters.apply(queries); List resultPath = result.getInverseResultPath(); Collections.reverse(resultPath); @@ -152,6 +162,47 @@ public class QueryJSONResolver implements Resolver { return buildAndStoreResponse(inputQuery, result, resultPath); } + private Optional checkFilterPermissions(List queries, WebUser user) { + for (InputFilterDto filter : queries) { + @Untrusted String filterKind = filter.getKind(); + if (!isFilterAllowed(user, filterKind)) { + return Optional.of(Response.builder() + .setStatus(403) + .setJSONContent("{\"error\": \"You don't have permission to use one of the given filters\"}") + .build()); + } + } + return Optional.empty(); + } + + private boolean isFilterAllowed(WebUser user, @Untrusted String filterKind) { + for (WebPermission allowed : getAllowingPermissions(filterKind)) { + if (user.hasPermission(allowed)) { + return true; + } + } + return false; + } + + private WebPermission[] getAllowingPermissions(@Untrusted String filterKind) { + switch (filterKind) { + case "playedBetween": + return new WebPermission[]{ + WebPermission.ACCESS_QUERY, + WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_CALENDAR, + WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_CALENDAR + }; + case "geolocations": + return new WebPermission[]{ + WebPermission.ACCESS_QUERY, + WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP, + WebPermission.PAGE_SERVER_GEOLOCATIONS_MAP + }; + default: + return new WebPermission[]{WebPermission.ACCESS_QUERY}; + } + } + private InputQueryDto parseInputQuery(@Untrusted Request request) { if (request.getRequestBody().length == 0) { return parseInputQueryFromQueryParams(request); diff --git a/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java b/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java index df9ae6071..60ab359a9 100644 --- a/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java +++ b/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java @@ -70,6 +70,7 @@ import static org.junit.jupiter.api.Assertions.*; class AccessControlTest { private static final int TEST_PORT_NUMBER = RandomData.randomInt(9005, 9500); + private static final String QUERY_VIEW_SIMPLE = "%7B%22afterDate%22%3A%2201%2F01%2F1970%22%2C%22afterTime%22%3A%2200%3A00%22%2C%22beforeDate%22%3A%2201%2F01%2F2024%22%2C%22beforeTime%22%3A%2200%3A00%22%2C%22servers%22%3A%5B%5D%7D"; private static final HTTPConnector CONNECTOR = new HTTPConnector(); @@ -77,6 +78,7 @@ class AccessControlTest { private static String address; private static String cookieNoAccess; + static Stream testCases() { return Stream.of( Arguments.of("/", WebPermission.ACCESS, 302, 403), @@ -133,6 +135,10 @@ class AccessControlTest { Arguments.of("/v1/filters", WebPermission.ACCESS_QUERY, 200, 403), Arguments.of("/v1/query", WebPermission.ACCESS_QUERY, 400, 403), Arguments.of("/v1/query?q=%5B%5D&view=%7B%22afterDate%22%3A%2224%2F10%2F2022%22%2C%22afterTime%22%3A%2218%3A21%22%2C%22beforeDate%22%3A%2223%2F11%2F2022%22%2C%22beforeTime%22%3A%2217%3A21%22%2C%22servers%22%3A%5B%0A%7B%22serverUUID%22%3A%22" + TestConstants.SERVER_UUID_STRING + "%22%2C%22serverName%22%3A%22" + TestConstants.SERVER_NAME + "%22%2C%22proxy%22%3Afalse%7D%5D%7D", WebPermission.ACCESS_QUERY, 200, 403), + Arguments.of("/v1/query?q=%5B%7B%22kind%22%3A%22playedBetween%22%2C%22parameters%22%3A%7B%22afterDate%22%3A%2201%2F01%2F1970%22%2C%22afterTime%22%3A%2200%3A00%22%2C%22beforeDate%22%3A%2201%2F01%2F2024%22%2C%22beforeTime%22%3A%2200%3A00%22%7D%7D%5D&view=" + QUERY_VIEW_SIMPLE, WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_CALENDAR, 200, 403), + Arguments.of("/v1/query?q=%5B%7B%22kind%22%3A%22playedBetween%22%2C%22parameters%22%3A%7B%22afterDate%22%3A%2201%2F01%2F1970%22%2C%22afterTime%22%3A%2200%3A00%22%2C%22beforeDate%22%3A%2201%2F01%2F2024%22%2C%22beforeTime%22%3A%2200%3A00%22%7D%7D%5D&view=" + QUERY_VIEW_SIMPLE, WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_CALENDAR, 200, 403), + Arguments.of("/v1/query?q=%5B%7B%22kind%22%3A%22geolocations%22%2C%22parameters%22%3A%7B%22selected%22%3A%22%5B%5C%22FIN%5C%22%5D%22%7D%7D%5D&view=" + QUERY_VIEW_SIMPLE, WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP, 200, 403), + Arguments.of("/v1/query?q=%5B%7B%22kind%22%3A%22geolocations%22%2C%22parameters%22%3A%7B%22selected%22%3A%22%5B%5C%22FIN%5C%22%5D%22%7D%7D%5D&view=" + QUERY_VIEW_SIMPLE, WebPermission.PAGE_SERVER_GEOLOCATIONS_MAP, 200, 403), Arguments.of("/v1/errors", WebPermission.ACCESS_ERRORS, 200, 403), Arguments.of("/errors", WebPermission.ACCESS_ERRORS, 200, 403), Arguments.of("/v1/network/listServers", WebPermission.PAGE_NETWORK_PERFORMANCE, 200, 403),