diff --git a/src/main/java/world/bentobox/bentobox/api/addons/AddonClassLoader.java b/src/main/java/world/bentobox/bentobox/api/addons/AddonClassLoader.java index 867f0c3f5..8c99d8332 100644 --- a/src/main/java/world/bentobox/bentobox/api/addons/AddonClassLoader.java +++ b/src/main/java/world/bentobox/bentobox/api/addons/AddonClassLoader.java @@ -8,15 +8,12 @@ import java.net.URLClassLoader; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Set; -import org.bukkit.Bukkit; import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.InvalidDescriptionException; -import org.bukkit.util.permissions.DefaultPermissions; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; @@ -31,7 +28,6 @@ import world.bentobox.bentobox.managers.AddonsManager; */ public class AddonClassLoader extends URLClassLoader { - private static final String DEFAULT = ".default"; private final Map> classes = new HashMap<>(); private Addon addon; private AddonsManager loader; @@ -70,31 +66,16 @@ public class AddonClassLoader extends URLClassLoader { addon = addonClass.getDeclaredConstructor().newInstance(); 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 private AddonDescription asDescription(YamlConfiguration data) throws InvalidAddonDescriptionException { 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); } + // Set permissions + if (data.isConfigurationSection("permissions")) { + builder.permissions(Objects.requireNonNull(data.getConfigurationSection("permissions"))); + } return builder.build(); } diff --git a/src/main/java/world/bentobox/bentobox/api/addons/AddonDescription.java b/src/main/java/world/bentobox/bentobox/api/addons/AddonDescription.java index 52192b918..efa00fa85 100644 --- a/src/main/java/world/bentobox/bentobox/api/addons/AddonDescription.java +++ b/src/main/java/world/bentobox/bentobox/api/addons/AddonDescription.java @@ -5,7 +5,9 @@ import java.util.Arrays; import java.util.List; import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; /** * @author tastybento, Poslovitch @@ -19,6 +21,7 @@ public final class AddonDescription { private final @NonNull List authors; private final @NonNull List dependencies; private final @NonNull List softDependencies; + private final @Nullable ConfigurationSection permissions; /** * Whether the addon should be included in Metrics or not. * @since 1.1 @@ -54,6 +57,7 @@ public final class AddonDescription { this.repository = builder.repository; this.icon = builder.icon; this.apiVersion = builder.apiVersion; + this.permissions = builder.permissions; } @NonNull @@ -150,6 +154,14 @@ public final class AddonDescription { return apiVersion; } + /** + * @return the permissions + * @since 1.13.0 + */ + public ConfigurationSection getPermissions() { + return permissions; + } + public static class Builder { private @NonNull String main; private @NonNull String name; @@ -162,6 +174,7 @@ public final class AddonDescription { private @NonNull String repository = ""; private @NonNull Material icon = Material.PAPER; private @NonNull String apiVersion = "1"; + private @Nullable ConfigurationSection permissions; /** * @since 1.1 @@ -243,5 +256,17 @@ public final class AddonDescription { public AddonDescription build() { 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; + } } } diff --git a/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java b/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java index b1c7b5b0d..f2324198a 100644 --- a/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java @@ -21,11 +21,14 @@ import java.util.logging.Level; import java.util.stream.Collectors; import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; 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.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.AddonClassLoader; 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.configuration.ConfigObject; import world.bentobox.bentobox.api.events.addon.AddonEvent; @@ -46,6 +50,10 @@ import world.bentobox.bentobox.util.Util; */ public class AddonsManager { + private static final String DEFAULT = ".default"; + + private static final String GAMEMODE = "[gamemode]."; + @NonNull private List addons; @NonNull @@ -154,10 +162,42 @@ public class AddonsManager { * Enables all the addons */ public void enableAddons() { - if (!getLoadedAddons().isEmpty()) { - plugin.log("Enabling addons..."); - getLoadedAddons().forEach(this::enableAddon); - plugin.log("Addons successfully enabled."); + if (getLoadedAddons().isEmpty()) return; + plugin.log("Enabling addons..."); + getLoadedAddons().forEach(this::enableAddon); + // 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); } + /** + * Sorts the addons into loading order taking into account dependencies + */ private void sortAddons() { // Lists all available addons as names. List names = addons.stream().map(a -> a.getDescription().getName()).collect(Collectors.toList()); diff --git a/src/test/java/world/bentobox/bentobox/managers/AddonsManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/AddonsManagerTest.java index 8abb6dd72..553d77805 100644 --- a/src/test/java/world/bentobox/bentobox/managers/AddonsManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/AddonsManagerTest.java @@ -5,6 +5,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; 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.never; import static org.mockito.Mockito.verify; @@ -14,9 +17,15 @@ import java.io.File; import java.nio.file.Files; import org.bukkit.Bukkit; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.event.Listener; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.PluginManager; +import org.bukkit.util.permissions.DefaultPermissions; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -31,12 +40,16 @@ import org.powermock.reflect.Whitebox; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.Settings; 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.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.objects.DataObject; @RunWith(PowerMockRunner.class) -@PrepareForTest( {Bukkit.class, BentoBox.class} ) +@PrepareForTest( {Bukkit.class, BentoBox.class, DefaultPermissions.class} ) public class AddonsManagerTest { private BentoBox plugin; @@ -67,6 +80,8 @@ public class AddonsManagerTest { when(s.getDatabaseType()).thenReturn(DatabaseType.MYSQL); // settings when(plugin.getSettings()).thenReturn(s); + + PowerMockito.mockStatic(DefaultPermissions.class); } /** @@ -235,7 +250,7 @@ public class AddonsManagerTest { assertFalse(am.getDataObjects().isEmpty()); assertTrue(am.getDataObjects().size() == 1); } - + /** * Test method for {@link world.bentobox.bentobox.managers.AddonsManager#isAddonCompatibleWithBentoBox(Addon)}. */ @@ -246,7 +261,7 @@ public class AddonsManagerTest { when(addon.getDescription()).thenReturn(addonDesc); assertTrue(am.isAddonCompatibleWithBentoBox(addon, "1.0.1-SNAPSHOT-b1642")); } - + /** * Test method for {@link world.bentobox.bentobox.managers.AddonsManager#isAddonCompatibleWithBentoBox(Addon)}. */ @@ -257,7 +272,7 @@ public class AddonsManagerTest { when(addon.getDescription()).thenReturn(addonDesc); assertTrue(am.isAddonCompatibleWithBentoBox(addon, "1.0.1")); } - + /** * Test method for {@link world.bentobox.bentobox.managers.AddonsManager#isAddonCompatibleWithBentoBox(Addon)}. */ @@ -268,7 +283,7 @@ public class AddonsManagerTest { when(addon.getDescription()).thenReturn(addonDesc); assertTrue(am.isAddonCompatibleWithBentoBox(addon, "1.0.1-SNAPSHOT-b1642")); } - + /** * Test method for {@link world.bentobox.bentobox.managers.AddonsManager#isAddonCompatibleWithBentoBox(Addon)}. */ @@ -279,7 +294,7 @@ public class AddonsManagerTest { when(addon.getDescription()).thenReturn(addonDesc); assertTrue(am.isAddonCompatibleWithBentoBox(addon, "1.0.1")); } - + /** * Test method for {@link world.bentobox.bentobox.managers.AddonsManager#isAddonCompatibleWithBentoBox(Addon)}. */ @@ -290,7 +305,7 @@ public class AddonsManagerTest { when(addon.getDescription()).thenReturn(addonDesc); assertFalse(am.isAddonCompatibleWithBentoBox(addon, "1.2-SNAPSHOT-b1642")); } - + /** * Test method for {@link world.bentobox.bentobox.managers.AddonsManager#isAddonCompatibleWithBentoBox(Addon)}. */ @@ -301,7 +316,7 @@ public class AddonsManagerTest { when(addon.getDescription()).thenReturn(addonDesc); assertFalse(am.isAddonCompatibleWithBentoBox(addon, "1.0.1-SNAPSHOT-b1642")); } - + /** * Test method for {@link world.bentobox.bentobox.managers.AddonsManager#isAddonCompatibleWithBentoBox(Addon)}. */ @@ -312,7 +327,7 @@ public class AddonsManagerTest { when(addon.getDescription()).thenReturn(addonDesc); assertFalse(am.isAddonCompatibleWithBentoBox(addon, "1.0.1")); } - + /** * Test method for {@link world.bentobox.bentobox.managers.AddonsManager#isAddonCompatibleWithBentoBox(Addon)}. */ @@ -323,7 +338,7 @@ public class AddonsManagerTest { when(addon.getDescription()).thenReturn(addonDesc); assertTrue(am.isAddonCompatibleWithBentoBox(addon, "1.11.1.11.1.1")); } - + /** * Test method for {@link world.bentobox.bentobox.managers.AddonsManager#isAddonCompatibleWithBentoBox(Addon)}. */ @@ -335,5 +350,78 @@ public class AddonsManagerTest { 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() { + } + + } }