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}"
|
empty: "{0}"
|
||||||
player-online: "&aOnline"
|
player-online: "&aOnline"
|
||||||
player-offline: "&cOffline"
|
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: "&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."
|
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}"
|
log: "&3LOG &3&l> {0}"
|
||||||
|
@ -22,12 +22,15 @@
|
|||||||
|
|
||||||
package me.lucko.luckperms.bukkit;
|
package me.lucko.luckperms.bukkit;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
import me.lucko.luckperms.bukkit.model.Injector;
|
import me.lucko.luckperms.bukkit.model.Injector;
|
||||||
import me.lucko.luckperms.bukkit.model.LPPermissible;
|
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.config.ConfigKeys;
|
||||||
import me.lucko.luckperms.common.constants.Message;
|
import me.lucko.luckperms.common.constants.Message;
|
||||||
import me.lucko.luckperms.common.core.model.User;
|
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.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
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.PlayerLoginEvent;
|
||||||
import org.bukkit.event.player.PlayerQuitEvent;
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
import org.bukkit.event.server.PluginEnableEvent;
|
import org.bukkit.event.server.PluginEnableEvent;
|
||||||
import org.bukkit.scheduler.BukkitTask;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
class BukkitListener extends AbstractListener implements Listener {
|
@RequiredArgsConstructor
|
||||||
|
public class BukkitListener implements Listener {
|
||||||
private final LPBukkitPlugin plugin;
|
private final LPBukkitPlugin plugin;
|
||||||
|
|
||||||
private final Set<UUID> deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>());
|
private final Set<UUID> deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>());
|
||||||
private final Set<UUID> deniedLogin = new HashSet<>();
|
private final Set<UUID> deniedLogin = Collections.synchronizedSet(new HashSet<>());
|
||||||
|
|
||||||
private final Map<UUID, BukkitTask> cleanupTasks = Collections.synchronizedMap(new HashMap<>());
|
|
||||||
|
|
||||||
BukkitListener(LPBukkitPlugin plugin) {
|
|
||||||
super(plugin);
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.LOW)
|
@EventHandler(priority = EventPriority.LOW)
|
||||||
public void onPlayerPreLogin(AsyncPlayerPreLoginEvent e) {
|
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) {
|
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());
|
deniedAsyncLogin.add(e.getUniqueId());
|
||||||
return;
|
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()) {
|
if (!plugin.isStarted() || !plugin.getStorage().isAcceptingLogins()) {
|
||||||
|
|
||||||
|
// log that the user tried to login, but was denied at this stage.
|
||||||
deniedAsyncLogin.add(e.getUniqueId());
|
deniedAsyncLogin.add(e.getUniqueId());
|
||||||
|
|
||||||
// The datastore is disabled, prevent players from joining the server
|
// actually deny the connection.
|
||||||
plugin.getLog().warn("The plugin storage is not loaded. Denying connection from: " + e.getUniqueId() + " - " + e.getName());
|
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.toString());
|
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.asString(plugin.getLocaleManager()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove any pending cleanup tasks
|
/* Actually process the login for the connection.
|
||||||
BukkitTask task = cleanupTasks.remove(e.getUniqueId());
|
We do this here to delay the login until the data is ready.
|
||||||
if (task != null) {
|
If the login gets cancelled later on, then this will be cleaned up.
|
||||||
task.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process login
|
This includes:
|
||||||
onAsyncLogin(e.getUniqueId(), e.getName());
|
- 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()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
public void onPlayerPreLoginMonitor(AsyncPlayerPreLoginEvent e) {
|
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())) {
|
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) {
|
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, "");
|
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,14 +121,22 @@ class BukkitListener extends AbstractListener implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Login event was cancelled by another plugin
|
// Login event was cancelled by another plugin
|
||||||
if (plugin.isStarted() && plugin.getStorage().isAcceptingLogins() && e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
|
if (e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
|
||||||
cleanupUser(e.getUniqueId());
|
// Schedule cleanup of this user.
|
||||||
|
plugin.getUserManager().scheduleUnload(e.getUniqueId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.LOW)
|
@EventHandler(priority = EventPriority.LOW)
|
||||||
public void onPlayerLogin(PlayerLoginEvent e) {
|
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) {
|
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());
|
deniedLogin.add(e.getPlayer().getUniqueId());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -117,21 +144,17 @@ class BukkitListener extends AbstractListener implements Listener {
|
|||||||
final Player player = e.getPlayer();
|
final Player player = e.getPlayer();
|
||||||
final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(player.getUniqueId()));
|
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) {
|
if (user == null) {
|
||||||
deniedLogin.add(e.getPlayer().getUniqueId());
|
deniedLogin.add(e.getPlayer().getUniqueId());
|
||||||
|
|
||||||
// User wasn't loaded for whatever reason.
|
plugin.getLog().warn("User " + player.getUniqueId() + " - " + player.getName() + " doesn't have data pre-loaded. - denying login.");
|
||||||
plugin.getLog().warn("User " + player.getUniqueId() + " - " + player.getName() + " could not be loaded. - denying login.");
|
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.asString(plugin.getLocaleManager()));
|
||||||
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.toString());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove any pending cleanup tasks
|
// User instance is there, now we can inject our custom Permissible into the player.
|
||||||
BukkitTask task = cleanupTasks.remove(e.getPlayer().getUniqueId());
|
// Care should be taken at this stage to ensure that async tasks which manipulate bukkit data check that the player is still online.
|
||||||
if (task != null) {
|
|
||||||
task.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Make a new permissible for the user
|
// Make a new permissible for the user
|
||||||
LPPermissible lpPermissible = new LPPermissible(player, user, plugin);
|
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.
|
// We assume all users are not op, but those who are need extra calculation.
|
||||||
if (player.isOp()) {
|
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) {
|
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())) {
|
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) {
|
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, "");
|
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Login event was cancelled by another plugin
|
||||||
if (e.getResult() != PlayerLoginEvent.Result.ALLOWED) {
|
if (e.getResult() != PlayerLoginEvent.Result.ALLOWED) {
|
||||||
// The player got denied on sync login.
|
// Schedule cleanup of this user.
|
||||||
cleanupUser(e.getPlayer().getUniqueId());
|
plugin.getUserManager().scheduleUnload(e.getPlayer().getUniqueId());
|
||||||
} else {
|
return;
|
||||||
plugin.refreshAutoOp(e.getPlayer());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// 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);
|
player.setOp(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call internal leave handling
|
// Request that the users data is unloaded.
|
||||||
onLeave(player.getUniqueId());
|
plugin.getUserManager().scheduleUnload(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
@ -209,7 +232,7 @@ class BukkitListener extends AbstractListener implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String s = e.getMessage()
|
String s = e.getMessage().toLowerCase()
|
||||||
.replace("/", "")
|
.replace("/", "")
|
||||||
.replace("bukkit:", "")
|
.replace("bukkit:", "")
|
||||||
.replace("spigot:", "")
|
.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 ")) {
|
if (s.equals("op") || s.startsWith("op ") || s.equals("deop") || s.startsWith("deop ")) {
|
||||||
e.setCancelled(true);
|
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.bukkit.vault.VaultHook;
|
||||||
import me.lucko.luckperms.common.api.ApiHandler;
|
import me.lucko.luckperms.common.api.ApiHandler;
|
||||||
import me.lucko.luckperms.common.api.ApiProvider;
|
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.caching.handlers.CachedStateManager;
|
||||||
import me.lucko.luckperms.common.calculators.CalculatorFactory;
|
import me.lucko.luckperms.common.calculators.CalculatorFactory;
|
||||||
import me.lucko.luckperms.common.commands.sender.Sender;
|
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.BufferedRequest;
|
||||||
import me.lucko.luckperms.common.utils.FileWatcher;
|
import me.lucko.luckperms.common.utils.FileWatcher;
|
||||||
import me.lucko.luckperms.common.utils.LoggerImpl;
|
import me.lucko.luckperms.common.utils.LoggerImpl;
|
||||||
|
import me.lucko.luckperms.common.utils.LoginHelper;
|
||||||
import me.lucko.luckperms.common.verbose.VerboseHandler;
|
import me.lucko.luckperms.common.verbose.VerboseHandler;
|
||||||
|
|
||||||
import org.bukkit.World;
|
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)
|
// Load any online users (in the case of a reload)
|
||||||
for (Player player : getServer().getOnlinePlayers()) {
|
for (Player player : getServer().getOnlinePlayers()) {
|
||||||
scheduler.doAsync(() -> {
|
scheduler.doAsync(() -> {
|
||||||
listener.onAsyncLogin(player.getUniqueId(), player.getName());
|
LoginHelper.loadUser(this, player.getUniqueId(), player.getName());
|
||||||
User user = getUserManager().get(getUuidCache().getUUID(player.getUniqueId()));
|
User user = getUserManager().get(getUuidCache().getUUID(player.getUniqueId()));
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
scheduler.doSync(() -> {
|
scheduler.doSync(() -> {
|
||||||
@ -431,11 +433,21 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
|
|||||||
if (getConfiguration().get(ConfigKeys.AUTO_OP)) {
|
if (getConfiguration().get(ConfigKeys.AUTO_OP)) {
|
||||||
try {
|
try {
|
||||||
LPPermissible permissible = Injector.getPermissible(player.getUniqueId());
|
LPPermissible permissible = Injector.getPermissible(player.getUniqueId());
|
||||||
if (permissible == null) {
|
if (permissible == null || !permissible.getActive().get()) {
|
||||||
return;
|
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);
|
boolean op = Optional.ofNullable(backing.get("luckperms.autoop")).orElse(false);
|
||||||
player.setOp(op);
|
player.setOp(op);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
@ -511,7 +523,8 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPlayerOnline(UUID external) {
|
public boolean isPlayerOnline(UUID external) {
|
||||||
return getServer().getPlayer(external) != null;
|
Player player = getServer().getPlayer(external);
|
||||||
|
return player != null && player.isOnline();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -64,7 +64,7 @@ public class Injector {
|
|||||||
PermissibleBase existing = (PermissibleBase) HUMAN_ENTITY_FIELD.get(player);
|
PermissibleBase existing = (PermissibleBase) HUMAN_ENTITY_FIELD.get(player);
|
||||||
if (existing instanceof LPPermissible) {
|
if (existing instanceof LPPermissible) {
|
||||||
// uh oh
|
// uh oh
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException("LPPermissible already injected into player " + player.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move attachments over from the old permissible.
|
// Move attachments over from the old permissible.
|
||||||
@ -73,6 +73,7 @@ public class Injector {
|
|||||||
attachments.clear();
|
attachments.clear();
|
||||||
existing.clearPermissions();
|
existing.clearPermissions();
|
||||||
|
|
||||||
|
lpPermissible.getActive().set(true);
|
||||||
lpPermissible.recalculatePermissions();
|
lpPermissible.recalculatePermissions();
|
||||||
lpPermissible.setOldPermissible(existing);
|
lpPermissible.setOldPermissible(existing);
|
||||||
|
|
||||||
@ -98,6 +99,8 @@ public class Injector {
|
|||||||
((LPPermissible) permissible).unsubscribeFromAllAsync();
|
((LPPermissible) permissible).unsubscribeFromAllAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
((LPPermissible) permissible).getActive().set(false);
|
||||||
|
|
||||||
if (dummy) {
|
if (dummy) {
|
||||||
HUMAN_ENTITY_FIELD.set(player, new DummyPermissibleBase());
|
HUMAN_ENTITY_FIELD.set(player, new DummyPermissibleBase());
|
||||||
} else {
|
} else {
|
||||||
|
@ -49,6 +49,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -66,6 +67,8 @@ public class LPPermissible extends PermissibleBase {
|
|||||||
@Setter
|
@Setter
|
||||||
private PermissibleBase oldPermissible = null;
|
private PermissibleBase oldPermissible = null;
|
||||||
|
|
||||||
|
private final AtomicBoolean active = new AtomicBoolean(false);
|
||||||
|
|
||||||
// Attachment stuff.
|
// Attachment stuff.
|
||||||
private final Map<String, PermissionAttachmentInfo> attachmentPermissions = new ConcurrentHashMap<>();
|
private final Map<String, PermissionAttachmentInfo> attachmentPermissions = new ConcurrentHashMap<>();
|
||||||
private final List<PermissionAttachment> attachments = Collections.synchronizedList(new LinkedList<>());
|
private final List<PermissionAttachment> attachments = Collections.synchronizedList(new LinkedList<>());
|
||||||
@ -81,10 +84,18 @@ public class LPPermissible extends PermissibleBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void updateSubscriptionsAsync() {
|
public void updateSubscriptionsAsync() {
|
||||||
|
if (!active.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
plugin.doAsync(this::updateSubscriptions);
|
plugin.doAsync(this::updateSubscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateSubscriptions() {
|
public void updateSubscriptions() {
|
||||||
|
if (!active.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
UserCache cache = user.getUserData();
|
UserCache cache = user.getUserData();
|
||||||
if (cache == null) {
|
if (cache == null) {
|
||||||
return;
|
return;
|
||||||
@ -110,9 +121,7 @@ public class LPPermissible extends PermissibleBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addAttachments(List<PermissionAttachment> attachments) {
|
public void addAttachments(List<PermissionAttachment> attachments) {
|
||||||
for (PermissionAttachment attachment : attachments) {
|
this.attachments.addAll(attachments);
|
||||||
this.attachments.add(attachment);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Contexts calculateContexts() {
|
public Contexts calculateContexts() {
|
||||||
@ -128,7 +137,7 @@ public class LPPermissible extends PermissibleBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasData() {
|
private boolean hasData() {
|
||||||
return user.getUserData() != null;
|
return user != null && user.getUserData() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
package me.lucko.luckperms.bungee;
|
package me.lucko.luckperms.bungee;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
import me.lucko.luckperms.api.Contexts;
|
import me.lucko.luckperms.api.Contexts;
|
||||||
import me.lucko.luckperms.api.caching.UserData;
|
import me.lucko.luckperms.api.caching.UserData;
|
||||||
import me.lucko.luckperms.api.context.MutableContextSet;
|
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.UuidCache;
|
||||||
import me.lucko.luckperms.common.core.model.User;
|
import me.lucko.luckperms.common.core.model.User;
|
||||||
import me.lucko.luckperms.common.defaults.Rule;
|
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.chat.TextComponent;
|
||||||
import net.md_5.bungee.api.connection.PendingConnection;
|
import net.md_5.bungee.api.connection.PendingConnection;
|
||||||
@ -44,62 +45,61 @@ import net.md_5.bungee.api.plugin.Listener;
|
|||||||
import net.md_5.bungee.event.EventHandler;
|
import net.md_5.bungee.event.EventHandler;
|
||||||
import net.md_5.bungee.event.EventPriority;
|
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.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@RequiredArgsConstructor
|
||||||
public class BungeeListener extends AbstractListener implements Listener {
|
public class BungeeListener implements Listener {
|
||||||
private static final TextComponent WARN_MESSAGE = new TextComponent(Message.LOADING_ERROR.toString());
|
|
||||||
private final LPBungeePlugin plugin;
|
private final LPBungeePlugin plugin;
|
||||||
|
|
||||||
BungeeListener(LPBungeePlugin plugin) {
|
private final Set<UUID> deniedLogin = Collections.synchronizedSet(new HashSet<>());
|
||||||
super(plugin);
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGH)
|
@EventHandler(priority = EventPriority.LOW)
|
||||||
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)
|
|
||||||
public void onPlayerLogin(LoginEvent e) {
|
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
|
This means that a player will have the same UUID across the network, even if parts of the network are running in
|
||||||
Offline mode. */
|
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);
|
e.registerIntent(plugin);
|
||||||
|
|
||||||
|
final long startTime = System.currentTimeMillis();
|
||||||
|
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(() -> {
|
plugin.doAsync(() -> {
|
||||||
final long startTime = System.currentTimeMillis();
|
|
||||||
final UuidCache cache = plugin.getUuidCache();
|
final UuidCache cache = plugin.getUuidCache();
|
||||||
final PendingConnection c = e.getConnection();
|
|
||||||
|
|
||||||
if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUIDS)) {
|
if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUIDS)) {
|
||||||
UUID uuid = plugin.getStorage().getUUID(c.getName()).join();
|
UUID uuid = plugin.getStorage().getUUID(c.getName()).join();
|
||||||
@ -109,6 +109,8 @@ public class BungeeListener extends AbstractListener implements Listener {
|
|||||||
// No previous data for this player
|
// No previous data for this player
|
||||||
plugin.getApiProvider().getEventFactory().handleUserFirstLogin(c.getUniqueId(), c.getName());
|
plugin.getApiProvider().getEventFactory().handleUserFirstLogin(c.getUniqueId(), c.getName());
|
||||||
cache.addToCache(c.getUniqueId(), c.getUniqueId());
|
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();
|
plugin.getStorage().force().saveUUIDData(c.getName(), c.getUniqueId()).join();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -118,12 +120,14 @@ public class BungeeListener extends AbstractListener implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Online mode, no cache needed. This is just for name -> uuid lookup.
|
// 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();
|
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
|
/* We have to make a new user on this thread whilst the connection is being held, or we get concurrency issues
|
||||||
// and the BungeeCord server try to make a new user at the same time.
|
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();
|
plugin.getStorage().force().loadUser(cache.getUUID(c.getUniqueId()), c.getName()).join();
|
||||||
|
|
||||||
User user = plugin.getUserManager().get(cache.getUUID(c.getUniqueId()));
|
User user = plugin.getUserManager().get(cache.getUUID(c.getUniqueId()));
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
plugin.getLog().warn("Failed to load user: " + c.getName());
|
plugin.getLog().warn("Failed to load user: " + c.getName());
|
||||||
@ -148,7 +152,13 @@ public class BungeeListener extends AbstractListener implements Listener {
|
|||||||
if (time >= 1000) {
|
if (time >= 1000) {
|
||||||
plugin.getLog().warn("Processing login for " + c.getName() + " took " + time + "ms.");
|
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);
|
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()));
|
final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(e.getPlayer().getUniqueId()));
|
||||||
|
|
||||||
if (user == null) {
|
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
|
// Wait until the last priority to unload, so plugins can still perform permission checks on this event
|
||||||
@EventHandler(priority = EventPriority.HIGHEST)
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
public void onPlayerQuit(PlayerDisconnectEvent e) {
|
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)
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
public void onServerSwitch(ServerConnectEvent e) {
|
public void onServerSwitch(ServerConnectEvent e) {
|
||||||
String serverName = e.getTarget().getName();
|
String serverName = e.getTarget().getName();
|
||||||
|
@ -323,7 +323,8 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPlayerOnline(UUID external) {
|
public boolean isPlayerOnline(UUID external) {
|
||||||
return getProxy().getPlayer(external) != null;
|
ProxiedPlayer player = getProxy().getPlayer(external);
|
||||||
|
return player != null && player.isConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -27,6 +27,7 @@ import lombok.Getter;
|
|||||||
|
|
||||||
import me.lucko.luckperms.common.commands.sender.Sender;
|
import me.lucko.luckperms.common.commands.sender.Sender;
|
||||||
import me.lucko.luckperms.common.commands.utils.Util;
|
import me.lucko.luckperms.common.commands.utils.Util;
|
||||||
|
import me.lucko.luckperms.common.locale.LocaleManager;
|
||||||
|
|
||||||
@SuppressWarnings("SpellCheckingInspection")
|
@SuppressWarnings("SpellCheckingInspection")
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@ -44,7 +45,7 @@ public enum Message {
|
|||||||
EMPTY("{0}", true),
|
EMPTY("{0}", true),
|
||||||
PLAYER_ONLINE("&aOnline", false),
|
PLAYER_ONLINE("&aOnline", false),
|
||||||
PLAYER_OFFLINE("&cOffline", 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("&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),
|
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),
|
LOG("&3LOG &3&l> {0}", true),
|
||||||
@ -452,6 +453,21 @@ public enum Message {
|
|||||||
return Util.color(showPrefix ? PREFIX + message : 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) {
|
public void send(Sender sender, Object... objects) {
|
||||||
String prefix = sender.getPlatform().getLocaleManager().getTranslation(PREFIX);
|
String prefix = sender.getPlatform().getLocaleManager().getTranslation(PREFIX);
|
||||||
if (prefix == null) {
|
if (prefix == null) {
|
||||||
|
@ -60,6 +60,13 @@ public interface UserManager extends Manager<UserIdentifier, User> {
|
|||||||
*/
|
*/
|
||||||
void cleanup(User 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
|
* 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
|
@Override
|
||||||
public void updateAllUsers() {
|
public void updateAllUsers() {
|
||||||
plugin.doSync(() -> {
|
plugin.doSync(() -> {
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
package me.lucko.luckperms.common.utils;
|
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.config.ConfigKeys;
|
||||||
import me.lucko.luckperms.common.core.UuidCache;
|
import me.lucko.luckperms.common.core.UuidCache;
|
||||||
@ -33,13 +33,12 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract listener shared by Bukkit & Sponge.
|
* Utilities for use in platform listeners
|
||||||
*/
|
*/
|
||||||
@AllArgsConstructor
|
@UtilityClass
|
||||||
public class AbstractListener {
|
public class LoginHelper {
|
||||||
private final LuckPermsPlugin plugin;
|
|
||||||
|
|
||||||
public void onAsyncLogin(UUID u, String username) {
|
public static void loadUser(LuckPermsPlugin plugin, UUID u, String username) {
|
||||||
final long startTime = System.currentTimeMillis();
|
final long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
final UuidCache cache = plugin.getUuidCache();
|
final UuidCache cache = plugin.getUuidCache();
|
||||||
@ -67,6 +66,7 @@ public class AbstractListener {
|
|||||||
User user = plugin.getUserManager().get(cache.getUUID(u));
|
User user = plugin.getUserManager().get(cache.getUUID(u));
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
plugin.getLog().warn("Failed to load user: " + username);
|
plugin.getLog().warn("Failed to load user: " + username);
|
||||||
|
throw new RuntimeException("Failed to load user");
|
||||||
} else {
|
} else {
|
||||||
// Setup defaults for the user
|
// Setup defaults for the user
|
||||||
boolean save = false;
|
boolean save = false;
|
||||||
@ -90,20 +90,7 @@ public class AbstractListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onLeave(UUID uuid) {
|
public static void refreshPlayer(LuckPermsPlugin plugin, 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) {
|
|
||||||
final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(uuid));
|
final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(uuid));
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
user.getRefreshBuffer().requestDirectly();
|
user.getRefreshBuffer().requestDirectly();
|
@ -440,7 +440,7 @@ public class LPSpongePlugin implements LuckPermsPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPlayerOnline(UUID external) {
|
public boolean isPlayerOnline(UUID external) {
|
||||||
return game.getServer().getPlayer(external).isPresent();
|
return game.getServer().getPlayer(external).map(Player::isOnline).orElse(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -22,12 +22,14 @@
|
|||||||
|
|
||||||
package me.lucko.luckperms.sponge;
|
package me.lucko.luckperms.sponge;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
import me.lucko.luckperms.api.caching.UserData;
|
import me.lucko.luckperms.api.caching.UserData;
|
||||||
import me.lucko.luckperms.api.context.MutableContextSet;
|
import me.lucko.luckperms.api.context.MutableContextSet;
|
||||||
import me.lucko.luckperms.common.constants.Message;
|
import me.lucko.luckperms.common.constants.Message;
|
||||||
import me.lucko.luckperms.common.core.UuidCache;
|
import me.lucko.luckperms.common.core.UuidCache;
|
||||||
import me.lucko.luckperms.common.core.model.User;
|
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 me.lucko.luckperms.sponge.timings.LPTiming;
|
||||||
|
|
||||||
import org.spongepowered.api.command.CommandSource;
|
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.Listener;
|
||||||
import org.spongepowered.api.event.Order;
|
import org.spongepowered.api.event.Order;
|
||||||
import org.spongepowered.api.event.command.SendCommandEvent;
|
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.event.network.ClientConnectionEvent;
|
||||||
import org.spongepowered.api.profile.GameProfile;
|
import org.spongepowered.api.profile.GameProfile;
|
||||||
import org.spongepowered.api.text.serializer.TextSerializers;
|
import org.spongepowered.api.text.serializer.TextSerializers;
|
||||||
|
import org.spongepowered.api.util.Tristate;
|
||||||
import org.spongepowered.api.world.World;
|
import org.spongepowered.api.world.World;
|
||||||
|
|
||||||
import co.aikar.timings.Timing;
|
import co.aikar.timings.Timing;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@RequiredArgsConstructor
|
||||||
public class SpongeListener extends AbstractListener {
|
public class SpongeListener {
|
||||||
private final LPSpongePlugin plugin;
|
private final LPSpongePlugin plugin;
|
||||||
|
|
||||||
SpongeListener(LPSpongePlugin plugin) {
|
private final Set<UUID> deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>());
|
||||||
super(plugin);
|
private final Set<UUID> deniedLogin = Collections.synchronizedSet(new HashSet<>());
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Listener(order = Order.EARLY)
|
@Listener(order = Order.AFTER_PRE)
|
||||||
|
@IsCancelled(Tristate.UNDEFINED)
|
||||||
public void onClientAuth(ClientConnectionEvent.Auth e) {
|
public void onClientAuth(ClientConnectionEvent.Auth e) {
|
||||||
if (!plugin.getStorage().isAcceptingLogins()) {
|
/* Called when the player first attempts a connection with the server.
|
||||||
/* Datastore is disabled, prevent players from joining the server
|
Listening on AFTER_PRE priority to allow plugins to modify username / UUID data here. (auth plugins) */
|
||||||
Just don't load their data, they will be kicked at login */
|
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final GameProfile p = e.getProfile();
|
/* either the plugin hasn't finished starting yet, or there was an issue connecting to the DB, performing file i/o, etc.
|
||||||
onAsyncLogin(p.getUniqueId(), p.getName().get()); // Load the user into LuckPerms
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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())));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@Listener(order = Order.BEFORE_POST)
|
||||||
@Listener(order = Order.EARLY)
|
@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) {
|
public void onClientLogin(ClientConnectionEvent.Login e) {
|
||||||
try (Timing ignored = plugin.getTimings().time(LPTiming.ON_CLIENT_LOGIN)) {
|
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();
|
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()));
|
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) {
|
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.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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +192,7 @@ public class SpongeListener extends AbstractListener {
|
|||||||
@Listener(order = Order.EARLY)
|
@Listener(order = Order.EARLY)
|
||||||
public void onClientJoin(ClientConnectionEvent.Join e) {
|
public void onClientJoin(ClientConnectionEvent.Join e) {
|
||||||
// Refresh permissions again
|
// Refresh permissions again
|
||||||
plugin.doAsync(() -> refreshPlayer(e.getTargetEntity().getUniqueId()));
|
plugin.doAsync(() -> LoginHelper.refreshPlayer(plugin, e.getTargetEntity().getUniqueId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Listener(order = Order.LAST)
|
@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
|
// 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
|
@Override
|
||||||
public void updateAllUsers() {
|
public void updateAllUsers() {
|
||||||
plugin.doSync(() -> {
|
plugin.doSync(() -> {
|
||||||
|
Loading…
Reference in New Issue
Block a user