mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-01-31 04:21:45 +01:00
Fix rate limiter failing /manage page test
Poor implementation makes /manage/groups page request /v1/permissions n times, where n is amount of groups. Since each visibility test registers a new group, there are a lot of them when opening the manage page, leading to tripping the rate-limiter
This commit is contained in:
parent
8a16455e95
commit
3dc6ac5afa
@ -40,14 +40,7 @@ public class RateLimitGuard {
|
||||
.expireAfterWrite(120, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
public boolean shouldPreventRequest(@Untrusted String accessor) {
|
||||
Integer attempts = requests.getIfPresent(accessor);
|
||||
if (attempts == null) return false;
|
||||
// Too many attempts, forbid further attempts.
|
||||
return attempts >= ATTEMPT_LIMIT;
|
||||
}
|
||||
|
||||
public void increaseAttemptCount(@Untrusted String requestPath, @Untrusted String accessor) {
|
||||
public boolean shouldPreventRequest(@Untrusted String requestPath, @Untrusted String accessor) {
|
||||
String previous = lastRequestPath.getIfPresent(accessor);
|
||||
if (!Objects.equals(previous, requestPath)) {
|
||||
resetAttemptCount(accessor);
|
||||
@ -60,23 +53,23 @@ public class RateLimitGuard {
|
||||
|
||||
lastRequestPath.put(accessor, requestPath);
|
||||
requests.put(accessor, attempts + 1);
|
||||
|
||||
// Too many attempts, forbid further attempts.
|
||||
return attempts + 1 >= ATTEMPT_LIMIT;
|
||||
}
|
||||
|
||||
public void resetAttemptCount(@Untrusted String accessor) {
|
||||
// previous request changed
|
||||
requests.cleanUp();
|
||||
requests.invalidate(accessor);
|
||||
requests.cleanUp();
|
||||
}
|
||||
|
||||
public static class Disabled extends RateLimitGuard {
|
||||
@Override
|
||||
public boolean shouldPreventRequest(String accessor) {
|
||||
public boolean shouldPreventRequest(@Untrusted String requestedPath, String accessor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increaseAttemptCount(String requestPath, String accessor) { /* Disabled */ }
|
||||
|
||||
@Override
|
||||
public void resetAttemptCount(String accessor) { /* Disabled */ }
|
||||
}
|
||||
|
@ -58,7 +58,6 @@ public class RequestHandler {
|
||||
public Response getResponse(InternalRequest internalRequest) {
|
||||
@Untrusted String accessAddress = internalRequest.getAccessAddress(webserverConfiguration);
|
||||
@Untrusted String requestedPath = internalRequest.getRequestedPath();
|
||||
rateLimitGuard.increaseAttemptCount(requestedPath, accessAddress);
|
||||
|
||||
boolean blocked = false;
|
||||
Response response;
|
||||
@ -66,7 +65,7 @@ public class RequestHandler {
|
||||
if (bruteForceGuard.shouldPreventRequest(accessAddress)) {
|
||||
response = responseFactory.failedLoginAttempts403();
|
||||
blocked = true;
|
||||
} else if (rateLimitGuard.shouldPreventRequest(accessAddress)) {
|
||||
} else if (rateLimitGuard.shouldPreventRequest(requestedPath, accessAddress)) {
|
||||
response = responseFactory.failedRateLimit403();
|
||||
blocked = true;
|
||||
} else if (!webserverConfiguration.getAllowedIpList().isAllowed(accessAddress)) {
|
||||
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.storage.database.transactions.commands;
|
||||
|
||||
import com.djrapitops.plan.storage.database.sql.tables.webuser.SecurityTable;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.webuser.WebGroupTable;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.webuser.WebGroupToPermissionTable;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.webuser.WebUserPreferencesTable;
|
||||
import com.djrapitops.plan.storage.database.transactions.Transaction;
|
||||
|
||||
/**
|
||||
* Transaction that removes all web groups from the database.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class RemoveWebGroupsTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
protected void performOperations() {
|
||||
clearTable(WebUserPreferencesTable.TABLE_NAME);
|
||||
clearTable(SecurityTable.TABLE_NAME);
|
||||
clearTable(WebGroupToPermissionTable.TABLE_NAME);
|
||||
clearTable(WebGroupTable.TABLE_NAME);
|
||||
}
|
||||
|
||||
private void clearTable(String tableName) {
|
||||
execute("DELETE FROM " + tableName);
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@ import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
|
||||
import com.djrapitops.plan.storage.database.transactions.StoreServerInformationTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.RemoveWebGroupsTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.StoreWebUserTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.StoreServerPlayerTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.webuser.StoreWebGroupTransaction;
|
||||
@ -94,6 +95,8 @@ class AccessControlVisibilityTest {
|
||||
|
||||
@AfterEach
|
||||
void tearDownTest(WebDriver driver) {
|
||||
String address = "https://localhost:" + TEST_PORT_NUMBER + "/auth/logout";
|
||||
driver.get(address);
|
||||
SeleniumExtension.newTab(driver);
|
||||
driver.manage().deleteAllCookies();
|
||||
}
|
||||
@ -206,6 +209,8 @@ class AccessControlVisibilityTest {
|
||||
@ParameterizedTest(name = "Access with visibility {0} can see element #{1} in /{2}")
|
||||
@MethodSource("pageLevelVisibleCases")
|
||||
void pageVisible(WebPermission permission, String element, String page, Database database, ServerUUID serverUUID, ChromeDriver driver) throws Exception {
|
||||
// TODO Remove after fixing manage/groups making bazillion calls to /v1/permissions
|
||||
database.executeTransaction(new RemoveWebGroupsTransaction()).get();
|
||||
User user = registerUser(database, permission);
|
||||
|
||||
String address = "https://localhost:" + TEST_PORT_NUMBER + "/" + page;
|
||||
@ -285,7 +290,7 @@ class AccessControlVisibilityTest {
|
||||
driver.get(address);
|
||||
login(driver, user);
|
||||
|
||||
SeleniumExtension.waitForElementToBeVisible(By.id("wrapper"), driver);
|
||||
SeleniumExtension.waitForElementToBeVisible(By.className("login-username"), driver);
|
||||
By id = By.id(element);
|
||||
assertThrows(NoSuchElementException.class, () -> driver.findElement(id), () -> "Saw element #" + element + " at " + address + " without permission to");
|
||||
assertNoLogs(driver, address);
|
||||
@ -327,7 +332,7 @@ class AccessControlVisibilityTest {
|
||||
driver.get(address);
|
||||
login(driver, user);
|
||||
|
||||
SeleniumExtension.waitForElementToBeVisible(By.id("wrapper"), driver);
|
||||
SeleniumExtension.waitForElementToBeVisible(By.className("login-username"), driver);
|
||||
By id = By.id(element);
|
||||
assertThrows(NoSuchElementException.class, () -> driver.findElement(id), () -> "Saw element #" + element + " at " + address + " without permission to");
|
||||
assertNoLogs(driver, address);
|
||||
@ -360,7 +365,7 @@ class AccessControlVisibilityTest {
|
||||
driver.get(address);
|
||||
login(driver, user);
|
||||
|
||||
SeleniumExtension.waitForElementToBeVisible(By.id("wrapper"), driver);
|
||||
SeleniumExtension.waitForElementToBeVisible(By.className("login-username"), driver);
|
||||
By id = By.id(element);
|
||||
assertThrows(NoSuchElementException.class, () -> driver.findElement(id), () -> "Saw element #" + element + " at " + address + " without permission to");
|
||||
assertNoLogs(driver, address);
|
||||
@ -406,7 +411,7 @@ class AccessControlVisibilityTest {
|
||||
driver.get(address);
|
||||
login(driver, playerUser);
|
||||
|
||||
SeleniumExtension.waitForElementToBeVisible(By.id("wrapper"), driver);
|
||||
SeleniumExtension.waitForElementToBeVisible(By.className("login-username"), driver);
|
||||
By id = By.id(element);
|
||||
assertThrows(NoSuchElementException.class, () -> driver.findElement(id), () -> "Saw element #" + element + " at /player/" + TestConstants.PLAYER_TWO_UUID + " without permission to");
|
||||
}
|
||||
|
@ -57,7 +57,8 @@ public class SeleniumExtension implements ParameterResolver, BeforeAllCallback,
|
||||
SeleniumExtension.waitForPageLoadForSeconds(5, driver);
|
||||
Awaitility.await("waitForElementToBeVisible " + by.toString())
|
||||
.atMost(5, TimeUnit.SECONDS)
|
||||
.ignoreException(NoSuchElementException.class)
|
||||
.ignoreExceptionsMatching(throwable -> throwable instanceof NoSuchElementException
|
||||
|| throwable instanceof StaleElementReferenceException)
|
||||
.until(() -> driver.findElement(by).isDisplayed());
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,7 @@ export const GroupEditContextProvider = ({groupName, children}) => {
|
||||
|
||||
const [allPermissions, setAllPermissions] = useState([]);
|
||||
useEffect(() => {
|
||||
// TODO Make this only happen once when opening groups page
|
||||
fetchAvailablePermissions().then(response => {
|
||||
setAllPermissions(response?.data?.permissions);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user