Make sure /v1/query checks access permissions for using filters.

This commit is contained in:
Aurora Lahtela 2023-10-21 10:57:48 +03:00
parent 65f706d3cc
commit 3d1c0f8d64
2 changed files with 58 additions and 1 deletions

View File

@ -139,12 +139,22 @@ public class QueryJSONResolver implements Resolver {
}
private Response getResponse(@Untrusted Request request) {
Optional<Response> cachedResult = checkForCachedResult(request);
Optional<WebUser> user = request.getUser();
boolean canAccessCache = user.map(u -> u.hasPermission(WebPermission.ACCESS_QUERY)).orElse(true);
Optional<Response> cachedResult = canAccessCache ? checkForCachedResult(request) : Optional.empty();
if (cachedResult.isPresent()) return cachedResult.get();
InputQueryDto inputQuery = parseInputQuery(request);
@Untrusted List<InputFilterDto> queries = inputQuery.getFilters();
// Check user has permission for the filter if login is enabled.
if (user.isPresent()) {
Optional<Response> errorResponse = checkFilterPermissions(queries, user.get());
if (errorResponse.isPresent()) {
return errorResponse.get();
}
}
Filter.Result result = filters.apply(queries);
List<Filter.ResultPath> resultPath = result.getInverseResultPath();
Collections.reverse(resultPath);
@ -152,6 +162,47 @@ public class QueryJSONResolver implements Resolver {
return buildAndStoreResponse(inputQuery, result, resultPath);
}
private Optional<Response> checkFilterPermissions(List<InputFilterDto> 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);

View File

@ -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<Arguments> 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),