mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2024-12-19 07:07:55 +01:00
Add IPv6 support for isLocal checks (#1592)
* Add IPv6 support for isLocal checks * Replace magic values like 127.0.0.1 and use our utility * Support for IPv6 local adresses in IPv6 only or dual stack environments * Loopback [::1] * Site-Local fc00::/7 * Link-local fe80::/10 * Introduce extra method for loopback addresses * Use public IP for passMaxLogin check * Use non-local IP addresses in test after change in verification
This commit is contained in:
parent
fc07ad3df1
commit
0227cb3f74
@ -18,6 +18,7 @@ import fr.xephi.authme.settings.commandconfig.CommandManager;
|
|||||||
import fr.xephi.authme.settings.properties.HooksSettings;
|
import fr.xephi.authme.settings.properties.HooksSettings;
|
||||||
import fr.xephi.authme.settings.properties.RegistrationSettings;
|
import fr.xephi.authme.settings.properties.RegistrationSettings;
|
||||||
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
||||||
|
import fr.xephi.authme.util.InternetProtocolUtils;
|
||||||
import fr.xephi.authme.util.PlayerUtils;
|
import fr.xephi.authme.util.PlayerUtils;
|
||||||
import org.bukkit.GameMode;
|
import org.bukkit.GameMode;
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
@ -183,8 +184,7 @@ public class AsynchronousJoin implements AsynchronousProcess {
|
|||||||
private boolean validatePlayerCountForIp(final Player player, String ip) {
|
private boolean validatePlayerCountForIp(final Player player, String ip) {
|
||||||
if (service.getProperty(RestrictionSettings.MAX_JOIN_PER_IP) > 0
|
if (service.getProperty(RestrictionSettings.MAX_JOIN_PER_IP) > 0
|
||||||
&& !service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)
|
&& !service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)
|
||||||
&& !"127.0.0.1".equalsIgnoreCase(ip)
|
&& !InternetProtocolUtils.isLoopbackAddress(ip)
|
||||||
&& !"localhost".equalsIgnoreCase(ip)
|
|
||||||
&& countOnlinePlayersByIp(ip) > service.getProperty(RestrictionSettings.MAX_JOIN_PER_IP)) {
|
&& countOnlinePlayersByIp(ip) > service.getProperty(RestrictionSettings.MAX_JOIN_PER_IP)) {
|
||||||
|
|
||||||
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(
|
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(
|
||||||
|
@ -30,6 +30,7 @@ import fr.xephi.authme.settings.properties.EmailSettings;
|
|||||||
import fr.xephi.authme.settings.properties.HooksSettings;
|
import fr.xephi.authme.settings.properties.HooksSettings;
|
||||||
import fr.xephi.authme.settings.properties.PluginSettings;
|
import fr.xephi.authme.settings.properties.PluginSettings;
|
||||||
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
||||||
|
import fr.xephi.authme.util.InternetProtocolUtils;
|
||||||
import fr.xephi.authme.util.PlayerUtils;
|
import fr.xephi.authme.util.PlayerUtils;
|
||||||
import fr.xephi.authme.util.Utils;
|
import fr.xephi.authme.util.Utils;
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
@ -325,8 +326,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
|||||||
// Do not perform the check if player has multiple accounts permission or if IP is localhost
|
// Do not perform the check if player has multiple accounts permission or if IP is localhost
|
||||||
if (service.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP) <= 0
|
if (service.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP) <= 0
|
||||||
|| service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)
|
|| service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)
|
||||||
|| "127.0.0.1".equalsIgnoreCase(ip)
|
|| InternetProtocolUtils.isLoopbackAddress(ip)) {
|
||||||
|| "localhost".equalsIgnoreCase(ip)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import fr.xephi.authme.service.bungeecord.BungeeSender;
|
|||||||
import fr.xephi.authme.service.bungeecord.MessageType;
|
import fr.xephi.authme.service.bungeecord.MessageType;
|
||||||
import fr.xephi.authme.settings.properties.RegistrationSettings;
|
import fr.xephi.authme.settings.properties.RegistrationSettings;
|
||||||
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
||||||
|
import fr.xephi.authme.util.InternetProtocolUtils;
|
||||||
import fr.xephi.authme.util.PlayerUtils;
|
import fr.xephi.authme.util.PlayerUtils;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
@ -119,8 +120,7 @@ public class AsyncRegister implements AsynchronousProcess {
|
|||||||
final int maxRegPerIp = service.getProperty(RestrictionSettings.MAX_REGISTRATION_PER_IP);
|
final int maxRegPerIp = service.getProperty(RestrictionSettings.MAX_REGISTRATION_PER_IP);
|
||||||
final String ip = PlayerUtils.getPlayerIp(player);
|
final String ip = PlayerUtils.getPlayerIp(player);
|
||||||
if (maxRegPerIp > 0
|
if (maxRegPerIp > 0
|
||||||
&& !"127.0.0.1".equalsIgnoreCase(ip)
|
&& !InternetProtocolUtils.isLoopbackAddress(ip)
|
||||||
&& !"localhost".equalsIgnoreCase(ip)
|
|
||||||
&& !service.hasPermission(player, ALLOW_MULTIPLE_ACCOUNTS)) {
|
&& !service.hasPermission(player, ALLOW_MULTIPLE_ACCOUNTS)) {
|
||||||
List<String> otherAccounts = database.getAllAuthsByIp(ip);
|
List<String> otherAccounts = database.getAllAuthsByIp(ip);
|
||||||
if (otherAccounts.size() >= maxRegPerIp) {
|
if (otherAccounts.size() >= maxRegPerIp) {
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
package fr.xephi.authme.util;
|
package fr.xephi.authme.util;
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class about the InternetProtocol
|
* Utility class about the InternetProtocol
|
||||||
*/
|
*/
|
||||||
public final class InternetProtocolUtils {
|
public final class InternetProtocolUtils {
|
||||||
|
|
||||||
private static final Pattern LOCAL_ADDRESS_PATTERN =
|
|
||||||
Pattern.compile("(^127\\.)|(^(0)?10\\.)|(^172\\.(0)?1[6-9]\\.)|(^172\\.(0)?2[0-9]\\.)"
|
|
||||||
+ "|(^172\\.(0)?3[0-1]\\.)|(^169\\.254\\.)|(^192\\.168\\.)");
|
|
||||||
|
|
||||||
// Utility class
|
// Utility class
|
||||||
private InternetProtocolUtils() {
|
private InternetProtocolUtils() {
|
||||||
}
|
}
|
||||||
@ -19,10 +16,57 @@ public final class InternetProtocolUtils {
|
|||||||
* Checks if the specified address is a private or loopback address
|
* Checks if the specified address is a private or loopback address
|
||||||
*
|
*
|
||||||
* @param address address to check
|
* @param address address to check
|
||||||
*
|
* @return true if the address is a local (site and link) or loopback address, false otherwise
|
||||||
* @return true if the address is a local or loopback address, false otherwise
|
|
||||||
*/
|
*/
|
||||||
public static boolean isLocalAddress(String address) {
|
public static boolean isLocalAddress(String address) {
|
||||||
return LOCAL_ADDRESS_PATTERN.matcher(address).find();
|
try {
|
||||||
|
InetAddress inetAddress = InetAddress.getByName(address);
|
||||||
|
|
||||||
|
// Examples: 127.0.0.1, localhost or [::1]
|
||||||
|
return isLoopbackAddress(address)
|
||||||
|
// Example: 10.0.0.0, 172.16.0.0, 192.168.0.0, fec0::/10 (deprecated)
|
||||||
|
// Ref: https://en.wikipedia.org/wiki/IP_address#Private_addresses
|
||||||
|
|| inetAddress.isSiteLocalAddress()
|
||||||
|
// Example: 169.254.0.0/16, fe80::/10
|
||||||
|
// Ref: https://en.wikipedia.org/wiki/IP_address#Address_autoconfiguration
|
||||||
|
|| inetAddress.isLinkLocalAddress()
|
||||||
|
// non deprecated unique site-local that java doesn't check yet -> fc00::/7
|
||||||
|
|| isIPv6UniqueSiteLocal(inetAddress);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the specified address is a loopback address. This can be one of the following:
|
||||||
|
* <ul>
|
||||||
|
* <li>127.0.0.1</li>
|
||||||
|
* <li>localhost</li>
|
||||||
|
* <li>[::1]</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param address address to check
|
||||||
|
* @return true if the address is a loopback one
|
||||||
|
*/
|
||||||
|
public static boolean isLoopbackAddress(String address) {
|
||||||
|
try {
|
||||||
|
InetAddress inetAddress = InetAddress.getByName(address);
|
||||||
|
return inetAddress.isLoopbackAddress();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isLoopbackAddress(InetAddress address) {
|
||||||
|
return address.isLoopbackAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isIPv6UniqueSiteLocal(InetAddress address) {
|
||||||
|
// ref: https://en.wikipedia.org/wiki/Unique_local_address
|
||||||
|
|
||||||
|
// currently undefined but could be used in the near future fc00::/8
|
||||||
|
return (address.getAddress()[0] & 0xFF) == 0xFC
|
||||||
|
// in use for unique site-local fd00::/8
|
||||||
|
|| (address.getAddress()[0] & 0xFF) == 0xFD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ public class AsynchronousLoginTest {
|
|||||||
public void shouldNotForceLoginUserWithAlreadyOnlineIp() {
|
public void shouldNotForceLoginUserWithAlreadyOnlineIp() {
|
||||||
// given
|
// given
|
||||||
String name = "oscar";
|
String name = "oscar";
|
||||||
String ip = "127.0.12.245";
|
String ip = "1.1.1.245";
|
||||||
Player player = mockPlayer(name);
|
Player player = mockPlayer(name);
|
||||||
TestHelper.mockPlayerIp(player, ip);
|
TestHelper.mockPlayerIp(player, ip);
|
||||||
given(playerCache.isAuthenticated(name)).willReturn(false);
|
given(playerCache.isAuthenticated(name)).willReturn(false);
|
||||||
@ -147,7 +147,7 @@ public class AsynchronousLoginTest {
|
|||||||
public void shouldNotForceLoginForCanceledEvent() {
|
public void shouldNotForceLoginForCanceledEvent() {
|
||||||
// given
|
// given
|
||||||
String name = "oscar";
|
String name = "oscar";
|
||||||
String ip = "127.0.12.245";
|
String ip = "1.1.1.245";
|
||||||
Player player = mockPlayer(name);
|
Player player = mockPlayer(name);
|
||||||
TestHelper.mockPlayerIp(player, ip);
|
TestHelper.mockPlayerIp(player, ip);
|
||||||
given(playerCache.isAuthenticated(name)).willReturn(false);
|
given(playerCache.isAuthenticated(name)).willReturn(false);
|
||||||
@ -180,7 +180,7 @@ public class AsynchronousLoginTest {
|
|||||||
mockOnlinePlayersInBukkitService();
|
mockOnlinePlayersInBukkitService();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "127.0.0.4");
|
boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "1.1.1.1");
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result, equalTo(false));
|
assertThat(result, equalTo(false));
|
||||||
@ -195,7 +195,7 @@ public class AsynchronousLoginTest {
|
|||||||
given(commonService.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP)).willReturn(0);
|
given(commonService.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP)).willReturn(0);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "192.168.0.1");
|
boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "2.2.2.2");
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result, equalTo(false));
|
assertThat(result, equalTo(false));
|
||||||
@ -210,7 +210,7 @@ public class AsynchronousLoginTest {
|
|||||||
given(commonService.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(true);
|
given(commonService.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(true);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "127.0.0.4");
|
boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "1.1.1.1");
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result, equalTo(false));
|
assertThat(result, equalTo(false));
|
||||||
@ -227,7 +227,7 @@ public class AsynchronousLoginTest {
|
|||||||
mockOnlinePlayersInBukkitService();
|
mockOnlinePlayersInBukkitService();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "192.168.0.1");
|
boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "2.2.2.2");
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result, equalTo(true));
|
assertThat(result, equalTo(true));
|
||||||
@ -242,28 +242,28 @@ public class AsynchronousLoginTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void mockOnlinePlayersInBukkitService() {
|
private void mockOnlinePlayersInBukkitService() {
|
||||||
// 127.0.0.4: albania (online), brazil (offline)
|
// 1.1.1.1: albania (online), brazil (offline)
|
||||||
Player playerA = mockPlayer("albania");
|
Player playerA = mockPlayer("albania");
|
||||||
TestHelper.mockPlayerIp(playerA, "127.0.0.4");
|
TestHelper.mockPlayerIp(playerA, "1.1.1.1");
|
||||||
given(dataSource.isLogged(playerA.getName())).willReturn(true);
|
given(dataSource.isLogged(playerA.getName())).willReturn(true);
|
||||||
Player playerB = mockPlayer("brazil");
|
Player playerB = mockPlayer("brazil");
|
||||||
TestHelper.mockPlayerIp(playerB, "127.0.0.4");
|
TestHelper.mockPlayerIp(playerB, "1.1.1.1");
|
||||||
given(dataSource.isLogged(playerB.getName())).willReturn(false);
|
given(dataSource.isLogged(playerB.getName())).willReturn(false);
|
||||||
|
|
||||||
// 192.168.0.1: congo (online), denmark (offline), ecuador (online)
|
// 2.2.2.2: congo (online), denmark (offline), ecuador (online)
|
||||||
Player playerC = mockPlayer("congo");
|
Player playerC = mockPlayer("congo");
|
||||||
TestHelper.mockPlayerIp(playerC, "192.168.0.1");
|
TestHelper.mockPlayerIp(playerC, "2.2.2.2");
|
||||||
given(dataSource.isLogged(playerC.getName())).willReturn(true);
|
given(dataSource.isLogged(playerC.getName())).willReturn(true);
|
||||||
Player playerD = mockPlayer("denmark");
|
Player playerD = mockPlayer("denmark");
|
||||||
TestHelper.mockPlayerIp(playerD, "192.168.0.1");
|
TestHelper.mockPlayerIp(playerD, "2.2.2.2");
|
||||||
given(dataSource.isLogged(playerD.getName())).willReturn(false);
|
given(dataSource.isLogged(playerD.getName())).willReturn(false);
|
||||||
Player playerE = mockPlayer("ecuador");
|
Player playerE = mockPlayer("ecuador");
|
||||||
TestHelper.mockPlayerIp(playerE, "192.168.0.1");
|
TestHelper.mockPlayerIp(playerE, "2.2.2.2");
|
||||||
given(dataSource.isLogged(playerE.getName())).willReturn(true);
|
given(dataSource.isLogged(playerE.getName())).willReturn(true);
|
||||||
|
|
||||||
// 192.168.0.0: france (offline)
|
// 3.3.3.3: france (offline)
|
||||||
Player playerF = mockPlayer("france");
|
Player playerF = mockPlayer("france");
|
||||||
TestHelper.mockPlayerIp(playerF, "192.168.0.0");
|
TestHelper.mockPlayerIp(playerF, "3.3.3.3");
|
||||||
|
|
||||||
List<Player> onlinePlayers = Arrays.asList(playerA, playerB, playerC, playerD, playerE, playerF);
|
List<Player> onlinePlayers = Arrays.asList(playerA, playerB, playerC, playerD, playerE, playerF);
|
||||||
returnGivenOnlinePlayers(bukkitService, onlinePlayers);
|
returnGivenOnlinePlayers(bukkitService, onlinePlayers);
|
||||||
|
@ -3,8 +3,8 @@ package fr.xephi.authme.util;
|
|||||||
import fr.xephi.authme.TestHelper;
|
import fr.xephi.authme.TestHelper;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for {@link InternetProtocolUtils}
|
* Test for {@link InternetProtocolUtils}
|
||||||
@ -13,14 +13,44 @@ public class InternetProtocolUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldCheckLocalAddress() {
|
public void shouldCheckLocalAddress() {
|
||||||
|
// loopback
|
||||||
|
assertThat(InternetProtocolUtils.isLocalAddress("localhost"), equalTo(true));
|
||||||
assertThat(InternetProtocolUtils.isLocalAddress("127.0.0.1"), equalTo(true));
|
assertThat(InternetProtocolUtils.isLocalAddress("127.0.0.1"), equalTo(true));
|
||||||
|
assertThat(InternetProtocolUtils.isLocalAddress("::1"), equalTo(true));
|
||||||
|
|
||||||
|
// site local
|
||||||
assertThat(InternetProtocolUtils.isLocalAddress("10.0.0.1"), equalTo(true));
|
assertThat(InternetProtocolUtils.isLocalAddress("10.0.0.1"), equalTo(true));
|
||||||
assertThat(InternetProtocolUtils.isLocalAddress("172.0.0.1"), equalTo(false));
|
assertThat(InternetProtocolUtils.isLocalAddress("172.0.0.1"), equalTo(false));
|
||||||
assertThat(InternetProtocolUtils.isLocalAddress("172.16.0.1"), equalTo(true));
|
assertThat(InternetProtocolUtils.isLocalAddress("172.16.0.1"), equalTo(true));
|
||||||
assertThat(InternetProtocolUtils.isLocalAddress("192.168.0.1"), equalTo(true));
|
assertThat(InternetProtocolUtils.isLocalAddress("192.168.0.1"), equalTo(true));
|
||||||
|
|
||||||
|
// deprecated site-local
|
||||||
|
// ref: https://en.wikipedia.org/wiki/IPv6_address#Default_address_selection
|
||||||
|
assertThat(InternetProtocolUtils.isLocalAddress("fec0::"), equalTo(true));
|
||||||
|
|
||||||
|
// unique site-local (not deprecated!)
|
||||||
|
// ref: https://en.wikipedia.org/wiki/Unique_local_address
|
||||||
|
assertThat(InternetProtocolUtils.isLocalAddress("fde4:8dba:82e1::"), equalTo(true));
|
||||||
|
assertThat(InternetProtocolUtils.isLocalAddress("fc00::"), equalTo(true));
|
||||||
|
assertThat(InternetProtocolUtils.isLocalAddress("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), equalTo(true));
|
||||||
|
assertThat(InternetProtocolUtils.isLocalAddress("fe00::"), equalTo(false));
|
||||||
|
|
||||||
|
// link local
|
||||||
|
assertThat(InternetProtocolUtils.isLocalAddress("169.254.0.64"), equalTo(true));
|
||||||
|
assertThat(InternetProtocolUtils.isLocalAddress("FE80:0000:0000:0000:C800:0EFF:FE74:0008"), equalTo(true));
|
||||||
|
|
||||||
|
// public
|
||||||
assertThat(InternetProtocolUtils.isLocalAddress("94.32.34.5"), equalTo(false));
|
assertThat(InternetProtocolUtils.isLocalAddress("94.32.34.5"), equalTo(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsLoopback() {
|
||||||
|
// loopback
|
||||||
|
assertThat(InternetProtocolUtils.isLoopbackAddress("localhost"), equalTo(true));
|
||||||
|
assertThat(InternetProtocolUtils.isLoopbackAddress("127.0.0.1"), equalTo(true));
|
||||||
|
assertThat(InternetProtocolUtils.isLoopbackAddress("::1"), equalTo(true));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldHavePrivateConstructor() {
|
public void shouldHavePrivateConstructor() {
|
||||||
// given / when / then
|
// given / when / then
|
||||||
|
Loading…
Reference in New Issue
Block a user