PandaLib/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorBukkit.java

307 lines
11 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package fr.pandacube.lib.paper.permissions;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import fr.pandacube.lib.permissions.Permissions;
import fr.pandacube.lib.reflect.Reflect;
import fr.pandacube.lib.util.Log;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permissible;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
/* package */ class PermissionsInjectorBukkit
{
// to be called : onEnable for console, onPlayerLogin (not Join) (Priority LOWEST) for players
public static void inject(CommandSender sender) {
Permissible oldP = getPermissible(sender);
if (oldP instanceof PandaPermissible)
return;
if (!oldP.getClass().equals(PermissibleBase.class)) {
Log.warning("Another plugin is already injecting permissions into Bukkit for " + sender.getName() + ": " + oldP.getClass().getName());
Log.warning("Not injecting our own permissions.");
}
PandaPermissible p = new PandaPermissible(sender, getPermissible(sender));
setPermissible(p.sender, p);
p.recalculatePermissions();
}
// to be called : onDisable for console, onPlayerQuit () and onDisable for players
public static void uninject(CommandSender sender)
{
Permissible perm = getPermissible(sender);
if (perm instanceof PandaPermissible p) {
setPermissible(sender, p.oldPermissible);
p.oldPermissible.recalculatePermissions();
}
}
private static void setPermissible(CommandSender sender, Permissible newPermissible)
{
try {
Field perm = getPermField(sender);
if (perm == null)
return;
perm.setAccessible(true);
perm.set(sender, newPermissible);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
/* package */ static Permissible getPermissible(CommandSender sender)
{
try {
Field perm = getPermField(sender);
perm.setAccessible(true);
Permissible p = (Permissible) perm.get(sender);
if (p == null) {
Log.warning("Null permissible instance found in provided CommandSender: " + sender, new Throwable());
}
return p;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private static Field getPermField(CommandSender sender) throws NoSuchFieldException {
if (sender == null) {
throw new IllegalArgumentException("sender cannot be null");
}
if (sender instanceof Player || sender instanceof ConsoleCommandSender)
return Reflect.ofClassOfInstance(sender).field("perm").get();
else
throw new IllegalArgumentException("Unsupported type for sender: " + sender.getClass());
}
/* package */ static class PandaPermissible extends PermissibleBase
{
private final CommandSender sender;
private final Permissible oldPermissible;
/* package */ final LoadingCache<String, List<Permission>> superPermsPermissionCache = CacheBuilder.newBuilder()
.build(CacheLoader.from(PandalibPaperPermissions.SUPERPERMS_PARENT_PERMISSION_GETTER::apply));
@SuppressWarnings("UnusedAssignment")
private boolean init = false;
/* assignment to false is necessary because of super class constructor calling the method recalculatePermission()
* and we dont want that.
*/
private PandaPermissible(CommandSender sender, Permissible oldPermissible)
{
super(sender);
this.sender = sender;
this.oldPermissible = oldPermissible;
init = true;
recalculatePermissions();
}
private Boolean hasPermissionOnServerInWorld(String permission) {
if (sender instanceof Player player) {
String world = player.getWorld().getName();
return Permissions.getPlayer(player.getUniqueId()).hasPermission(permission, PandalibPaperPermissions.serverName, world);
}
return true;
}
@Override
public boolean hasPermission(String permission)
{
/*
* WARNING: dont call PermissibleOnlinePlayer#hasPermission(String) here, or it will result on a stack overflow
*/
if (permission.toLowerCase().startsWith("minecraft.command."))
permission = PandalibPaperPermissions.permissionMap.getOrDefault(permission.toLowerCase(), permission);
Boolean res = hasPermissionOnServerInWorld(permission); // supports negative permission
if (res != null)
return res;
res = PandalibPaperPermissions.hasSuperPermsPermission(sender, permission, this::hasPermission, this); // supports negative permission
if (res != null)
return res;
boolean reversed = permission.startsWith("-");
if (reversed) {
permission = permission.substring(1);
}
return oldPermissible.hasPermission(permission) != reversed;
}
@Override
public boolean hasPermission(Permission permission)
{
if (permission.getName().toLowerCase().startsWith("minecraft.command.") && PandalibPaperPermissions.permissionMap.containsKey(permission.getName().toLowerCase())) {
return hasPermission(PandalibPaperPermissions.permissionMap.get(permission.getName().toLowerCase()));
}
Boolean res = hasPermissionOnServerInWorld(permission.getName()); // supports negative permission
if (res != null)
return res;
res = PandalibPaperPermissions.hasSuperPermsPermission(sender, permission.getName(), this::hasPermission, this); // supports negative permission
if (res != null)
return res;
return oldPermissible.hasPermission(permission); // doesn't need to manage negative permission (should not happen)
}
@Override
public void recalculatePermissions()
{
// need this check because super class constructor calls this method,
// thus before oldPermissible has its value assigned
if (!init)
return;
oldPermissible.recalculatePermissions();
superPermsPermissionCache.invalidateAll();
effectivePermissionsListCache.invalidateAll();
}
private Map<String, Boolean> getEffectivePermissionsOnServerInWorld() {
if (sender instanceof Player player) {
String world = player.getWorld().getName();
return Permissions.getPlayer(player.getUniqueId()).listEffectivePermissions(PandalibPaperPermissions.serverName, world);
}
return new HashMap<>();
}
// key is world
private final Cache<String, Set<PermissionAttachmentInfo>> effectivePermissionsListCache = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
@Override
public @NotNull Set<PermissionAttachmentInfo> getEffectivePermissions()
{
// PlotSquared uses this method to optimize permission range (plots.limit.10 for example)
// MobArena uses this method when a player leave the arena
// LibsDisguises uses this method (and only this one) to parse all the permissions
if (sender instanceof Player player) {
String world = player.getWorld().getName();
try {
return effectivePermissionsListCache.get(world, () -> {
// first get the superperms effective permissions (that take isOp into account)
Map<String, PermissionAttachmentInfo> perms = oldPermissible.getEffectivePermissions().stream()
.collect(Collectors.toMap(PermissionAttachmentInfo::getPermission, Function.identity()));
// then override them with the permissions from our permission system (that has priority, and take current world into account)
for (Map.Entry<String, Boolean> permE : getEffectivePermissionsOnServerInWorld().entrySet()) {
perms.put(permE.getKey(), new PermissionAttachmentInfo(this, permE.getKey(), null, permE.getValue()));
}
return new LinkedHashSet<>(perms.values());
});
} catch (ExecutionException e) {
Log.severe(e);
}
}
return oldPermissible.getEffectivePermissions();
}
@Override
public boolean isOp()
{
return oldPermissible.isOp();
}
@Override
public void setOp(boolean value)
{
oldPermissible.setOp(value);
}
@Override
public boolean isPermissionSet(@NotNull String permission)
{
Boolean res = hasPermissionOnServerInWorld(permission);
if (res != null)
return true;
return oldPermissible.isPermissionSet(permission);
}
@Override
public boolean isPermissionSet(Permission permission)
{
Boolean res = hasPermissionOnServerInWorld(permission.getName());
if (res != null)
return true;
return oldPermissible.isPermissionSet(permission);
}
@Override
public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin)
{
return oldPermissible.addAttachment(plugin);
}
@Override
public PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks)
{
return oldPermissible.addAttachment(plugin, ticks);
}
@Override
public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value)
{
return oldPermissible.addAttachment(plugin, name, value);
}
@Override
public PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value, int ticks)
{
return oldPermissible.addAttachment(plugin, name, value, ticks);
}
@Override
public void removeAttachment(@NotNull PermissionAttachment attachment)
{
oldPermissible.removeAttachment(attachment);
}
@Override
public synchronized void clearPermissions()
{
if (oldPermissible instanceof PermissibleBase)
((PermissibleBase) oldPermissible).clearPermissions();
}
}
}