[BREAKING] Remove legacy behavior for notify permissions.

* Remove the old per-world permission cache.
* Use the new permission caching feature.
* Always use permission subscriptions.

Pitfalls:
* Permission subscriptions might fail under certain conditions
(legacy?).
This commit is contained in:
asofold 2018-02-07 15:15:34 +01:00
parent c50cc4f052
commit 6f4c61d8f3
6 changed files with 26 additions and 322 deletions

View File

@ -1,100 +0,0 @@
/*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.components.registry.feature;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map.Entry;
import java.util.Set;
/**
* Store set of player names by defaultPermissions they have to get players with a certain permission.<br>
* TODO: Might later detach to an interface (+SimpleNameSetPermStateHolder).
* @author mc_dev
*
*/
public class NameSetPermState implements PermStateReceiver{
/** Map permission to player names (all exact case). */
protected final HashMap<String, Set<String>> playerSets = new HashMap<String, Set<String>>();
protected String[] defaultPermissions;
public NameSetPermState(String... permissions){
this.defaultPermissions = permissions;
}
@Override
public String[] getDefaultPermissions() {
return defaultPermissions;
}
@Override
public boolean hasPermission(final String player, final String permission) {
final Set<String> names = playerSets.get(permission);
if (names == null) return false;
return (names.contains(player));
}
@Override
public void setPermission(final String player, final String permission, boolean state) {
Set<String> names = playerSets.get(permission);
if (names == null){
if (!state) return;
names = new LinkedHashSet<String>(20);
playerSets.put(permission, names);
}
if (state) names.add(player);
else names.remove(player);
}
@Override
public void removePlayer(final String player) {
// TODO: Something more efficient ? [mostly used with few permissions, though].
final Iterator<Entry<String, Set<String>>> it = playerSets.entrySet().iterator();
while (it.hasNext()){
final Entry<String, Set<String>> entry = it.next();
final Set<String> set = entry.getValue();
set.remove(player);
if (set.isEmpty()) it.remove();
}
}
/**
* Get a set with the players that hold the permission.
* @param permission
* @return
*/
public Set<String> getPlayers(final String permission){
return playerSets.get(permission);
}
public void addDefaultPermissions(Collection<String> permissions){
Collection<String> newDefaults = new HashSet<String>();
newDefaults.addAll(Arrays.asList(this.defaultPermissions));
newDefaults.addAll(permissions);
}
public void setDefaultPermissions(Collection<String> permissions){
defaultPermissions = new String[permissions.size()];
permissions.toArray(defaultPermissions);
}
}

View File

@ -1,40 +0,0 @@
/*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.components.registry.feature;
/**
* Permission cache. Allow to query permissions (a defined set of permissions), to be registered and automatically be updated, according to registry.<br>
* The permissions are not updated in real time but on certain events, to be specified by the registry.
*
* @author mc_dev
*
*/
public interface PermStateHolder {
/**
* Get the default permissions that are guaranteed to be held here.
* @return
*/
public String[] getDefaultPermissions();
/**
* Test a permission. If not available the result will be false, no updating of permissions is expected on calling this.
* @param player
* @param permission
* @return
*/
public boolean hasPermission(String player, String permission);
}

View File

@ -1,30 +0,0 @@
/*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.components.registry.feature;
/**
* Receive permission changes for certain permissions.
* @author mc_dev
*
*/
public interface PermStateReceiver extends PermStateHolder{
public void setPermission(String player, String permission, boolean state);
public void removePlayer(String player);
}

View File

@ -91,7 +91,6 @@ public abstract class ConfPaths {
public static final String LOGGING_BACKEND_FILE_PREFIX = LOGGING_BACKEND_FILE + "prefix";
private static final String LOGGING_BACKEND_INGAMECHAT = LOGGING_BACKEND + "ingamechat.";
public static final String LOGGING_BACKEND_INGAMECHAT_ACTIVE = LOGGING_BACKEND_INGAMECHAT + "active";
public static final String LOGGING_BACKEND_INGAMECHAT_SUBSCRIPTIONS = LOGGING_BACKEND_INGAMECHAT + "subscriptions";
public static final String LOGGING_BACKEND_INGAMECHAT_PREFIX = LOGGING_BACKEND_INGAMECHAT + "prefix";
private static final String LOGGING_EXTENDED = LOGGING + "extended.";
@ -791,8 +790,6 @@ public abstract class ConfPaths {
public static final String LOGGING_FILENAME = "logging.filename";
@Moved(newPath = LOGGING_BACKEND_INGAMECHAT_ACTIVE)
public static final String LOGGING_INGAMECHAT = "logging.ingamechat";
@Moved(newPath = LOGGING_BACKEND_INGAMECHAT_SUBSCRIPTIONS)
public static final String LOGGING_USESUBSCRIPTIONS = "logging.usesubscriptions";
@Moved(newPath = PROTECT_PLUGINS_HIDE_ACTIVE)
public static final String MISCELLANEOUS_PROTECTPLUGINS = "miscellaneous.protectplugins";
@Moved(newPath = PROTECT_CLIENTS_MOTD_ALLOWALL)
@ -870,6 +867,10 @@ public abstract class ConfPaths {
public static final String MISCELLANEOUS_MANAGELISTENERS = "miscellaneous.managelisteners";
@Deprecated
public static final String COMPATIBILITY_MANAGELISTENERS = "compatibility.managelisteners";
@Deprecated
public static final String LOGGING_USESUBSCRIPTIONS = "logging.usesubscriptions";
@Deprecated
public static final String LOGGING_BACKEND_INGAMECHAT_SUBSCRIPTIONS = "logging.backend.ingamechat.subscriptions";
/**
* Get moved paths for which an annotation doesn't work.

View File

@ -64,7 +64,6 @@ public class DefaultConfig extends ConfigFile {
set(ConfPaths.LOGGING_BACKEND_FILE_FILENAME, "nocheatplus.log", 785);
set(ConfPaths.LOGGING_BACKEND_INGAMECHAT_ACTIVE, true, 785);
set(ConfPaths.LOGGING_BACKEND_INGAMECHAT_PREFIX, "&cNCP: &f", 785);
set(ConfPaths.LOGGING_BACKEND_INGAMECHAT_SUBSCRIPTIONS, false, 785);
// Data settings.
// Expired offline players data.
@ -80,6 +79,7 @@ public class DefaultConfig extends ConfigFile {
// Permission settings.
set(ConfPaths.PERMISSIONS_POLICY_DEFAULT, "ALWAYS", 1140);
set(ConfPaths.PERMISSIONS_POLICY_RULES, Arrays.asList(
"nocheatplus.notify :: INTERVAL:60, -world, -offline", // Not sure about this one.
"nocheatplus.admin.debug :: INTERVAL:5",
"nocheatplus.admin* :: ALWAYS",
// TODO: NOTIFY (not command).
@ -96,7 +96,7 @@ public class DefaultConfig extends ConfigFile {
"nocheatplus.checks.chat.* :: INTERVAL:2",
"nocheatplus.checks.net.* :: INTERVAL:2",
"nocheatplus.checks.moving.survivalfly.* :: INTERVAL:5" // (Excludes the sf base permission.)
), 1140);
), 1142);
// Protection features.
// Hide plugins.

View File

@ -39,7 +39,6 @@ import org.bukkit.event.Event;
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.PlayerKickEvent;
import org.bukkit.event.player.PlayerLoginEvent;
@ -95,8 +94,6 @@ import fr.neatmonster.nocheatplus.components.registry.feature.IPostRegisterRunna
import fr.neatmonster.nocheatplus.components.registry.feature.IRegisterAsGenericInstance;
import fr.neatmonster.nocheatplus.components.registry.feature.JoinLeaveListener;
import fr.neatmonster.nocheatplus.components.registry.feature.NCPListener;
import fr.neatmonster.nocheatplus.components.registry.feature.NameSetPermState;
import fr.neatmonster.nocheatplus.components.registry.feature.PermStateReceiver;
import fr.neatmonster.nocheatplus.components.registry.feature.TickListener;
import fr.neatmonster.nocheatplus.components.registry.order.SetupOrder;
import fr.neatmonster.nocheatplus.config.ConfPaths;
@ -156,10 +153,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
/** Central logging access point. */
private BukkitLogManager logManager = null; // Not final, but intended to stay, once set [change to init=syso?].
/** Names of players with a certain permission. */
// TODO: Maintain NOTIFY within DataManager, set per world fetching policy.
private final NameSetPermState nameSetPerms = new NameSetPermState(Permissions.NOTIFY.getLowerCaseStringRepresentation());
/** Lower case player name to milliseconds point of time of release */
private final Map<String, Long> denyLoginNames = Collections.synchronizedMap(new HashMap<String, Long>());
@ -185,8 +178,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
private final EventRegistryBukkit eventRegistry = new EventRegistryBukkit(this);
private boolean lateListenerRegistered = false;
/** The event listeners (Bukkit Listener, MiniListener). */
private final List<Object> listeners = new ArrayList<Object>();
@ -194,12 +185,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
* (Kept here, for if during runtime some might get added.)*/
private final List<INotifyReload> notifyReload = new LinkedList<INotifyReload>();
/** If to use subscriptions or not. */
private boolean useSubscriptions = false;
/** Permission states stored on a per-world basis, updated with join/quit/kick. */
private final List<PermStateReceiver> permStateReceivers = new ArrayList<PermStateReceiver>();
/** Components that check consistency. */
private final List<ConsistencyChecker> consistencyCheckers = new ArrayList<ConsistencyChecker>();
@ -370,46 +355,8 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
@Override
public int sendAdminNotifyMessage(final String message) {
if (useSubscriptions) {
// TODO: Might respect console settings, or add extra config section (e.g. notifications).
return sendAdminNotifyMessageSubscriptions(message);
}
else {
return sendAdminNotifyMessageStored(message);
}
}
@Deprecated
private boolean hasTurnedOffNotifications(final String playerName) {
final PlayerData data = DataManager.getPlayerData(playerName);
return data != null && data.getNotifyOff();
}
private boolean hasTurnedOffNotifications(final Player player) {
return DataManager.getPlayerData(player).getNotifyOff();
}
/**
* Send notification to players with stored notify-permission (world changes, login, permissions are not re-checked here).
* @param message
* @return
*/
public int sendAdminNotifyMessageStored(final String message) {
final Set<String> names = nameSetPerms.getPlayers(Permissions.NOTIFY.getLowerCaseStringRepresentation());
if (names == null) return 0;
int done = 0;
for (final String name : names) {
if (hasTurnedOffNotifications(name)) {
// Has turned off notifications.
continue;
}
final Player player = DataManager.getPlayerExact(name);
if (player != null) {
player.sendMessage(message);
done ++;
}
}
return done;
// TODO: Does this always work?
return sendAdminNotifyMessageSubscriptions(message);
}
/**
@ -423,32 +370,25 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
final Permission bukkitPerm = Permissions.NOTIFY.getBukkitPermission();
final Set<Permissible> permissibles = Bukkit.getPluginManager().getPermissionSubscriptions(
lcPerm);
final Set<String> names = nameSetPerms.getPlayers(Permissions.NOTIFY.getLowerCaseStringRepresentation());
final Set<String> done = new HashSet<String>(permissibles.size() + (names == null ? 0 : names.size()));
final Set<String> done = new HashSet<String>(permissibles.size());
for (final Permissible permissible : permissibles) {
if (permissible instanceof CommandSender && permissible.hasPermission(bukkitPerm)) {
if (permissible instanceof CommandSender) {
final CommandSender sender = (CommandSender) permissible;
if ((sender instanceof Player) && hasTurnedOffNotifications((Player) sender)) {
continue;
}
sender.sendMessage(message);
done.add(sender.getName());
}
}
// Fall-back checking for players.
if (names != null) {
for (final String name : names) {
if (!done.contains(name)) {
final Player player = DataManager.getPlayerExact(name);
if (player != null && player.hasPermission(bukkitPerm)) {
if (hasTurnedOffNotifications(player)) {
continue;
}
player.sendMessage(message);
done.add(name);
if (sender instanceof Player) {
// Use the permission caching feature.
final Player player = (Player) sender;
final PlayerData data = DataManager.getPlayerData(player);
if (data.getNotifyOff() || !data.hasPermission(Permissions.NOTIFY, player)) {
continue;
}
}
else if (!sender.hasPermission(bukkitPerm)) {
// TODO: Add permission cache for non-player-things?
continue;
}
// Finally send.
sender.sendMessage(message);
done.add(sender.getName());
}
}
return done.size();
@ -543,11 +483,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
TickTask.addTickListener((TickListener) obj);
added = true;
}
if (obj instanceof PermStateReceiver) {
// No immediate update done.
permStateReceivers.add((PermStateReceiver) obj);
added = true;
}
if (obj instanceof ConsistencyChecker) {
consistencyCheckers.add((ConsistencyChecker) obj);
added = true;
@ -621,9 +556,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
listeners.remove(obj);
eventRegistry.unregisterAttached(obj); // Never know (e.g. attach all listeners to each other).
}
if (obj instanceof PermStateReceiver) {
permStateReceivers.remove((PermStateReceiver) obj);
}
if (obj instanceof TickListener) {
TickTask.removeTickListener((TickListener) obj);
}
@ -684,7 +616,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
}
// TODO: Prevent register feature ?
eventRegistry.clear();
lateListenerRegistered = false;
BukkitScheduler sched = getServer().getScheduler();
@ -782,8 +713,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
listeners.clear();
// Remove config listeners.
notifyReload.clear();
// World specific permissions.
permStateReceivers.clear();
// Sub registries.
subRegistries.clear();
// Just in case: clear the subComponentHolders.
@ -992,7 +921,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
// Add the "low level" system components first.
for (final Object obj : new Object[]{
nameSetPerms,
getCoreListener(),
// Put ReloadListener first, because Checks could also listen to it.
new ReloadHook(),
@ -1159,7 +1087,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
logManager.severe(Streams.INIT, t);
}
for (final Player player : onlinePlayers) {
updatePermStateReceivers(player);
if (player.isSleeping()) {
CombinedData.getData(player).wasInBed = true;
}
@ -1167,10 +1094,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
if (onlinePlayers.length > 0) {
logManager.info(Streams.INIT, "Updated data for " + onlinePlayers.length + " players (post-enable).");
}
// Register late listener.
// TODO: Instead register a MiniListener with appropriate order (see getCoreListener).
Bukkit.getPluginManager().registerEvents(getLateListener(), this);
lateListenerRegistered = true;
// Finished.
logManager.info(Streams.INIT, "Post-enable finished.");
// Log version to file (queued).
@ -1225,7 +1148,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
private void setInstanceMembers(final ConfigFile config) {
configProblemsChat = ConfigManager.isConfigUpToDate(config, config.getInt(ConfPaths.CONFIGVERSION_NOTIFYMAXPATHS));
configProblemsFile = configProblemsChat == null ? null : ConfigManager.isConfigUpToDate(config, -1);
useSubscriptions = config.getBoolean(ConfPaths.LOGGING_BACKEND_INGAMECHAT_SUBSCRIPTIONS);
clearExemptionsOnJoin = config.getBoolean(ConfPaths.COMPATIBILITY_EXEMPTIONS_REMOVE_JOIN);
clearExemptionsOnLeave = config.getBoolean(ConfPaths.COMPATIBILITY_EXEMPTIONS_REMOVE_LEAVE);
NCPExemptionManager.setExemptionSettings(new ExemptionSettings(config));
@ -1309,20 +1231,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
});
}
/**
* Listener to be registered in postEnable. Flag must be set elsewhere.
*
* @return
*/
private Listener getLateListener() {
return new NCPListener() {
@EventHandler(priority = EventPriority.LOWEST) // Do update comment in NoCheatPlusAPI with changing.
public void onPlayerJoinLowestLate(final PlayerJoinEvent event) {
updatePermStateReceivers(event.getPlayer());
}
};
}
/**
* Quick solution to hide the listener methods, expect refactoring.
* @return
@ -1352,19 +1260,8 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
@EventHandler(priority = EventPriority.LOWEST) // Do update comment in NoCheatPlusAPI with changing.
public void onPlayerJoinLowest(final PlayerJoinEvent event) {
final Player player = event.getPlayer();
if (!lateListenerRegistered) {
/*
* TODO: Register a MiniListener with appropriate order set
* (needs order to be set everywhere).
*/
// Let's see if this gets logged with big servers :).
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().warning(Streams.STATUS, "Player " + player.getName() + " joins before the post-enable task has run.");
// (Assume postEnable will run and call updatePermStateReceivers(player).)
} else {
updatePermStateReceivers(player);
}
if (clearExemptionsOnJoin) {
final Player player = event.getPlayer();
NCPExemptionManager.unexempt(player);
}
}
@ -1375,11 +1272,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
onJoinLow(event.getPlayer());
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerchangedWorld(final PlayerChangedWorldEvent event) {
updatePermStateReceivers(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerKick(final PlayerKickEvent event) {
onLeave(event.getPlayer());
@ -1394,9 +1286,9 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
private void onJoinLow(final Player player) {
final String playerName = player.getName();
if (nameSetPerms.hasPermission(playerName, Permissions.NOTIFY.getLowerCaseStringRepresentation())) {
final PlayerData data = DataManager.getPlayerData(player);
if (data.hasPermission(Permissions.NOTIFY, player)) { // Updates the cache.
// Login notifications...
final PlayerData data = DataManager.getPlayerData(player);
// // Update available.
// if (updateAvailable) player.sendMessage(ChatColor.RED + "NCP: " + ChatColor.WHITE + "A new update of NoCheatPlus is available.\n" + "Download it at http://nocheatplus.org/update");
@ -1425,9 +1317,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
}
private void onLeave(final Player player) {
for (final PermStateReceiver pr : permStateReceivers) {
pr.removePlayer(player.getName());
}
for (final JoinLeaveListener jlListener : joinLeaveListeners) {
try{
jlListener.playerLeaves(player);
@ -1442,22 +1331,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
}
}
private void updatePermStateReceivers(final Player player) {
final Map<String, Boolean> checked = new HashMap<String, Boolean>(20);
final String name = player.getName();
// TODO: Get rid / use PlayerData instead !
for (final PermStateReceiver pr : permStateReceivers) {
for (final String permission : pr.getDefaultPermissions()) {
Boolean state = checked.get(permission);
if (state == null) {
state = player.hasPermission(permission);
checked.put(permission, state);
}
pr.setPermission(name, permission, state);
}
}
}
private void scheduleConsistencyCheckers() {
BukkitScheduler sched = getServer().getScheduler();
if (consistencyCheckerTaskId != -1) {