Implements placeholder permissions for addons (#1305)

Implements #1303. 

Addons will no longer need to see their permissions updated each time there is a new gamemode. They can use [gamemode] in their permissions to automatically register the permissions on all available gamemodes.
This commit is contained in:
tastybento 2020-04-25 16:20:48 -07:00 committed by GitHub
parent fe58159db3
commit 1f3a79127a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 182 additions and 41 deletions

View File

@ -8,15 +8,12 @@ import java.net.URLClassLoader;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.InvalidDescriptionException; import org.bukkit.plugin.InvalidDescriptionException;
import org.bukkit.util.permissions.DefaultPermissions;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -31,7 +28,6 @@ import world.bentobox.bentobox.managers.AddonsManager;
*/ */
public class AddonClassLoader extends URLClassLoader { public class AddonClassLoader extends URLClassLoader {
private static final String DEFAULT = ".default";
private final Map<String, Class<?>> classes = new HashMap<>(); private final Map<String, Class<?>> classes = new HashMap<>();
private Addon addon; private Addon addon;
private AddonsManager loader; private AddonsManager loader;
@ -70,31 +66,16 @@ public class AddonClassLoader extends URLClassLoader {
addon = addonClass.getDeclaredConstructor().newInstance(); addon = addonClass.getDeclaredConstructor().newInstance();
addon.setDescription(asDescription(data)); addon.setDescription(asDescription(data));
// Set permissions
if (data.isConfigurationSection("permissions")) {
ConfigurationSection perms = data.getConfigurationSection("permissions");
perms.getKeys(true).forEach(perm -> {
if (perms.contains(perm + DEFAULT) && perms.contains(perm + ".description")) {
registerPermission(perms, perm);
}
});
}
} }
private void registerPermission(ConfigurationSection perms, String perm) {
if (perms.getString(perm + DEFAULT) == null) {
Bukkit.getLogger().severe("Permission default is invalid : " + perms.getName());
return;
}
PermissionDefault pd = PermissionDefault.getByName(perms.getString(perm + DEFAULT));
if (pd == null) {
Bukkit.getLogger().severe("Permission default is invalid : " + perms.getName());
return;
}
String desc = perms.getString(perm + ".description");
DefaultPermissions.registerPermission(perm, desc, pd);
}
/**
* Convers the addon.yml to an AddonDescription
* @param data - yaml config (addon.yml)
* @return Addon Description
* @throws InvalidAddonDescriptionException - if there's a bug in the addon.yml
*/
@NonNull @NonNull
private AddonDescription asDescription(YamlConfiguration data) throws InvalidAddonDescriptionException { private AddonDescription asDescription(YamlConfiguration data) throws InvalidAddonDescriptionException {
AddonDescription.Builder builder = new AddonDescription.Builder(data.getString("main"), data.getString("name"), data.getString("version")) AddonDescription.Builder builder = new AddonDescription.Builder(data.getString("main"), data.getString("name"), data.getString("version"))
@ -119,6 +100,10 @@ public class AddonClassLoader extends URLClassLoader {
} }
builder.apiVersion(apiVersion); builder.apiVersion(apiVersion);
} }
// Set permissions
if (data.isConfigurationSection("permissions")) {
builder.permissions(Objects.requireNonNull(data.getConfigurationSection("permissions")));
}
return builder.build(); return builder.build();
} }

View File

@ -5,7 +5,9 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
/** /**
* @author tastybento, Poslovitch * @author tastybento, Poslovitch
@ -19,6 +21,7 @@ public final class AddonDescription {
private final @NonNull List<String> authors; private final @NonNull List<String> authors;
private final @NonNull List<String> dependencies; private final @NonNull List<String> dependencies;
private final @NonNull List<String> softDependencies; private final @NonNull List<String> softDependencies;
private final @Nullable ConfigurationSection permissions;
/** /**
* Whether the addon should be included in Metrics or not. * Whether the addon should be included in Metrics or not.
* @since 1.1 * @since 1.1
@ -54,6 +57,7 @@ public final class AddonDescription {
this.repository = builder.repository; this.repository = builder.repository;
this.icon = builder.icon; this.icon = builder.icon;
this.apiVersion = builder.apiVersion; this.apiVersion = builder.apiVersion;
this.permissions = builder.permissions;
} }
@NonNull @NonNull
@ -150,6 +154,14 @@ public final class AddonDescription {
return apiVersion; return apiVersion;
} }
/**
* @return the permissions
* @since 1.13.0
*/
public ConfigurationSection getPermissions() {
return permissions;
}
public static class Builder { public static class Builder {
private @NonNull String main; private @NonNull String main;
private @NonNull String name; private @NonNull String name;
@ -162,6 +174,7 @@ public final class AddonDescription {
private @NonNull String repository = ""; private @NonNull String repository = "";
private @NonNull Material icon = Material.PAPER; private @NonNull Material icon = Material.PAPER;
private @NonNull String apiVersion = "1"; private @NonNull String apiVersion = "1";
private @Nullable ConfigurationSection permissions;
/** /**
* @since 1.1 * @since 1.1
@ -243,5 +256,17 @@ public final class AddonDescription {
public AddonDescription build() { public AddonDescription build() {
return new AddonDescription(this); return new AddonDescription(this);
} }
/**
* Sets the permission config section. Taken from the addon.yml
* @param permissions - YAML configuration section
* @return Builder
* @since 1.13.0
*/
@NonNull
public Builder permissions(ConfigurationSection permissions) {
this.permissions = permissions;
return this;
}
} }
} }

View File

@ -21,11 +21,14 @@ import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.util.permissions.DefaultPermissions;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -34,6 +37,7 @@ import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.Addon.State; import world.bentobox.bentobox.api.addons.Addon.State;
import world.bentobox.bentobox.api.addons.AddonClassLoader; import world.bentobox.bentobox.api.addons.AddonClassLoader;
import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.addons.exceptions.InvalidAddonDescriptionException;
import world.bentobox.bentobox.api.addons.exceptions.InvalidAddonFormatException; import world.bentobox.bentobox.api.addons.exceptions.InvalidAddonFormatException;
import world.bentobox.bentobox.api.configuration.ConfigObject; import world.bentobox.bentobox.api.configuration.ConfigObject;
import world.bentobox.bentobox.api.events.addon.AddonEvent; import world.bentobox.bentobox.api.events.addon.AddonEvent;
@ -46,6 +50,10 @@ import world.bentobox.bentobox.util.Util;
*/ */
public class AddonsManager { public class AddonsManager {
private static final String DEFAULT = ".default";
private static final String GAMEMODE = "[gamemode].";
@NonNull @NonNull
private List<Addon> addons; private List<Addon> addons;
@NonNull @NonNull
@ -154,10 +162,42 @@ public class AddonsManager {
* Enables all the addons * Enables all the addons
*/ */
public void enableAddons() { public void enableAddons() {
if (!getLoadedAddons().isEmpty()) { if (getLoadedAddons().isEmpty()) return;
plugin.log("Enabling addons..."); plugin.log("Enabling addons...");
getLoadedAddons().forEach(this::enableAddon); getLoadedAddons().forEach(this::enableAddon);
plugin.log("Addons successfully enabled."); // Set perms for enabled addons
this.getEnabledAddons().forEach(this::setPerms);
plugin.log("Addons successfully enabled.");
}
void setPerms(Addon addon) {
ConfigurationSection perms = addon.getDescription().getPermissions();
perms.getKeys(true).parallelStream().filter(perm -> perms.contains(perm + DEFAULT) && perms.contains(perm + ".description"))
.forEach(perm -> {
try {
registerPermission(perms, perm);
} catch (InvalidAddonDescriptionException e) {
plugin.logError("Addon " + addon.getDescription().getName() + ": " + e.getMessage());
}
});
}
void registerPermission(ConfigurationSection perms, String perm) throws InvalidAddonDescriptionException {
if (perms.getString(perm + DEFAULT) == null) {
throw new InvalidAddonDescriptionException("Permission default is invalid : " + perm + DEFAULT);
}
PermissionDefault pd = PermissionDefault.getByName(perms.getString(perm + DEFAULT));
if (pd == null) {
throw new InvalidAddonDescriptionException("Permission default is invalid : " + perm + DEFAULT);
}
String desc = perms.getString(perm + ".description");
// Replace placeholders for Game Mode Addon names
if (perm.contains(GAMEMODE)) {
getGameModeAddons().stream().map(g -> g.getPermissionPrefix())
.forEach(p -> DefaultPermissions.registerPermission(perm.replace(GAMEMODE, p), desc, pd));
} else {
// Single perm
DefaultPermissions.registerPermission(perm, desc, pd);
} }
} }
@ -380,6 +420,9 @@ public class AddonsManager {
classes.putIfAbsent(name, clazz); classes.putIfAbsent(name, clazz);
} }
/**
* Sorts the addons into loading order taking into account dependencies
*/
private void sortAddons() { private void sortAddons() {
// Lists all available addons as names. // Lists all available addons as names.
List<String> names = addons.stream().map(a -> a.getDescription().getName()).collect(Collectors.toList()); List<String> names = addons.stream().map(a -> a.getDescription().getName()).collect(Collectors.toList());

View File

@ -5,6 +5,9 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -14,9 +17,15 @@ import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.util.permissions.DefaultPermissions;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -31,12 +40,16 @@ import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.Addon.State;
import world.bentobox.bentobox.api.addons.AddonDescription; import world.bentobox.bentobox.api.addons.AddonDescription;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.addons.exceptions.InvalidAddonDescriptionException;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType; import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType;
import world.bentobox.bentobox.database.objects.DataObject; import world.bentobox.bentobox.database.objects.DataObject;
@RunWith(PowerMockRunner.class) @RunWith(PowerMockRunner.class)
@PrepareForTest( {Bukkit.class, BentoBox.class} ) @PrepareForTest( {Bukkit.class, BentoBox.class, DefaultPermissions.class} )
public class AddonsManagerTest { public class AddonsManagerTest {
private BentoBox plugin; private BentoBox plugin;
@ -67,6 +80,8 @@ public class AddonsManagerTest {
when(s.getDatabaseType()).thenReturn(DatabaseType.MYSQL); when(s.getDatabaseType()).thenReturn(DatabaseType.MYSQL);
// settings // settings
when(plugin.getSettings()).thenReturn(s); when(plugin.getSettings()).thenReturn(s);
PowerMockito.mockStatic(DefaultPermissions.class);
} }
/** /**
@ -335,5 +350,78 @@ public class AddonsManagerTest {
assertFalse(am.isAddonCompatibleWithBentoBox(addon, "1.11.1")); assertFalse(am.isAddonCompatibleWithBentoBox(addon, "1.11.1"));
} }
/**
* Test method for {@link world.bentobox.bentobox.managers.AddonsManager#registerPermission(org.bukkit.configuration.ConfigurationSection, String)}
* @throws InvalidAddonDescriptionException
* @throws InvalidConfigurationException
*/
@Test
public void testRegisterPermissionStandardPerm() throws InvalidAddonDescriptionException, InvalidConfigurationException {
String perms =
" 'bskyblock.intopten':\n" +
" description: Player is in the top ten.\n" +
" default: true\n";
YamlConfiguration config = new YamlConfiguration();
config.loadFromString(perms);
am.registerPermission(config, "bskyblock.intopten");
PowerMockito.verifyStatic(DefaultPermissions.class);
DefaultPermissions.registerPermission(eq("bskyblock.intopten"), anyString(), any(PermissionDefault.class));
}
/**
* Test method for {@link world.bentobox.bentobox.managers.AddonsManager#registerPermission(org.bukkit.configuration.ConfigurationSection, String)}
* @throws InvalidAddonDescriptionException
* @throws InvalidConfigurationException
*/
@Test
public void testRegisterPermissionGameModePerm() throws InvalidAddonDescriptionException, InvalidConfigurationException {
String perms =
" '[gamemode].intopten':\n" +
" description: Player is in the top ten.\n" +
" default: true\n";
YamlConfiguration config = new YamlConfiguration();
config.loadFromString(perms);
GameModeAddon addon = new MyGameMode();
AddonDescription addonDesc = new AddonDescription.Builder("main.class", "mygame", "1.0.0").apiVersion("1.11.1.0.0.0.1")
.permissions(config)
.build();
addon.setDescription(addonDesc);
addon.setState(State.ENABLED);
am.getAddons().add(addon);
am.registerPermission(config, "[gamemode].intopten");
PowerMockito.verifyStatic(DefaultPermissions.class);
DefaultPermissions.registerPermission(eq("mygame.intopten"), anyString(), any(PermissionDefault.class));
}
class MyGameMode extends GameModeAddon {
@Override
public void createWorlds() {
}
@Override
public WorldSettings getWorldSettings() {
return null;
}
@Override
public @Nullable ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
return null;
}
@Override
public void saveWorldSettings() {
}
@Override
public void onEnable() {
}
@Override
public void onDisable() {
}
}
} }