/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ package org.anjocaido.groupmanager.permissions; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import org.anjocaido.groupmanager.GroupManager; import org.anjocaido.groupmanager.data.User; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.event.server.PluginEnableEvent; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionAttachment; import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.plugin.PluginManager; /** * * BukkitPermissions overrides to force GM reponses to Superperms * * @author ElgarL */ public class BukkitPermissions { private static Field permissions; // Setup reflection (Thanks to Codename_B for the reflection source) static { try { permissions = PermissionAttachment.class.getDeclaredField("permissions"); permissions.setAccessible(true); } catch (final SecurityException e) { e.printStackTrace(); } catch (final NoSuchFieldException e) { e.printStackTrace(); } } protected WeakHashMap attachments = new WeakHashMap(); protected LinkedHashMap registeredPermissions = new LinkedHashMap(); protected GroupManager plugin; protected boolean dumpAllPermissions = true; protected boolean dumpMatchedPermissions = true; private boolean player_join = false; public BukkitPermissions(final GroupManager plugin) { this.plugin = plugin; this.reset(); this.registerEvents(); GroupManager.logger.info("Superperms support enabled."); } public void collectPermissions() { registeredPermissions.clear(); for (final Permission perm : Bukkit.getPluginManager().getPermissions()) { registeredPermissions.put(perm.getName().toLowerCase(), perm); } } /** * Returns a map of ALL child permissions registered with bukkit * null is empty * * @param node * @param playerPermArray * current list of perms to check against for * negations * @return Map of child permissions */ public Map getAllChildren(final String node, final Set playerPermArray) { final LinkedList stack = new LinkedList(); final Map alreadyVisited = new HashMap(); stack.push(node); alreadyVisited.put(node, true); while (!stack.isEmpty()) { final String now = stack.pop(); final Map children = getChildren(now); if ((children != null) && (!playerPermArray.contains("-" + now))) { for (final String childName : children.keySet()) { if (!alreadyVisited.containsKey(childName)) { stack.push(childName); alreadyVisited.put(childName, children.get(childName)); } } } } alreadyVisited.remove(node); if (!alreadyVisited.isEmpty()) return alreadyVisited; return null; } /** * Fetch all permissions which are registered with superperms. * {can include child nodes) * * @param includeChildren * @return List of all permission nodes */ public List getAllRegisteredPermissions(final boolean includeChildren) { final List perms = new ArrayList(); for (final String key : registeredPermissions.keySet()) { if (!perms.contains(key)) { perms.add(key); if (includeChildren) { final Map children = getAllChildren(key, new HashSet()); if (children != null) { for (final String node : children.keySet()) if (!perms.contains(node)) perms.add(node); } } } } return perms; } /** * Returns a map of the child permissions (1 node deep) as registered with * Bukkit. * null is empty * * @param node * @return Map of child permissions */ public Map getChildren(final String node) { final Permission perm = registeredPermissions.get(node.toLowerCase()); if (perm == null) return null; return perm.getChildren(); } /** * @return the player_join */ public boolean isPlayer_join() { return player_join; } /** * List all effective permissions for this player. * * @param player * @return List of permissions */ public List listPerms(final Player player) { final List perms = new ArrayList(); /* * // All permissions registered with Bukkit for this player * PermissionAttachment attachment = this.attachments.get(player); * * // List perms for this player perms.add("Attachment Permissions:"); * for(Map.Entry entry : * attachment.getPermissions().entrySet()){ perms.add(" " + * entry.getKey() + " = " + entry.getValue()); } */ perms.add("Effective Permissions:"); for (final PermissionAttachmentInfo info : player.getEffectivePermissions()) { if (info.getValue() == true) perms.add(" " + info.getPermission() + " = " + info.getValue()); } return perms; } /** * Remove all attachments in case of a restart or reload. */ public void removeAllAttachments() { /* * Remove all attachments. */ for (final String key : attachments.keySet()) { attachments.get(key).remove(); } attachments.clear(); } public void reset() { /* * collect new permissions * and register all attachments. */ this.collectPermissions(); this.updateAllPlayers(); } /** * @param player_join * the player_join to set */ public void setPlayer_join(final boolean player_join) { this.player_join = player_join; } /** * force Bukkit to update every OnlinePlayers permissions. */ public void updateAllPlayers() { for (final Player player : Bukkit.getServer().getOnlinePlayers()) { updatePermissions(player); } } public void updatePermissions(final Player player) { this.updatePermissions(player, null); } /** * Push all permissions which are registered with GM for this player, on * this world to Bukkit and make it update for the child nodes. * * @param player * @param world */ public void updatePermissions(final Player player, String world) { if (player == null || !GroupManager.isLoaded()) { return; } final String name = player.getName(); // Reset the User objects player reference. final User user = plugin.getWorldsHolder().getWorldData(player.getWorld().getName()).getUser(name); if (user != null) user.updatePlayer(player); PermissionAttachment attachment; // Find the players current attachment, or add a new one. if (this.attachments.containsKey(name)) { attachment = this.attachments.get(name); } else { attachment = player.addAttachment(plugin); this.attachments.put(name, attachment); } if (world == null) { world = player.getWorld().getName(); } // Add all permissions for this player (GM only) // child nodes will be calculated by Bukkit. List playerPermArray = new ArrayList(plugin.getWorldsHolder().getWorldData(world).getPermissionsHandler().getAllPlayersPermissions(name, false)); final LinkedHashMap newPerms = new LinkedHashMap(); // Sort the perm list by parent/child, so it will push to superperms // correctly. playerPermArray = sort(playerPermArray); Boolean value = false; for (final String permission : playerPermArray) { value = (!permission.startsWith("-")); newPerms.put((value ? permission : permission.substring(1)), value); } /* * Do not push any perms to bukkit if... * We are in offline mode * and the player has the 'groupmanager.noofflineperms' permission. */ if (!Bukkit.getServer().getOnlineMode() && (newPerms.containsKey("groupmanager.noofflineperms") && (newPerms.get("groupmanager.noofflineperms") == true))) { removeAttachment(name); return; } /** * This is put in place until such a time as Bukkit pull 466 is * implemented https://github.com/Bukkit/Bukkit/pull/466 */ try { // Codename_B source synchronized (attachment.getPermissible()) { @SuppressWarnings("unchecked") final Map orig = (Map) permissions.get(attachment); // Clear the map (faster than removing the attachment and // recalculating) orig.clear(); // Then whack our map into there orig.putAll(newPerms); // That's all folks! attachment.getPermissible().recalculatePermissions(); } } catch (final IllegalArgumentException e) { e.printStackTrace(); } catch (final IllegalAccessException e) { e.printStackTrace(); } GroupManager.logger.finest("Attachment updated for: " + name); } /** * force Bukkit to update this Players permissions. */ public void updatePlayer(final Player player) { if (player != null) this.updatePermissions(player, null); } private void registerEvents() { final PluginManager manager = plugin.getServer().getPluginManager(); manager.registerEvents(new PlayerEvents(), plugin); manager.registerEvents(new BukkitEvents(), plugin); } /** * Force remove any attachments * * @param player */ private void removeAttachment(final String playerName) { if (attachments.containsKey(playerName)) { attachments.get(playerName).remove(); attachments.remove(playerName); } } /** * Sort a permission node list by parent/child * * @param permList * @return List sorted for priority */ private List sort(final List permList) { final List result = new ArrayList(); for (final String key : permList) { /* * Ignore stupid plugins which add empty permission nodes. */ if (!key.isEmpty()) { final String a = key.charAt(0) == '-' ? key.substring(1) : key; final Map allchildren = GroupManager.BukkitPermissions.getAllChildren(a, new HashSet()); if (allchildren != null) { final ListIterator itr = result.listIterator(); while (itr.hasNext()) { final String node = itr.next(); final String b = node.charAt(0) == '-' ? node.substring(1) : node; // Insert the parent node before the child if (allchildren.containsKey(b)) { itr.set(key); itr.add(node); break; } } } if (!result.contains(key)) result.add(key); } } return result; } protected class BukkitEvents implements Listener { @EventHandler(priority = EventPriority.NORMAL) public void onPluginDisable(final PluginDisableEvent event) { collectPermissions(); // updateAllPlayers(); } @EventHandler(priority = EventPriority.NORMAL) public void onPluginEnable(final PluginEnableEvent event) { if (!GroupManager.isLoaded()) return; collectPermissions(); updateAllPlayers(); } } /** * Player events tracked to cause Superperms updates * * @author ElgarL * */ protected class PlayerEvents implements Listener { @EventHandler(priority = EventPriority.LOWEST) public void onPlayerChangeWorld(final PlayerChangedWorldEvent event) { // has changed worlds updatePermissions(event.getPlayer(), event.getPlayer().getWorld().getName()); } @EventHandler(priority = EventPriority.LOWEST) public void onPlayerJoin(final PlayerJoinEvent event) { setPlayer_join(true); final Player player = event.getPlayer(); GroupManager.logger.finest("Player Join event: " + player.getName()); /* * Tidy up any lose ends */ removeAttachment(player.getName()); // force GM to create the player if they are not already listed. plugin.getWorldsHolder().getWorldData(player.getWorld().getName()).getUser(player.getUniqueId().toString(), player.getName()); setPlayer_join(false); updatePermissions(event.getPlayer()); setPlayer_join(false); } /* * Trigger at highest so we tidy up last. */ @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerQuit(final PlayerQuitEvent event) { if (!GroupManager.isLoaded()) return; final Player player = event.getPlayer(); /* * force remove any attachments as bukkit may not */ removeAttachment(player.getName()); } } }