Merge remote-tracking branch 'origin/inventory-cached' into feargames

This commit is contained in:
Gabriele C 2021-12-12 19:00:57 +01:00
commit ec36e6875e
9 changed files with 127 additions and 65 deletions

View File

@ -10,6 +10,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class PlayerCache {
private final Map<String, PlayerAuth> cache = new ConcurrentHashMap<>();
private final Map<String, RegistrationStatus> registeredCache = new ConcurrentHashMap<>();
PlayerCache() {
}
@ -20,6 +21,7 @@ public class PlayerCache {
* @param auth the player auth object to save
*/
public void updatePlayer(PlayerAuth auth) {
registeredCache.put(auth.getNickname().toLowerCase(), RegistrationStatus.REGISTERED);
cache.put(auth.getNickname().toLowerCase(), auth);
}
@ -30,6 +32,7 @@ public class PlayerCache {
*/
public void removePlayer(String user) {
cache.remove(user.toLowerCase());
registeredCache.remove(user.toLowerCase());
}
/**
@ -43,6 +46,35 @@ public class PlayerCache {
return cache.containsKey(user.toLowerCase());
}
/**
* Add a registration entry to the cache for active use later like the player active playing.
*
* @param user player name
* @param status registration status
*/
public void addRegistrationStatus(String user, RegistrationStatus status) {
registeredCache.put(user.toLowerCase(), status);
}
/**
* Update the status for existing entries like currently active users
* @param user player name
* @param status newest query result
*/
public void updateRegistrationStatus(String user, RegistrationStatus status) {
registeredCache.replace(user, status);
}
/**
* Checks if there is cached result with the player having an account.
* <b>Warning: This shouldn't be used for authentication, because the result could be outdated.</b>
* @param user player name
* @return Cached result about being registered or unregistered and UNKNOWN if there is no cache entry
*/
public RegistrationStatus getRegistrationStatus(String user) {
return registeredCache.getOrDefault(user.toLowerCase(), RegistrationStatus.UNKNOWN);
}
/**
* Returns the PlayerAuth associated with the given user, if available.
*
@ -66,8 +98,13 @@ public class PlayerCache {
*
* @return all player auths inside the player cache
*/
public Map<String, PlayerAuth> getCache() {
public Map<String, PlayerAuth> getAuthCache() {
return this.cache;
}
public enum RegistrationStatus {
REGISTERED,
UNREGISTERED,
UNKNOWN
}
}

View File

@ -267,7 +267,7 @@ public class CacheDataSource implements DataSource {
@Override
public List<String> getLoggedPlayersWithEmptyMail() {
return playerCache.getCache().values().stream()
return playerCache.getAuthCache().values().stream()
.filter(auth -> Utils.isEmailEmpty(auth.getEmail()))
.map(PlayerAuth::getRealName)
.collect(Collectors.toList());

View File

@ -1,6 +1,7 @@
package fr.xephi.authme.listener;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.auth.PlayerCache.RegistrationStatus;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.service.ValidationService;
@ -17,7 +18,7 @@ import javax.inject.Inject;
/**
* Service class for the AuthMe listeners to determine whether an event should be canceled.
*/
class ListenerService implements SettingsDependent {
public class ListenerService implements SettingsDependent {
private final DataSource dataSource;
private final PlayerCache playerCache;
@ -77,28 +78,34 @@ class ListenerService implements SettingsDependent {
* @return true if the associated event should be canceled, false otherwise
*/
public boolean shouldCancelEvent(Player player) {
return player != null && !checkAuth(player.getName()) && !PlayerUtils.isNpc(player);
return player != null && !PlayerUtils.isNpc(player) && shouldRestrictPlayer(player.getName());
}
/**
* Check if restriction are required for the given player name. The check will be performed against the local
* cache. This means changes from other sources like web services will have a delay to it.
*
* @param name player name
* @return true if the player needs to be restricted
*/
public boolean shouldRestrictPlayer(String name) {
if (validationService.isUnrestricted(name) || playerCache.isAuthenticated(name)) {
return false;
}
if (isRegistrationForced) {
// registration always required to play - so restrict everything
return true;
}
// registration not enforced, but registered players needs to be restricted if not logged in
// if there is no data fall back to safer alternative to prevent any leakage
final RegistrationStatus status = playerCache.getRegistrationStatus(name);
return status != RegistrationStatus.UNREGISTERED;
}
@Override
public void reload(Settings settings) {
isRegistrationForced = settings.getProperty(RegistrationSettings.FORCE);
}
/**
* Checks whether the player is allowed to perform actions (i.e. whether he is logged in
* or if other settings permit playing).
*
* @param name the name of the player to verify
* @return true if the player may play, false otherwise
*/
private boolean checkAuth(String name) {
if (validationService.isUnrestricted(name) || playerCache.isAuthenticated(name)) {
return true;
}
if (!isRegistrationForced && !dataSource.isAuthAvailable(name)) {
return true;
}
return false;
}
}

View File

@ -24,20 +24,21 @@ import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.StructureModifier;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.listener.ListenerService;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.service.BukkitService;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
class InventoryPacketAdapter extends PacketAdapter {
private static final int PLAYER_INVENTORY = 0;
@ -49,13 +50,12 @@ class InventoryPacketAdapter extends PacketAdapter {
private static final int HOTBAR_SIZE = 9;
private final ConsoleLogger logger = ConsoleLoggerFactory.get(InventoryPacketAdapter.class);
private final PlayerCache playerCache;
private final DataSource dataSource;
InventoryPacketAdapter(AuthMe plugin, PlayerCache playerCache, DataSource dataSource) {
private final ListenerService listenerService;
InventoryPacketAdapter(AuthMe plugin, ListenerService listenerService) {
super(plugin, PacketType.Play.Server.SET_SLOT, PacketType.Play.Server.WINDOW_ITEMS);
this.playerCache = playerCache;
this.dataSource = dataSource;
this.listenerService = listenerService;
}
@Override
@ -64,7 +64,7 @@ class InventoryPacketAdapter extends PacketAdapter {
PacketContainer packet = packetEvent.getPacket();
int windowId = packet.getIntegers().read(0);
if (windowId == PLAYER_INVENTORY && shouldHideInventory(player.getName())) {
if (windowId == PLAYER_INVENTORY && listenerService.shouldRestrictPlayer(player.getName())) {
packetEvent.setCancelled(true);
}
}
@ -78,14 +78,10 @@ class InventoryPacketAdapter extends PacketAdapter {
ProtocolLibrary.getProtocolManager().addPacketListener(this);
bukkitService.getOnlinePlayers().stream()
.filter(player -> shouldHideInventory(player.getName()))
.filter(player -> listenerService.shouldRestrictPlayer(player.getName()))
.forEach(this::sendBlankInventoryPacket);
}
private boolean shouldHideInventory(String playerName) {
return !playerCache.isAuthenticated(playerName) && dataSource.isAuthAvailable(playerName);
}
public void unregister() {
ProtocolLibrary.getProtocolManager().removePacketListener(this);
}

View File

@ -1,19 +1,21 @@
package fr.xephi.authme.listener.protocollib;
import ch.jalu.injector.annotations.NoFieldScan;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.listener.ListenerService;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import org.bukkit.entity.Player;
@NoFieldScan
public class ProtocolLibService implements SettingsDependent {
@ -31,16 +33,16 @@ public class ProtocolLibService implements SettingsDependent {
private boolean isEnabled;
private final AuthMe plugin;
private final BukkitService bukkitService;
private final ListenerService listenerService;
private final PlayerCache playerCache;
private final DataSource dataSource;
@Inject
ProtocolLibService(AuthMe plugin, Settings settings, BukkitService bukkitService, PlayerCache playerCache,
DataSource dataSource) {
ProtocolLibService(AuthMe plugin, Settings settings, BukkitService bukkitService, ListenerService listenerService,
PlayerCache playerCache) {
this.plugin = plugin;
this.bukkitService = bukkitService;
this.listenerService = listenerService;
this.playerCache = playerCache;
this.dataSource = dataSource;
reload(settings);
}
@ -66,7 +68,7 @@ public class ProtocolLibService implements SettingsDependent {
if (protectInvBeforeLogin) {
if (inventoryPacketAdapter == null) {
// register the packet listener and start hiding it for all already online players (reload)
inventoryPacketAdapter = new InventoryPacketAdapter(plugin, playerCache, dataSource);
inventoryPacketAdapter = new InventoryPacketAdapter(plugin, listenerService);
inventoryPacketAdapter.register(bukkitService);
}
} else if (inventoryPacketAdapter != null) {
@ -76,7 +78,7 @@ public class ProtocolLibService implements SettingsDependent {
if (denyTabCompleteBeforeLogin) {
if (tabCompletePacketAdapter == null) {
tabCompletePacketAdapter = new TabCompletePacketAdapter(plugin, playerCache);
tabCompletePacketAdapter = new TabCompletePacketAdapter(plugin, listenerService);
tabCompletePacketAdapter.register();
}
} else if (tabCompletePacketAdapter != null) {
@ -118,8 +120,8 @@ public class ProtocolLibService implements SettingsDependent {
public void reload(Settings settings) {
boolean oldProtectInventory = this.protectInvBeforeLogin;
this.protectInvBeforeLogin = settings.getProperty(RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN);
this.denyTabCompleteBeforeLogin = settings.getProperty(RestrictionSettings.DENY_TABCOMPLETE_BEFORE_LOGIN);
this.protectInvBeforeLogin = settings.getProperty(RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN);
//it was true and will be deactivated now, so we need to restore the inventory for every player
if (oldProtectInventory && !protectInvBeforeLogin && inventoryPacketAdapter != null) {

View File

@ -8,24 +8,26 @@ import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.FieldAccessException;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.listener.ListenerService;
import fr.xephi.authme.output.ConsoleLoggerFactory;
class TabCompletePacketAdapter extends PacketAdapter {
private final ConsoleLogger logger = ConsoleLoggerFactory.get(TabCompletePacketAdapter.class);
private final PlayerCache playerCache;
TabCompletePacketAdapter(AuthMe plugin, PlayerCache playerCache) {
private final ListenerService listenerService;
TabCompletePacketAdapter(AuthMe plugin, ListenerService listenerService) {
super(plugin, ListenerPriority.NORMAL, PacketType.Play.Client.TAB_COMPLETE);
this.playerCache = playerCache;
this.listenerService = listenerService;
}
@Override
public void onPacketReceiving(PacketEvent event) {
if (event.getPacketType() == PacketType.Play.Client.TAB_COMPLETE) {
try {
if (!playerCache.isAuthenticated(event.getPlayer().getName())) {
String playerName = event.getPlayer().getName();
if (listenerService.shouldRestrictPlayer(playerName)) {
event.setCancelled(true);
}
} catch (FieldAccessException e) {

View File

@ -2,6 +2,8 @@ package fr.xephi.authme.process.join;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.ProxySessionManager;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.auth.PlayerCache.RegistrationStatus;
import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.ProtectInventoryEvent;
@ -82,6 +84,9 @@ public class AsynchronousJoin implements AsynchronousProcess {
@Inject
private BungeeSender bungeeSender;
@Inject
private PlayerCache playerCache;
@Inject
private ProxySessionManager proxySessionManager;
@ -121,17 +126,10 @@ public class AsynchronousJoin implements AsynchronousProcess {
}
final boolean isAuthAvailable = database.isAuthAvailable(name);
RegistrationStatus status = isAuthAvailable ? RegistrationStatus.REGISTERED : RegistrationStatus.UNREGISTERED;
playerCache.addRegistrationStatus(name, status);
if (isAuthAvailable) {
// Protect inventory
if (service.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) {
ProtectInventoryEvent ev = bukkitService.createAndCallEvent(
isAsync -> new ProtectInventoryEvent(player, isAsync));
if (ev.isCancelled()) {
player.updateInventory();
logger.fine("ProtectInventoryEvent has been cancelled for " + player.getName() + "...");
}
}
protectInventory(player);
// Session logic
if (sessionService.canResumeSession(player)) {
@ -164,6 +162,18 @@ public class AsynchronousJoin implements AsynchronousProcess {
processJoinSync(player, isAuthAvailable);
}
private void protectInventory(Player player) {
// Protect inventory
if (service.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) {
ProtectInventoryEvent ev = bukkitService.createAndCallEvent(
isAsync -> new ProtectInventoryEvent(player, isAsync));
if (ev.isCancelled()) {
player.updateInventory();
logger.fine("ProtectInventoryEvent has been cancelled for " + player.getName() + "...");
}
}
}
private void handlePlayerWithUnmetNameRestriction(Player player, String ip) {
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> {
player.kickPlayer(service.retrieveSingleMessage(player, MessageKey.NOT_OWNER_ERROR));

View File

@ -3,6 +3,7 @@ package fr.xephi.authme.service;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.auth.PlayerCache.RegistrationStatus;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.AbstractTeleportEvent;
@ -112,7 +113,8 @@ public class TeleportationService implements Reloadable {
return;
}
if (!player.hasPlayedBefore() || !dataSource.isAuthAvailable(player.getName())) {
RegistrationStatus registrationStatus = playerCache.getRegistrationStatus(player.getName());
if (!player.hasPlayedBefore() || registrationStatus == RegistrationStatus.UNREGISTERED) {
logger.debug("Attempting to teleport player `{0}` to first spawn", player.getName());
performTeleportation(player, new FirstSpawnTeleportEvent(player, firstSpawn));
}

View File

@ -4,6 +4,7 @@ import ch.jalu.injector.testing.BeforeInjecting;
import ch.jalu.injector.testing.DelayedInjectionRunner;
import ch.jalu.injector.testing.InjectDelayed;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.auth.PlayerCache.RegistrationStatus;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.settings.Settings;
@ -119,6 +120,7 @@ public class ListenerServiceTest {
String playerName = "myPlayer1";
Player player = mockPlayerWithName(playerName);
given(playerCache.isAuthenticated(playerName)).willReturn(false);
given(playerCache.getRegistrationStatus(playerName)).willReturn(RegistrationStatus.UNREGISTERED);
given(settings.getProperty(RegistrationSettings.FORCE)).willReturn(false);
EntityEvent event = mock(EntityEvent.class);
given(event.getEntity()).willReturn(player);
@ -130,7 +132,7 @@ public class ListenerServiceTest {
// then
assertThat(result, equalTo(false));
verify(playerCache).isAuthenticated(playerName);
verify(dataSource).isAuthAvailable(playerName);
verify(playerCache).getRegistrationStatus(playerName);
}
@Test
@ -154,10 +156,9 @@ public class ListenerServiceTest {
public void shouldAllowNpcPlayer() {
// given
String playerName = "other_npc";
Player player = mockPlayerWithName(playerName);
Player player = mockPlayerWithName(playerName, true);
EntityEvent event = mock(EntityEvent.class);
given(event.getEntity()).willReturn(player);
given(player.hasMetadata("NPC")).willReturn(true);
// when
boolean result = listenerService.shouldCancelEvent(event);
@ -214,8 +215,13 @@ public class ListenerServiceTest {
}
private static Player mockPlayerWithName(String name) {
return mockPlayerWithName(name,false);
}
private static Player mockPlayerWithName(String name, boolean npc) {
Player player = mock(Player.class);
given(player.getName()).willReturn(name);
given(player.hasMetadata("NPC")).willReturn(npc);
return player;
}