mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2025-02-08 07:51:33 +01:00
#876 Keep track of wrong logins by (ip, username) and implement threshold
This commit is contained in:
parent
bfcd28a9a1
commit
2417bf4c3f
@ -1,5 +1,6 @@
|
|||||||
package fr.xephi.authme.cache;
|
package fr.xephi.authme.cache;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import fr.xephi.authme.initialization.SettingsDependent;
|
import fr.xephi.authme.initialization.SettingsDependent;
|
||||||
import fr.xephi.authme.output.MessageKey;
|
import fr.xephi.authme.output.MessageKey;
|
||||||
import fr.xephi.authme.output.Messages;
|
import fr.xephi.authme.output.Messages;
|
||||||
@ -11,17 +12,20 @@ import org.bukkit.entity.Player;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manager for handling tempbans
|
* Manager for handling temporary bans.
|
||||||
*/
|
*/
|
||||||
// TODO Gnat008 20160613: Figure out the best way to remove entries based on time
|
// TODO #876: Implement HasCleanup interface
|
||||||
public class TempbanManager implements SettingsDependent {
|
public class TempbanManager implements SettingsDependent {
|
||||||
|
|
||||||
private static final long MINUTE_IN_MILLISECONDS = 60000;
|
private static final long MINUTE_IN_MILLISECONDS = 60_000;
|
||||||
|
// TODO #876: Make a setting out of this
|
||||||
|
private static final long COUNTER_RETENTION_MILLIS = 6 * 60 * MINUTE_IN_MILLISECONDS;
|
||||||
|
|
||||||
private final ConcurrentHashMap<String, Integer> ipLoginFailureCounts;
|
private final Map<String, Map<String, TimedCounter>> ipLoginFailureCounts;
|
||||||
private final BukkitService bukkitService;
|
private final BukkitService bukkitService;
|
||||||
private final Messages messages;
|
private final Messages messages;
|
||||||
|
|
||||||
@ -38,30 +42,40 @@ public class TempbanManager implements SettingsDependent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increases the failure count for the given IP address.
|
* Increases the failure count for the given IP address/username combination.
|
||||||
*
|
*
|
||||||
* @param address The player's IP address
|
* @param address The player's IP address
|
||||||
|
* @param name The username
|
||||||
*/
|
*/
|
||||||
public void increaseCount(String address) {
|
public void increaseCount(String address, String name) {
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
Integer count = ipLoginFailureCounts.get(address);
|
Map<String, TimedCounter> countsByName = ipLoginFailureCounts.get(address);
|
||||||
|
if (countsByName == null) {
|
||||||
|
countsByName = new ConcurrentHashMap<>();
|
||||||
|
ipLoginFailureCounts.put(address, countsByName);
|
||||||
|
}
|
||||||
|
|
||||||
if (count == null) {
|
TimedCounter counter = countsByName.get(name);
|
||||||
ipLoginFailureCounts.put(address, 1);
|
if (counter == null) {
|
||||||
|
countsByName.put(name, new TimedCounter(1));
|
||||||
} else {
|
} else {
|
||||||
ipLoginFailureCounts.put(address, count + 1);
|
counter.increment(COUNTER_RETENTION_MILLIS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the failure count for a given IP address to 0.
|
* Set the failure count for a given IP address / username combination to 0.
|
||||||
*
|
*
|
||||||
* @param address The IP address
|
* @param address The IP address
|
||||||
|
* @param name The username
|
||||||
*/
|
*/
|
||||||
public void resetCount(String address) {
|
public void resetCount(String address, String name) {
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
ipLoginFailureCounts.remove(address);
|
Map<String, TimedCounter> map = ipLoginFailureCounts.get(address);
|
||||||
|
if (map != null) {
|
||||||
|
map.remove(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,8 +87,14 @@ public class TempbanManager implements SettingsDependent {
|
|||||||
*/
|
*/
|
||||||
public boolean shouldTempban(String address) {
|
public boolean shouldTempban(String address) {
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
Integer count = ipLoginFailureCounts.get(address);
|
Map<String, TimedCounter> countsByName = ipLoginFailureCounts.get(address);
|
||||||
return count != null && count >= threshold;
|
if (countsByName != null) {
|
||||||
|
int total = 0;
|
||||||
|
for (TimedCounter counter : countsByName.values()) {
|
||||||
|
total += counter.getCount(COUNTER_RETENTION_MILLIS);
|
||||||
|
}
|
||||||
|
return total >= threshold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -103,7 +123,7 @@ public class TempbanManager implements SettingsDependent {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
resetCount(ip);
|
ipLoginFailureCounts.remove(ip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,4 +133,46 @@ public class TempbanManager implements SettingsDependent {
|
|||||||
this.threshold = settings.getProperty(SecuritySettings.MAX_LOGIN_TEMPBAN);
|
this.threshold = settings.getProperty(SecuritySettings.MAX_LOGIN_TEMPBAN);
|
||||||
this.length = settings.getProperty(SecuritySettings.TEMPBAN_LENGTH);
|
this.length = settings.getProperty(SecuritySettings.TEMPBAN_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counter with an associated timestamp, keeping track of when the last entry has been added.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
static final class TimedCounter {
|
||||||
|
|
||||||
|
private int counter;
|
||||||
|
private long lastIncrementTimestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param start the initial value to set the counter to
|
||||||
|
*/
|
||||||
|
TimedCounter(int start) {
|
||||||
|
this.counter = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the count, taking into account the last entry timestamp.
|
||||||
|
*
|
||||||
|
* @param threshold the threshold in milliseconds until when to consider a counter
|
||||||
|
* @return the counter's value, or {@code 0} if it was last incremented longer ago than the threshold
|
||||||
|
*/
|
||||||
|
int getCount(long threshold) {
|
||||||
|
if (System.currentTimeMillis() - lastIncrementTimestamp > threshold) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the counter, taking into account the last entry timestamp.
|
||||||
|
*
|
||||||
|
* @param threshold in milliseconds, the time span until which to consider the existing number
|
||||||
|
*/
|
||||||
|
void increment(long threshold) {
|
||||||
|
counter = getCount(threshold) + 1;
|
||||||
|
lastIncrementTimestamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
|||||||
// Increase the counts here before knowing the result of the login.
|
// Increase the counts here before knowing the result of the login.
|
||||||
// If the login is successful, we clear the captcha count for the player.
|
// If the login is successful, we clear the captcha count for the player.
|
||||||
captchaManager.increaseCount(name);
|
captchaManager.increaseCount(name);
|
||||||
tempbanManager.increaseCount(ip);
|
tempbanManager.increaseCount(ip, name);
|
||||||
|
|
||||||
String email = pAuth.getEmail();
|
String email = pAuth.getEmail();
|
||||||
boolean passwordVerified = forceLogin || passwordSecurity.comparePassword(
|
boolean passwordVerified = forceLogin || passwordSecurity.comparePassword(
|
||||||
@ -153,6 +153,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
|||||||
database.updateSession(auth);
|
database.updateSession(auth);
|
||||||
|
|
||||||
captchaManager.resetCounts(name);
|
captchaManager.resetCounts(name);
|
||||||
|
tempbanManager.resetCount(ip, name);
|
||||||
player.setNoDamageTicks(0);
|
player.setNoDamageTicks(0);
|
||||||
|
|
||||||
if (!forceLogin)
|
if (!forceLogin)
|
||||||
|
@ -2,6 +2,7 @@ package fr.xephi.authme.cache;
|
|||||||
|
|
||||||
import fr.xephi.authme.ReflectionTestUtils;
|
import fr.xephi.authme.ReflectionTestUtils;
|
||||||
import fr.xephi.authme.TestHelper;
|
import fr.xephi.authme.TestHelper;
|
||||||
|
import fr.xephi.authme.cache.TempbanManager.TimedCounter;
|
||||||
import fr.xephi.authme.output.MessageKey;
|
import fr.xephi.authme.output.MessageKey;
|
||||||
import fr.xephi.authme.output.Messages;
|
import fr.xephi.authme.output.Messages;
|
||||||
import fr.xephi.authme.settings.Settings;
|
import fr.xephi.authme.settings.Settings;
|
||||||
@ -49,13 +50,14 @@ public class TempbanManagerTest {
|
|||||||
String address = "192.168.1.1";
|
String address = "192.168.1.1";
|
||||||
|
|
||||||
// when
|
// when
|
||||||
for (int i = 0; i < 2; ++i) {
|
manager.increaseCount(address, "Bob");
|
||||||
manager.increaseCount(address);
|
manager.increaseCount(address, "Todd");
|
||||||
}
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(manager.shouldTempban(address), equalTo(false));
|
assertThat(manager.shouldTempban(address), equalTo(false));
|
||||||
manager.increaseCount(address);
|
assertHasCount(manager, address, "Bob", 1);
|
||||||
|
assertHasCount(manager, address, "Todd", 1);
|
||||||
|
manager.increaseCount(address, "Bob");
|
||||||
assertThat(manager.shouldTempban(address), equalTo(true));
|
assertThat(manager.shouldTempban(address), equalTo(true));
|
||||||
assertThat(manager.shouldTempban("10.0.0.1"), equalTo(false));
|
assertThat(manager.shouldTempban("10.0.0.1"), equalTo(false));
|
||||||
}
|
}
|
||||||
@ -68,20 +70,20 @@ public class TempbanManagerTest {
|
|||||||
TempbanManager manager = new TempbanManager(bukkitService, messages, settings);
|
TempbanManager manager = new TempbanManager(bukkitService, messages, settings);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
manager.increaseCount(address);
|
manager.increaseCount(address, "test");
|
||||||
manager.increaseCount(address);
|
manager.increaseCount(address, "test");
|
||||||
manager.increaseCount(address);
|
manager.increaseCount(address, "test");
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(manager.shouldTempban(address), equalTo(true));
|
assertThat(manager.shouldTempban(address), equalTo(true));
|
||||||
assertHasCount(manager, address, 3);
|
assertHasCount(manager, address, "test", 3);
|
||||||
|
|
||||||
// when 2
|
// when 2
|
||||||
manager.resetCount(address);
|
manager.resetCount(address, "test");
|
||||||
|
|
||||||
// then 2
|
// then 2
|
||||||
assertThat(manager.shouldTempban(address), equalTo(false));
|
assertThat(manager.shouldTempban(address), equalTo(false));
|
||||||
assertHasCount(manager, address, null);
|
assertHasNoEntries(manager, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -93,11 +95,11 @@ public class TempbanManagerTest {
|
|||||||
TempbanManager manager = new TempbanManager(bukkitService, messages, settings);
|
TempbanManager manager = new TempbanManager(bukkitService, messages, settings);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
manager.increaseCount(address);
|
manager.increaseCount(address, "username");
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(manager.shouldTempban(address), equalTo(false));
|
assertThat(manager.shouldTempban(address), equalTo(false));
|
||||||
assertHasCount(manager, address, null);
|
assertHasNoEntries(manager, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -109,10 +111,10 @@ public class TempbanManagerTest {
|
|||||||
given(settings.getProperty(SecuritySettings.TEMPBAN_ON_MAX_LOGINS)).willReturn(false);
|
given(settings.getProperty(SecuritySettings.TEMPBAN_ON_MAX_LOGINS)).willReturn(false);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
manager.increaseCount(address);
|
manager.increaseCount(address, "username");
|
||||||
// assumptions
|
// assumptions
|
||||||
assertThat(manager.shouldTempban(address), equalTo(true));
|
assertThat(manager.shouldTempban(address), equalTo(true));
|
||||||
assertHasCount(manager, address, 1);
|
assertHasCount(manager, address, "username", 1);
|
||||||
// end assumptions
|
// end assumptions
|
||||||
manager.reload(settings);
|
manager.reload(settings);
|
||||||
boolean result = manager.shouldTempban(address);
|
boolean result = manager.shouldTempban(address);
|
||||||
@ -173,9 +175,9 @@ public class TempbanManagerTest {
|
|||||||
given(messages.retrieveSingle(MessageKey.TEMPBAN_MAX_LOGINS)).willReturn(banReason);
|
given(messages.retrieveSingle(MessageKey.TEMPBAN_MAX_LOGINS)).willReturn(banReason);
|
||||||
Settings settings = mockSettings(10, 60);
|
Settings settings = mockSettings(10, 60);
|
||||||
TempbanManager manager = new TempbanManager(bukkitService, messages, settings);
|
TempbanManager manager = new TempbanManager(bukkitService, messages, settings);
|
||||||
manager.increaseCount(ip);
|
manager.increaseCount(ip, "user");
|
||||||
manager.increaseCount(ip);
|
manager.increaseCount(ip, "name2");
|
||||||
manager.increaseCount(ip);
|
manager.increaseCount(ip, "user");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
manager.tempbanPlayer(player);
|
manager.tempbanPlayer(player);
|
||||||
@ -183,7 +185,7 @@ public class TempbanManagerTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
verify(player).kickPlayer(banReason);
|
verify(player).kickPlayer(banReason);
|
||||||
assertHasCount(manager, ip, null);
|
assertHasNoEntries(manager, ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Settings mockSettings(int maxTries, int tempbanLength) {
|
private static Settings mockSettings(int maxTries, int tempbanLength) {
|
||||||
@ -194,10 +196,18 @@ public class TempbanManagerTest {
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertHasCount(TempbanManager manager, String address, Integer count) {
|
@SuppressWarnings("unchecked")
|
||||||
@SuppressWarnings("unchecked")
|
private static void assertHasNoEntries(TempbanManager manager, String address) {
|
||||||
Map<String, Integer> playerCounts = (Map<String, Integer>) ReflectionTestUtils
|
Map<String, Map<?, ?>> playerCounts = (Map<String, Map<?, ?>>) ReflectionTestUtils
|
||||||
.getFieldValue(TempbanManager.class, manager, "ipLoginFailureCounts");
|
.getFieldValue(TempbanManager.class, manager, "ipLoginFailureCounts");
|
||||||
assertThat(playerCounts.get(address), equalTo(count));
|
Map map = playerCounts.get(address);
|
||||||
|
assertThat(map == null || map.isEmpty(), equalTo(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static void assertHasCount(TempbanManager manager, String address, String name, int count) {
|
||||||
|
Map<String, Map<String, TimedCounter>> playerCounts = (Map<String, Map<String, TimedCounter>>)
|
||||||
|
ReflectionTestUtils.getFieldValue(TempbanManager.class, manager, "ipLoginFailureCounts");
|
||||||
|
assertThat(playerCounts.get(address).get(name).getCount(10000L), equalTo(count));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user