mirror of
https://github.com/LuckPerms/LuckPerms.git
synced 2024-12-29 12:37:40 +01:00
Fix some concurrency issues with login handling
This commit is contained in:
parent
486b19aa90
commit
8e557d122b
@ -9,7 +9,7 @@ log-error: "&7&l[&bLuck&3Perms&7&l] &4[ERROR] {0}"
|
||||
empty: "{0}"
|
||||
player-online: "&aOnline"
|
||||
player-offline: "&cOffline"
|
||||
loading-error: "Permissions data could not be loaded. Please contact an administrator."
|
||||
loading-error: "Permissions data could not be loaded. Please try again later."
|
||||
op-disabled: "&bThe vanilla OP system is disabled on this server."
|
||||
op-disabled-sponge: "&2Server Operator status has no effect when a permission plugin is installed. Please edit user data directly."
|
||||
log: "&3LOG &3&l> {0}"
|
||||
|
@ -22,12 +22,15 @@
|
||||
|
||||
package me.lucko.luckperms.bukkit;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import me.lucko.luckperms.bukkit.model.Injector;
|
||||
import me.lucko.luckperms.bukkit.model.LPPermissible;
|
||||
import me.lucko.luckperms.common.caching.UserCache;
|
||||
import me.lucko.luckperms.common.config.ConfigKeys;
|
||||
import me.lucko.luckperms.common.constants.Message;
|
||||
import me.lucko.luckperms.common.core.model.User;
|
||||
import me.lucko.luckperms.common.utils.AbstractListener;
|
||||
import me.lucko.luckperms.common.utils.LoginHelper;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@ -39,62 +42,78 @@ import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.server.PluginEnableEvent;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
class BukkitListener extends AbstractListener implements Listener {
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitListener implements Listener {
|
||||
private final LPBukkitPlugin plugin;
|
||||
|
||||
private final Set<UUID> deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>());
|
||||
private final Set<UUID> deniedLogin = new HashSet<>();
|
||||
|
||||
private final Map<UUID, BukkitTask> cleanupTasks = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
BukkitListener(LPBukkitPlugin plugin) {
|
||||
super(plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
private final Set<UUID> deniedLogin = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onPlayerPreLogin(AsyncPlayerPreLoginEvent e) {
|
||||
/* Called when the player first attempts a connection with the server.
|
||||
Listening on LOW priority to allow plugins to modify username / UUID data here. (auth plugins) */
|
||||
|
||||
/* the player was denied entry to the server before this priority.
|
||||
log this, so we can handle appropriately later. */
|
||||
if (e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
|
||||
plugin.getLog().warn("Connection from " + e.getUniqueId() + " was already denied. No permissions data will be loaded.");
|
||||
deniedAsyncLogin.add(e.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
/* either the plugin hasn't finished starting yet, or there was an issue connecting to the DB, performing file i/o, etc.
|
||||
we don't let players join in this case, because it means they can connect to the server without their permissions data.
|
||||
some server admins rely on negating perms to stop users from causing damage etc, so it's really important that
|
||||
this data is loaded. */
|
||||
if (!plugin.isStarted() || !plugin.getStorage().isAcceptingLogins()) {
|
||||
|
||||
// log that the user tried to login, but was denied at this stage.
|
||||
deniedAsyncLogin.add(e.getUniqueId());
|
||||
|
||||
// The datastore is disabled, prevent players from joining the server
|
||||
plugin.getLog().warn("The plugin storage is not loaded. Denying connection from: " + e.getUniqueId() + " - " + e.getName());
|
||||
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.toString());
|
||||
// actually deny the connection.
|
||||
plugin.getLog().warn("Permissions storage is not loaded yet. Denying connection from: " + e.getUniqueId() + " - " + e.getName());
|
||||
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.asString(plugin.getLocaleManager()));
|
||||
return;
|
||||
}
|
||||
|
||||
// remove any pending cleanup tasks
|
||||
BukkitTask task = cleanupTasks.remove(e.getUniqueId());
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
/* Actually process the login for the connection.
|
||||
We do this here to delay the login until the data is ready.
|
||||
If the login gets cancelled later on, then this will be cleaned up.
|
||||
|
||||
This includes:
|
||||
- loading uuid data
|
||||
- loading permissions
|
||||
- creating a user instance in the UserManager for this connection.
|
||||
- setting up cached data. */
|
||||
try {
|
||||
LoginHelper.loadUser(plugin, e.getUniqueId(), e.getName());
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
|
||||
// there was some error loading data. deny the connection
|
||||
deniedAsyncLogin.add(e.getUniqueId());
|
||||
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.asString(plugin.getLocaleManager()));
|
||||
}
|
||||
}
|
||||
|
||||
// Process login
|
||||
onAsyncLogin(e.getUniqueId(), e.getName());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerPreLoginMonitor(AsyncPlayerPreLoginEvent e) {
|
||||
// If they were denied before/at LOW, then don't bother handling here.
|
||||
/* Listen to see if the event was cancelled after we initially handled the connection
|
||||
If the connection was cancelled here, we need to do something to clean up the data that was loaded. */
|
||||
|
||||
// Check to see if this connection was denied at LOW.
|
||||
if (deniedAsyncLogin.remove(e.getUniqueId())) {
|
||||
|
||||
// this is a problem, as they were denied at low priority, but are now being allowed.
|
||||
// This is a problem, as they were denied at low priority, but are now being allowed.
|
||||
if (e.getLoginResult() == AsyncPlayerPreLoginEvent.Result.ALLOWED) {
|
||||
new IllegalStateException("Player connection was re-allowed for " + e.getUniqueId()).printStackTrace();
|
||||
plugin.getLog().severe("Player connection was re-allowed for " + e.getUniqueId());
|
||||
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "");
|
||||
}
|
||||
|
||||
@ -102,14 +121,22 @@ class BukkitListener extends AbstractListener implements Listener {
|
||||
}
|
||||
|
||||
// Login event was cancelled by another plugin
|
||||
if (plugin.isStarted() && plugin.getStorage().isAcceptingLogins() && e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
|
||||
cleanupUser(e.getUniqueId());
|
||||
if (e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
|
||||
// Schedule cleanup of this user.
|
||||
plugin.getUserManager().scheduleUnload(e.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onPlayerLogin(PlayerLoginEvent e) {
|
||||
/* Called when the player starts logging into the server.
|
||||
At this point, the users data should be present and loaded.
|
||||
Listening on LOW priority to allow plugins to further modify data here. (auth plugins, etc.) */
|
||||
|
||||
/* the player was denied entry to the server before this priority.
|
||||
log this, so we can handle appropriately later. */
|
||||
if (e.getResult() != PlayerLoginEvent.Result.ALLOWED) {
|
||||
plugin.getLog().warn("Login from " + e.getPlayer().getUniqueId() + " was denied before an attachment could be injected.");
|
||||
deniedLogin.add(e.getPlayer().getUniqueId());
|
||||
return;
|
||||
}
|
||||
@ -117,21 +144,17 @@ class BukkitListener extends AbstractListener implements Listener {
|
||||
final Player player = e.getPlayer();
|
||||
final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(player.getUniqueId()));
|
||||
|
||||
/* User instance is null for whatever reason. Could be that it was unloaded between asyncpre and now. */
|
||||
if (user == null) {
|
||||
deniedLogin.add(e.getPlayer().getUniqueId());
|
||||
|
||||
// User wasn't loaded for whatever reason.
|
||||
plugin.getLog().warn("User " + player.getUniqueId() + " - " + player.getName() + " could not be loaded. - denying login.");
|
||||
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.toString());
|
||||
plugin.getLog().warn("User " + player.getUniqueId() + " - " + player.getName() + " doesn't have data pre-loaded. - denying login.");
|
||||
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.asString(plugin.getLocaleManager()));
|
||||
return;
|
||||
}
|
||||
|
||||
// remove any pending cleanup tasks
|
||||
BukkitTask task = cleanupTasks.remove(e.getPlayer().getUniqueId());
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
}
|
||||
|
||||
// User instance is there, now we can inject our custom Permissible into the player.
|
||||
// Care should be taken at this stage to ensure that async tasks which manipulate bukkit data check that the player is still online.
|
||||
try {
|
||||
// Make a new permissible for the user
|
||||
LPPermissible lpPermissible = new LPPermissible(player, user, plugin);
|
||||
@ -147,30 +170,43 @@ class BukkitListener extends AbstractListener implements Listener {
|
||||
|
||||
// We assume all users are not op, but those who are need extra calculation.
|
||||
if (player.isOp()) {
|
||||
plugin.doAsync(() -> user.getUserData().preCalculate(plugin.getPreProcessContexts(true)));
|
||||
plugin.doAsync(() -> {
|
||||
UserCache userData = user.getUserData();
|
||||
if (userData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
userData.preCalculate(plugin.getPreProcessContexts(true));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerLoginMonitor(PlayerLoginEvent e) {
|
||||
// If they were denied before/at LOW, then don't bother handling here.
|
||||
/* Listen to see if the event was cancelled after we initially handled the login
|
||||
If the connection was cancelled here, we need to do something to clean up the data that was loaded. */
|
||||
|
||||
// Check to see if this connection was denied at LOW.
|
||||
if (deniedLogin.remove(e.getPlayer().getUniqueId())) {
|
||||
|
||||
// this is a problem, as they were denied at low priority, but are now being allowed.
|
||||
// This is a problem, as they were denied at low priority, but are now being allowed.
|
||||
if (e.getResult() == PlayerLoginEvent.Result.ALLOWED) {
|
||||
new IllegalStateException("Player connection was re-allowed for " + e.getPlayer().getUniqueId()).printStackTrace();
|
||||
plugin.getLog().severe("Player connection was re-allowed for " + e.getPlayer().getUniqueId());
|
||||
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, "");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Login event was cancelled by another plugin
|
||||
if (e.getResult() != PlayerLoginEvent.Result.ALLOWED) {
|
||||
// The player got denied on sync login.
|
||||
cleanupUser(e.getPlayer().getUniqueId());
|
||||
} else {
|
||||
plugin.refreshAutoOp(e.getPlayer());
|
||||
// Schedule cleanup of this user.
|
||||
plugin.getUserManager().scheduleUnload(e.getPlayer().getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
// everything is going well. login was processed ok, this is just to refresh auto-op status.
|
||||
plugin.refreshAutoOp(e.getPlayer());
|
||||
}
|
||||
|
||||
// Wait until the last priority to unload, so plugins can still perform permission checks on this event
|
||||
@ -186,21 +222,8 @@ class BukkitListener extends AbstractListener implements Listener {
|
||||
player.setOp(false);
|
||||
}
|
||||
|
||||
// Call internal leave handling
|
||||
onLeave(player.getUniqueId());
|
||||
}
|
||||
|
||||
private void cleanupUser(UUID uuid) {
|
||||
if (cleanupTasks.containsKey(uuid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BukkitTask task = plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
|
||||
onLeave(uuid);
|
||||
cleanupTasks.remove(uuid);
|
||||
}, 60L);
|
||||
|
||||
cleanupTasks.put(uuid, task);
|
||||
// Request that the users data is unloaded.
|
||||
plugin.getUserManager().scheduleUnload(player.getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@ -209,7 +232,7 @@ class BukkitListener extends AbstractListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
String s = e.getMessage()
|
||||
String s = e.getMessage().toLowerCase()
|
||||
.replace("/", "")
|
||||
.replace("bukkit:", "")
|
||||
.replace("spigot:", "")
|
||||
@ -217,7 +240,7 @@ class BukkitListener extends AbstractListener implements Listener {
|
||||
|
||||
if (s.equals("op") || s.startsWith("op ") || s.equals("deop") || s.startsWith("deop ")) {
|
||||
e.setCancelled(true);
|
||||
e.getPlayer().sendMessage(Message.OP_DISABLED.toString());
|
||||
e.getPlayer().sendMessage(Message.OP_DISABLED.asString(plugin.getLocaleManager()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,7 @@ import me.lucko.luckperms.bukkit.model.LPPermissible;
|
||||
import me.lucko.luckperms.bukkit.vault.VaultHook;
|
||||
import me.lucko.luckperms.common.api.ApiHandler;
|
||||
import me.lucko.luckperms.common.api.ApiProvider;
|
||||
import me.lucko.luckperms.common.caching.UserCache;
|
||||
import me.lucko.luckperms.common.caching.handlers.CachedStateManager;
|
||||
import me.lucko.luckperms.common.calculators.CalculatorFactory;
|
||||
import me.lucko.luckperms.common.commands.sender.Sender;
|
||||
@ -73,6 +74,7 @@ import me.lucko.luckperms.common.treeview.PermissionVault;
|
||||
import me.lucko.luckperms.common.utils.BufferedRequest;
|
||||
import me.lucko.luckperms.common.utils.FileWatcher;
|
||||
import me.lucko.luckperms.common.utils.LoggerImpl;
|
||||
import me.lucko.luckperms.common.utils.LoginHelper;
|
||||
import me.lucko.luckperms.common.verbose.VerboseHandler;
|
||||
|
||||
import org.bukkit.World;
|
||||
@ -302,7 +304,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
|
||||
// Load any online users (in the case of a reload)
|
||||
for (Player player : getServer().getOnlinePlayers()) {
|
||||
scheduler.doAsync(() -> {
|
||||
listener.onAsyncLogin(player.getUniqueId(), player.getName());
|
||||
LoginHelper.loadUser(this, player.getUniqueId(), player.getName());
|
||||
User user = getUserManager().get(getUuidCache().getUUID(player.getUniqueId()));
|
||||
if (user != null) {
|
||||
scheduler.doSync(() -> {
|
||||
@ -431,11 +433,21 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
|
||||
if (getConfiguration().get(ConfigKeys.AUTO_OP)) {
|
||||
try {
|
||||
LPPermissible permissible = Injector.getPermissible(player.getUniqueId());
|
||||
if (permissible == null) {
|
||||
if (permissible == null || !permissible.getActive().get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Boolean> backing = permissible.getUser().getUserData().getPermissionData(permissible.calculateContexts()).getImmutableBacking();
|
||||
User user = permissible.getUser();
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
UserCache userData = user.getUserData();
|
||||
if (userData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Boolean> backing = userData.getPermissionData(permissible.calculateContexts()).getImmutableBacking();
|
||||
boolean op = Optional.ofNullable(backing.get("luckperms.autoop")).orElse(false);
|
||||
player.setOp(op);
|
||||
} catch (Exception ignored) {}
|
||||
@ -511,7 +523,8 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
|
||||
|
||||
@Override
|
||||
public boolean isPlayerOnline(UUID external) {
|
||||
return getServer().getPlayer(external) != null;
|
||||
Player player = getServer().getPlayer(external);
|
||||
return player != null && player.isOnline();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -64,7 +64,7 @@ public class Injector {
|
||||
PermissibleBase existing = (PermissibleBase) HUMAN_ENTITY_FIELD.get(player);
|
||||
if (existing instanceof LPPermissible) {
|
||||
// uh oh
|
||||
throw new IllegalStateException();
|
||||
throw new IllegalStateException("LPPermissible already injected into player " + player.toString());
|
||||
}
|
||||
|
||||
// Move attachments over from the old permissible.
|
||||
@ -73,6 +73,7 @@ public class Injector {
|
||||
attachments.clear();
|
||||
existing.clearPermissions();
|
||||
|
||||
lpPermissible.getActive().set(true);
|
||||
lpPermissible.recalculatePermissions();
|
||||
lpPermissible.setOldPermissible(existing);
|
||||
|
||||
@ -98,6 +99,8 @@ public class Injector {
|
||||
((LPPermissible) permissible).unsubscribeFromAllAsync();
|
||||
}
|
||||
|
||||
((LPPermissible) permissible).getActive().set(false);
|
||||
|
||||
if (dummy) {
|
||||
HUMAN_ENTITY_FIELD.set(player, new DummyPermissibleBase());
|
||||
} else {
|
||||
|
@ -49,6 +49,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -66,6 +67,8 @@ public class LPPermissible extends PermissibleBase {
|
||||
@Setter
|
||||
private PermissibleBase oldPermissible = null;
|
||||
|
||||
private final AtomicBoolean active = new AtomicBoolean(false);
|
||||
|
||||
// Attachment stuff.
|
||||
private final Map<String, PermissionAttachmentInfo> attachmentPermissions = new ConcurrentHashMap<>();
|
||||
private final List<PermissionAttachment> attachments = Collections.synchronizedList(new LinkedList<>());
|
||||
@ -81,10 +84,18 @@ public class LPPermissible extends PermissibleBase {
|
||||
}
|
||||
|
||||
public void updateSubscriptionsAsync() {
|
||||
if (!active.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.doAsync(this::updateSubscriptions);
|
||||
}
|
||||
|
||||
public void updateSubscriptions() {
|
||||
if (!active.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UserCache cache = user.getUserData();
|
||||
if (cache == null) {
|
||||
return;
|
||||
@ -110,9 +121,7 @@ public class LPPermissible extends PermissibleBase {
|
||||
}
|
||||
|
||||
public void addAttachments(List<PermissionAttachment> attachments) {
|
||||
for (PermissionAttachment attachment : attachments) {
|
||||
this.attachments.add(attachment);
|
||||
}
|
||||
this.attachments.addAll(attachments);
|
||||
}
|
||||
|
||||
public Contexts calculateContexts() {
|
||||
@ -128,7 +137,7 @@ public class LPPermissible extends PermissibleBase {
|
||||
}
|
||||
|
||||
private boolean hasData() {
|
||||
return user.getUserData() != null;
|
||||
return user != null && user.getUserData() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -22,6 +22,8 @@
|
||||
|
||||
package me.lucko.luckperms.bungee;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import me.lucko.luckperms.api.Contexts;
|
||||
import me.lucko.luckperms.api.caching.UserData;
|
||||
import me.lucko.luckperms.api.context.MutableContextSet;
|
||||
@ -30,7 +32,6 @@ import me.lucko.luckperms.common.constants.Message;
|
||||
import me.lucko.luckperms.common.core.UuidCache;
|
||||
import me.lucko.luckperms.common.core.model.User;
|
||||
import me.lucko.luckperms.common.defaults.Rule;
|
||||
import me.lucko.luckperms.common.utils.AbstractListener;
|
||||
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
@ -44,63 +45,62 @@ import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.event.EventPriority;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class BungeeListener extends AbstractListener implements Listener {
|
||||
private static final TextComponent WARN_MESSAGE = new TextComponent(Message.LOADING_ERROR.toString());
|
||||
@RequiredArgsConstructor
|
||||
public class BungeeListener implements Listener {
|
||||
private final LPBungeePlugin plugin;
|
||||
|
||||
BungeeListener(LPBungeePlugin plugin) {
|
||||
super(plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
private final Set<UUID> deniedLogin = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH)
|
||||
public void onPlayerPermissionCheck(PermissionCheckEvent e) {
|
||||
if (!(e.getSender() instanceof ProxiedPlayer)) {
|
||||
e.setHasPermission(true);
|
||||
return;
|
||||
}
|
||||
|
||||
final ProxiedPlayer player = ((ProxiedPlayer) e.getSender());
|
||||
|
||||
User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(player.getUniqueId()));
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
UserData userData = user.getUserData();
|
||||
if (userData == null) {
|
||||
plugin.getLog().warn("Player " + player.getName() + " does not have any user data setup.");
|
||||
return;
|
||||
}
|
||||
|
||||
Contexts contexts = new Contexts(
|
||||
plugin.getContextManager().getApplicableContext(player),
|
||||
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
|
||||
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
|
||||
true,
|
||||
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
|
||||
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
|
||||
false
|
||||
);
|
||||
|
||||
e.setHasPermission(userData.getPermissionData(contexts).getPermissionValue(e.getPermission()).asBoolean());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onPlayerLogin(LoginEvent e) {
|
||||
/* Delay the login here, as we want to cache UUID data before the player is connected to a backend bukkit server.
|
||||
/* Called when the player first attempts a connection with the server.
|
||||
Listening on LOW priority to allow plugins to modify username / UUID data here. (auth plugins)
|
||||
|
||||
Delay the login here, as we want to cache UUID data before the player is connected to a backend bukkit server.
|
||||
This means that a player will have the same UUID across the network, even if parts of the network are running in
|
||||
Offline mode. */
|
||||
|
||||
// registers the plugins intent to modify this events state going forward.
|
||||
// this will prevent the event from completing until we're finished handling.
|
||||
e.registerIntent(plugin);
|
||||
plugin.doAsync(() -> {
|
||||
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final UuidCache cache = plugin.getUuidCache();
|
||||
final PendingConnection c = e.getConnection();
|
||||
|
||||
/* either the plugin hasn't finished starting yet, or there was an issue connecting to the DB, performing file i/o, etc.
|
||||
as this is bungeecord, we will still allow the login, as players can't really do much harm without permissions data.
|
||||
the proxy will just fallback to using the config file perms. */
|
||||
if (!plugin.getStorage().isAcceptingLogins()) {
|
||||
// log that the user tried to login, but was denied at this stage.
|
||||
deniedLogin.add(c.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
/* another plugin (or the proxy itself) has cancelled this connection already */
|
||||
if (e.isCancelled()) {
|
||||
plugin.getLog().warn("Connection from " + c.getUniqueId() + " was already denied. No permissions data will be loaded.");
|
||||
deniedLogin.add(c.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
/* Actually process the login for the connection.
|
||||
We do this here to delay the login until the data is ready.
|
||||
If the login gets cancelled later on, then this will be cleaned up.
|
||||
|
||||
This includes:
|
||||
- loading uuid data
|
||||
- loading permissions
|
||||
- creating a user instance in the UserManager for this connection.
|
||||
- setting up cached data. */
|
||||
plugin.doAsync(() -> {
|
||||
final UuidCache cache = plugin.getUuidCache();
|
||||
|
||||
if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUIDS)) {
|
||||
UUID uuid = plugin.getStorage().getUUID(c.getName()).join();
|
||||
if (uuid != null) {
|
||||
@ -109,6 +109,8 @@ public class BungeeListener extends AbstractListener implements Listener {
|
||||
// No previous data for this player
|
||||
plugin.getApiProvider().getEventFactory().handleUserFirstLogin(c.getUniqueId(), c.getName());
|
||||
cache.addToCache(c.getUniqueId(), c.getUniqueId());
|
||||
|
||||
// Join this call, as we want this to be set for when the player connects to the backend.
|
||||
plugin.getStorage().force().saveUUIDData(c.getName(), c.getUniqueId()).join();
|
||||
}
|
||||
} else {
|
||||
@ -118,12 +120,14 @@ public class BungeeListener extends AbstractListener implements Listener {
|
||||
}
|
||||
|
||||
// Online mode, no cache needed. This is just for name -> uuid lookup.
|
||||
// Again, join this call so the data is available for the backend.
|
||||
plugin.getStorage().force().saveUUIDData(c.getName(), c.getUniqueId()).join();
|
||||
}
|
||||
|
||||
// We have to make a new user on this thread whilst the connection is being held, or we get concurrency issues as the Bukkit server
|
||||
// and the BungeeCord server try to make a new user at the same time.
|
||||
/* We have to make a new user on this thread whilst the connection is being held, or we get concurrency issues
|
||||
as the Bukkit server and the BungeeCord server try to make a new user at the same time. */
|
||||
plugin.getStorage().force().loadUser(cache.getUUID(c.getUniqueId()), c.getName()).join();
|
||||
|
||||
User user = plugin.getUserManager().get(cache.getUUID(c.getUniqueId()));
|
||||
if (user == null) {
|
||||
plugin.getLog().warn("Failed to load user: " + c.getName());
|
||||
@ -148,7 +152,13 @@ public class BungeeListener extends AbstractListener implements Listener {
|
||||
if (time >= 1000) {
|
||||
plugin.getLog().warn("Processing login for " + c.getName() + " took " + time + "ms.");
|
||||
}
|
||||
|
||||
// finally, complete out intent to modify state, so the proxy can continue handling the connection.
|
||||
e.completeIntent(plugin);
|
||||
|
||||
// schedule a cleanup of the users data in a few seconds.
|
||||
// this should cover the eventuality that the login fails.
|
||||
plugin.getUserManager().scheduleUnload(c.getUniqueId());
|
||||
});
|
||||
}
|
||||
|
||||
@ -158,17 +168,57 @@ public class BungeeListener extends AbstractListener implements Listener {
|
||||
final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(e.getPlayer().getUniqueId()));
|
||||
|
||||
if (user == null) {
|
||||
plugin.getProxy().getScheduler().schedule(plugin, () -> player.sendMessage(WARN_MESSAGE), 3, TimeUnit.SECONDS);
|
||||
plugin.getProxy().getScheduler().schedule(plugin, () -> {
|
||||
if (!player.isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.sendMessage(new TextComponent(Message.LOADING_ERROR.asString(plugin.getLocaleManager())));
|
||||
}, 3, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until the last priority to unload, so plugins can still perform permission checks on this event
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPlayerQuit(PlayerDisconnectEvent e) {
|
||||
onLeave(e.getPlayer().getUniqueId());
|
||||
// Request that the users data is unloaded.
|
||||
plugin.getUserManager().scheduleUnload(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
// We don't preprocess all servers, so we may have to do it here.
|
||||
@EventHandler(priority = EventPriority.HIGH)
|
||||
public void onPlayerPermissionCheck(PermissionCheckEvent e) {
|
||||
if (!(e.getSender() instanceof ProxiedPlayer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ProxiedPlayer player = ((ProxiedPlayer) e.getSender());
|
||||
|
||||
User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(player.getUniqueId()));
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
UserData userData = user.getUserData();
|
||||
if (userData == null) {
|
||||
plugin.getLog().warn("Player " + player.getName() + " does not have any user data setup.");
|
||||
plugin.doAsync(() -> user.setupData(false));
|
||||
return;
|
||||
}
|
||||
|
||||
Contexts contexts = new Contexts(
|
||||
plugin.getContextManager().getApplicableContext(player),
|
||||
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
|
||||
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
|
||||
true,
|
||||
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
|
||||
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
|
||||
false
|
||||
);
|
||||
|
||||
e.setHasPermission(userData.getPermissionData(contexts).getPermissionValue(e.getPermission()).asBoolean());
|
||||
}
|
||||
|
||||
// We don't pre-process all servers, so we have to do it here.
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onServerSwitch(ServerConnectEvent e) {
|
||||
String serverName = e.getTarget().getName();
|
||||
|
@ -323,7 +323,8 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
|
||||
|
||||
@Override
|
||||
public boolean isPlayerOnline(UUID external) {
|
||||
return getProxy().getPlayer(external) != null;
|
||||
ProxiedPlayer player = getProxy().getPlayer(external);
|
||||
return player != null && player.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -27,6 +27,7 @@ import lombok.Getter;
|
||||
|
||||
import me.lucko.luckperms.common.commands.sender.Sender;
|
||||
import me.lucko.luckperms.common.commands.utils.Util;
|
||||
import me.lucko.luckperms.common.locale.LocaleManager;
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@AllArgsConstructor
|
||||
@ -44,7 +45,7 @@ public enum Message {
|
||||
EMPTY("{0}", true),
|
||||
PLAYER_ONLINE("&aOnline", false),
|
||||
PLAYER_OFFLINE("&cOffline", false),
|
||||
LOADING_ERROR("Permissions data could not be loaded. Please contact an administrator.", true),
|
||||
LOADING_ERROR("Permissions data could not be loaded. Please try again later.", true),
|
||||
OP_DISABLED("&bThe vanilla OP system is disabled on this server.", false),
|
||||
OP_DISABLED_SPONGE("&2Server Operator status has no effect when a permission plugin is installed. Please edit user data directly.", true),
|
||||
LOG("&3LOG &3&l> {0}", true),
|
||||
@ -452,6 +453,21 @@ public enum Message {
|
||||
return Util.color(showPrefix ? PREFIX + message : message);
|
||||
}
|
||||
|
||||
public String asString(LocaleManager localeManager) {
|
||||
String prefix = localeManager.getTranslation(PREFIX);
|
||||
if (prefix == null) {
|
||||
prefix = PREFIX.getMessage();
|
||||
}
|
||||
|
||||
String s = localeManager.getTranslation(this);
|
||||
if (s == null) {
|
||||
s = message;
|
||||
}
|
||||
|
||||
s = s.replace("{PREFIX}", prefix).replace("\\n", "\n");
|
||||
return Util.color(showPrefix ? (prefix + s) : (s));
|
||||
}
|
||||
|
||||
public void send(Sender sender, Object... objects) {
|
||||
String prefix = sender.getPlatform().getLocaleManager().getTranslation(PREFIX);
|
||||
if (prefix == null) {
|
||||
|
@ -60,6 +60,13 @@ public interface UserManager extends Manager<UserIdentifier, User> {
|
||||
*/
|
||||
void cleanup(User user);
|
||||
|
||||
/**
|
||||
* Schedules a task to cleanup a user after a certain period of time, if they're not on the server anymore.
|
||||
*
|
||||
* @param uuid external uuid of the player
|
||||
*/
|
||||
void scheduleUnload(UUID uuid);
|
||||
|
||||
/**
|
||||
* Reloads the data of all online users
|
||||
*/
|
||||
|
@ -134,6 +134,18 @@ public class GenericUserManager extends AbstractManager<UserIdentifier, User> im
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleUnload(UUID uuid) {
|
||||
plugin.getScheduler().doAsyncLater(() -> {
|
||||
User user = get(plugin.getUuidCache().getUUID(uuid));
|
||||
if (user != null && !plugin.isPlayerOnline(uuid)) {
|
||||
user.unregisterData();
|
||||
unload(user);
|
||||
}
|
||||
plugin.getUuidCache().clearCache(uuid);
|
||||
}, 40L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAllUsers() {
|
||||
plugin.doSync(() -> {
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
package me.lucko.luckperms.common.utils;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import me.lucko.luckperms.common.config.ConfigKeys;
|
||||
import me.lucko.luckperms.common.core.UuidCache;
|
||||
@ -33,13 +33,12 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* An abstract listener shared by Bukkit & Sponge.
|
||||
* Utilities for use in platform listeners
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class AbstractListener {
|
||||
private final LuckPermsPlugin plugin;
|
||||
@UtilityClass
|
||||
public class LoginHelper {
|
||||
|
||||
public void onAsyncLogin(UUID u, String username) {
|
||||
public static void loadUser(LuckPermsPlugin plugin, UUID u, String username) {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
|
||||
final UuidCache cache = plugin.getUuidCache();
|
||||
@ -67,6 +66,7 @@ public class AbstractListener {
|
||||
User user = plugin.getUserManager().get(cache.getUUID(u));
|
||||
if (user == null) {
|
||||
plugin.getLog().warn("Failed to load user: " + username);
|
||||
throw new RuntimeException("Failed to load user");
|
||||
} else {
|
||||
// Setup defaults for the user
|
||||
boolean save = false;
|
||||
@ -90,20 +90,7 @@ public class AbstractListener {
|
||||
}
|
||||
}
|
||||
|
||||
protected void onLeave(UUID uuid) {
|
||||
final UuidCache cache = plugin.getUuidCache();
|
||||
|
||||
final User user = plugin.getUserManager().get(cache.getUUID(uuid));
|
||||
if (user != null) {
|
||||
user.unregisterData();
|
||||
plugin.getUserManager().unload(user);
|
||||
}
|
||||
|
||||
// Unload the user from memory when they disconnect;
|
||||
cache.clearCache(uuid);
|
||||
}
|
||||
|
||||
protected void refreshPlayer(UUID uuid) {
|
||||
public static void refreshPlayer(LuckPermsPlugin plugin, UUID uuid) {
|
||||
final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(uuid));
|
||||
if (user != null) {
|
||||
user.getRefreshBuffer().requestDirectly();
|
@ -440,7 +440,7 @@ public class LPSpongePlugin implements LuckPermsPlugin {
|
||||
|
||||
@Override
|
||||
public boolean isPlayerOnline(UUID external) {
|
||||
return game.getServer().getPlayer(external).isPresent();
|
||||
return game.getServer().getPlayer(external).map(Player::isOnline).orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -22,12 +22,14 @@
|
||||
|
||||
package me.lucko.luckperms.sponge;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import me.lucko.luckperms.api.caching.UserData;
|
||||
import me.lucko.luckperms.api.context.MutableContextSet;
|
||||
import me.lucko.luckperms.common.constants.Message;
|
||||
import me.lucko.luckperms.common.core.UuidCache;
|
||||
import me.lucko.luckperms.common.core.model.User;
|
||||
import me.lucko.luckperms.common.utils.AbstractListener;
|
||||
import me.lucko.luckperms.common.utils.LoginHelper;
|
||||
import me.lucko.luckperms.sponge.timings.LPTiming;
|
||||
|
||||
import org.spongepowered.api.command.CommandSource;
|
||||
@ -35,49 +37,131 @@ import org.spongepowered.api.entity.living.player.Player;
|
||||
import org.spongepowered.api.event.Listener;
|
||||
import org.spongepowered.api.event.Order;
|
||||
import org.spongepowered.api.event.command.SendCommandEvent;
|
||||
import org.spongepowered.api.event.filter.IsCancelled;
|
||||
import org.spongepowered.api.event.network.ClientConnectionEvent;
|
||||
import org.spongepowered.api.profile.GameProfile;
|
||||
import org.spongepowered.api.text.serializer.TextSerializers;
|
||||
import org.spongepowered.api.util.Tristate;
|
||||
import org.spongepowered.api.world.World;
|
||||
|
||||
import co.aikar.timings.Timing;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class SpongeListener extends AbstractListener {
|
||||
@RequiredArgsConstructor
|
||||
public class SpongeListener {
|
||||
private final LPSpongePlugin plugin;
|
||||
|
||||
SpongeListener(LPSpongePlugin plugin) {
|
||||
super(plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
private final Set<UUID> deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>());
|
||||
private final Set<UUID> deniedLogin = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
@Listener(order = Order.EARLY)
|
||||
@Listener(order = Order.AFTER_PRE)
|
||||
@IsCancelled(Tristate.UNDEFINED)
|
||||
public void onClientAuth(ClientConnectionEvent.Auth e) {
|
||||
if (!plugin.getStorage().isAcceptingLogins()) {
|
||||
/* Datastore is disabled, prevent players from joining the server
|
||||
Just don't load their data, they will be kicked at login */
|
||||
/* Called when the player first attempts a connection with the server.
|
||||
Listening on AFTER_PRE priority to allow plugins to modify username / UUID data here. (auth plugins) */
|
||||
|
||||
final GameProfile p = e.getProfile();
|
||||
|
||||
/* the player was denied entry to the server before this priority.
|
||||
log this, so we can handle appropriately later. */
|
||||
if (e.isCancelled()) {
|
||||
plugin.getLog().warn("Connection from " + p.getUniqueId() + " was already denied. No permissions data will be loaded.");
|
||||
deniedAsyncLogin.add(p.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
final GameProfile p = e.getProfile();
|
||||
onAsyncLogin(p.getUniqueId(), p.getName().get()); // Load the user into LuckPerms
|
||||
/* either the plugin hasn't finished starting yet, or there was an issue connecting to the DB, performing file i/o, etc.
|
||||
we don't let players join in this case, because it means they can connect to the server without their permissions data.
|
||||
some server admins rely on negating perms to stop users from causing damage etc, so it's really important that
|
||||
this data is loaded. */
|
||||
if (!plugin.getStorage().isAcceptingLogins()) {
|
||||
|
||||
// log that the user tried to login, but was denied at this stage.
|
||||
deniedAsyncLogin.add(p.getUniqueId());
|
||||
|
||||
// actually deny the connection.
|
||||
plugin.getLog().warn("Permissions storage is not loaded yet. Denying connection from: " + p.getUniqueId() + " - " + p.getName());
|
||||
e.setCancelled(true);
|
||||
e.setMessageCancelled(true);
|
||||
//noinspection deprecation
|
||||
e.setMessage(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(Message.LOADING_ERROR.asString(plugin.getLocaleManager())));
|
||||
return;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Listener(order = Order.EARLY)
|
||||
/* Actually process the login for the connection.
|
||||
We do this here to delay the login until the data is ready.
|
||||
If the login gets cancelled later on, then this will be cleaned up.
|
||||
|
||||
This includes:
|
||||
- loading uuid data
|
||||
- loading permissions
|
||||
- creating a user instance in the UserManager for this connection.
|
||||
- setting up cached data. */
|
||||
try {
|
||||
LoginHelper.loadUser(plugin, p.getUniqueId(), p.getName().orElseThrow(() -> new RuntimeException("No username present for user " + p.getUniqueId())));
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
|
||||
e.setCancelled(true);
|
||||
e.setMessageCancelled(true);
|
||||
//noinspection deprecation
|
||||
e.setMessage(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(Message.LOADING_ERROR.asString(plugin.getLocaleManager())));
|
||||
}
|
||||
}
|
||||
|
||||
@Listener(order = Order.BEFORE_POST)
|
||||
@IsCancelled(Tristate.UNDEFINED)
|
||||
public void onClientAuthMonitor(ClientConnectionEvent.Auth e) {
|
||||
/* Listen to see if the event was cancelled after we initially handled the connection
|
||||
If the connection was cancelled here, we need to do something to clean up the data that was loaded. */
|
||||
|
||||
// Check to see if this connection was denied at LOW.
|
||||
if (deniedAsyncLogin.remove(e.getProfile().getUniqueId())) {
|
||||
|
||||
// This is a problem, as they were denied at low priority, but are now being allowed.
|
||||
if (e.isCancelled()) {
|
||||
plugin.getLog().severe("Player connection was re-allowed for " + e.getProfile().getUniqueId());
|
||||
e.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Listener(order = Order.AFTER_PRE)
|
||||
@IsCancelled(Tristate.UNDEFINED)
|
||||
public void onClientLogin(ClientConnectionEvent.Login e) {
|
||||
try (Timing ignored = plugin.getTimings().time(LPTiming.ON_CLIENT_LOGIN)) {
|
||||
/* Called when the player starts logging into the server.
|
||||
At this point, the users data should be present and loaded.
|
||||
Listening on LOW priority to allow plugins to further modify data here. (auth plugins, etc.) */
|
||||
|
||||
final GameProfile player = e.getProfile();
|
||||
|
||||
/* the player was denied entry to the server before this priority.
|
||||
log this, so we can handle appropriately later. */
|
||||
if (e.isCancelled()) {
|
||||
plugin.getLog().warn("Login from " + player.getUniqueId() + " was denied before an attachment could be injected.");
|
||||
deniedLogin.add(player.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(player.getUniqueId()));
|
||||
|
||||
// Check if the user was loaded successfully.
|
||||
/* User instance is null for whatever reason. Could be that it was unloaded between asyncpre and now. */
|
||||
if (user == null) {
|
||||
deniedLogin.add(player.getUniqueId());
|
||||
|
||||
plugin.getLog().warn("User " + player.getUniqueId() + " - " + player.getName() + " doesn't have data pre-loaded. - denying login.");
|
||||
e.setCancelled(true);
|
||||
e.setMessage(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(Message.LOADING_ERROR.toString()));
|
||||
e.setMessageCancelled(true);
|
||||
//noinspection deprecation
|
||||
e.setMessage(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(Message.LOADING_ERROR.asString(plugin.getLocaleManager())));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -108,7 +192,7 @@ public class SpongeListener extends AbstractListener {
|
||||
@Listener(order = Order.EARLY)
|
||||
public void onClientJoin(ClientConnectionEvent.Join e) {
|
||||
// Refresh permissions again
|
||||
plugin.doAsync(() -> refreshPlayer(e.getTargetEntity().getUniqueId()));
|
||||
plugin.doAsync(() -> LoginHelper.refreshPlayer(plugin, e.getTargetEntity().getUniqueId()));
|
||||
}
|
||||
|
||||
@Listener(order = Order.LAST)
|
||||
|
@ -205,6 +205,11 @@ public class SpongeUserManager implements UserManager, LPSubjectCollection {
|
||||
// Do nothing - this instance uses other means in order to cleanup
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleUnload(UUID uuid) {
|
||||
// Do nothing - this instance uses other means in order to cleanup
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAllUsers() {
|
||||
plugin.doSync(() -> {
|
||||
|
Loading…
Reference in New Issue
Block a user