Rewrite of cooldowns to use island ID instead of player ID

Adds cooldown to island leave command.

Tries to mitigate cooldown bypassing by players who change island owner
or use subowner or members to issue invites instead of owner. Now the
cooldown is based on the island ID itself.

Original API's are kept for compatibility with addons that use
cooldowns.

Relates to https://github.com/BentoBoxWorld/BentoBox/issues/727
This commit is contained in:
tastybento 2019-06-05 22:55:32 -07:00
parent d57a9aa6d8
commit 7b2e8a657e
14 changed files with 110 additions and 26 deletions

View File

@ -107,7 +107,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Cool down tracker
*/
private Map<UUID, Map<UUID, Long>> cooldowns = new HashMap<>();
private Map<String, Map<String, Long>> cooldowns = new HashMap<>();
/**
* Top level command
@ -670,32 +670,76 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Set a cool down - can be set by other commands on this one
* @param uniqueId - the caller
* @param uniqueId - the unique ID that is having the cooldown
* @param targetUUID - the target (if any)
* @param timeInSeconds - time in seconds to cool down
* @since 1.5.0
*/
public void setCooldown(UUID uniqueId, UUID targetUUID, int timeInSeconds) {
public void setCooldown(String uniqueId, String targetUUID, int timeInSeconds) {
cooldowns.putIfAbsent(uniqueId, new HashMap<>());
cooldowns.get(uniqueId).put(targetUUID, System.currentTimeMillis() + timeInSeconds * 1000);
}
/**
* Check if cool down is in progress
* Set a cool down - can be set by other commands on this one
* @param uniqueId - the UUID that is having the cooldown
* @param targetUUID - the target UUID (if any)
* @param timeInSeconds - time in seconds to cool down
*/
public void setCooldown(UUID uniqueId, UUID targetUUID, int timeInSeconds) {
cooldowns.putIfAbsent(uniqueId.toString(), new HashMap<>());
cooldowns.get(uniqueId.toString()).put(targetUUID == null ? null : targetUUID.toString(), System.currentTimeMillis() + timeInSeconds * 1000);
}
/**
* Set a cool down for a user - can be set by other commands on this one
* @param uniqueId - the UUID that is having the cooldown
* @param timeInSeconds - time in seconds to cool down
* @since 1.5.0
*/
public void setCooldown(UUID uniqueId, int timeInSeconds) {
setCooldown(uniqueId, null, timeInSeconds);
}
/**
* Check if cool down is in progress for user
* @param user - the caller of the command
* @param targetUUID - the target (if any)
* @return true if cool down in place, false if not
*/
protected boolean checkCooldown(User user, UUID targetUUID) {
if (!cooldowns.containsKey(user.getUniqueId()) || user.isOp() || user.hasPermission(getPermissionPrefix() + "mod.bypasscooldowns")) {
return checkCooldown(user, user.getUniqueId().toString(), targetUUID == null ? null : targetUUID.toString());
}
/**
* Check if cool down is in progress for user
* @param user - the user to check
* @return true if cool down in place, false if not
* @since 1.5.0
*/
protected boolean checkCooldown(User user) {
return checkCooldown(user, user.getUniqueId().toString(), null);
}
/**
* Check if cool down is in progress
* @param user - the caller of the command
* @param uniqueId - the id that needs to be checked
* @param targetUUID - the target (if any)
* @return true if cool down in place, false if not
* @since 1.5.0
*/
protected boolean checkCooldown(User user, String uniqueId, String targetUUID) {
if (!cooldowns.containsKey(uniqueId) || user.isOp() || user.hasPermission(getPermissionPrefix() + "mod.bypasscooldowns")) {
return false;
}
cooldowns.putIfAbsent(user.getUniqueId(), new HashMap<>());
if (cooldowns.get(user.getUniqueId()).getOrDefault(targetUUID, 0L) - System.currentTimeMillis() <= 0) {
cooldowns.putIfAbsent(uniqueId, new HashMap<>());
if (cooldowns.get(uniqueId).getOrDefault(targetUUID, 0L) - System.currentTimeMillis() <= 0) {
// Cool down is done
cooldowns.get(user.getUniqueId()).remove(targetUUID);
cooldowns.get(uniqueId).remove(targetUUID);
return false;
}
int timeToGo = (int) ((cooldowns.get(user.getUniqueId()).getOrDefault(targetUUID, 0L) - System.currentTimeMillis()) / 1000);
int timeToGo = (int) ((cooldowns.get(uniqueId).getOrDefault(targetUUID, 0L) - System.currentTimeMillis()) / 1000);
user.sendMessage("general.errors.you-must-wait", TextVariables.NUMBER, String.valueOf(timeToGo));
return true;
}

View File

@ -46,7 +46,8 @@ public class IslandBanCommand extends CompositeCommand {
return false;
}
// Check rank to use command
if (getIslands().getIsland(getWorld(), user).getRank(user) < getPlugin().getSettings().getRankCommand(getUsage())) {
Island island = getIslands().getIsland(getWorld(), user);
if (island.getRank(user) < getPlugin().getSettings().getRankCommand(getUsage())) {
user.sendMessage("general.errors.no-permission");
return false;
}
@ -69,7 +70,7 @@ public class IslandBanCommand extends CompositeCommand {
user.sendMessage("commands.island.ban.player-already-banned");
return false;
}
if (getSettings().getBanCooldown() > 0 && checkCooldown(user, targetUUID)) {
if (getSettings().getBanCooldown() > 0 && checkCooldown(user, island.getUniqueId(), targetUUID.toString())) {
return false;
}
User target = User.getInstance(targetUUID);

View File

@ -86,7 +86,7 @@ public class IslandCreateCommand extends CompositeCommand {
return false;
}
if (getSettings().isResetCooldownOnCreate()) {
getParent().getSubCommand("reset").ifPresent(resetCommand -> resetCommand.setCooldown(user.getUniqueId(), null, getSettings().getResetCooldown()));
getParent().getSubCommand("reset").ifPresent(resetCommand -> resetCommand.setCooldown(user.getUniqueId(), getSettings().getResetCooldown()));
}
return true;
}

View File

@ -35,7 +35,7 @@ public class IslandResetCommand extends ConfirmableCommand {
@Override
public boolean canExecute(User user, String label, List<String> args) {
// Check cooldown
if (getSettings().getResetCooldown() > 0 && checkCooldown(user, null)) {
if (getSettings().getResetCooldown() > 0 && checkCooldown(user)) {
return false;
}
@ -128,7 +128,7 @@ public class IslandResetCommand extends ConfirmableCommand {
user.sendMessage("commands.island.create.unable-create-island");
return false;
}
setCooldown(user.getUniqueId(), null, getSettings().getResetCooldown());
setCooldown(user.getUniqueId(), getSettings().getResetCooldown());
return true;
}
}

View File

@ -84,7 +84,7 @@ public class IslandUnbanCommand extends CompositeCommand {
// Set cooldown
if (getSettings().getBanCooldown() > 0 && getParent() != null) {
getParent().getSubCommand("ban").ifPresent(subCommand ->
subCommand.setCooldown(issuer.getUniqueId(), target.getUniqueId(), getSettings().getBanCooldown() * 60));
subCommand.setCooldown(island.getUniqueId(), target.getUniqueId().toString(), getSettings().getBanCooldown() * 60));
}
return true;
}

View File

@ -48,7 +48,8 @@ public class IslandTeamCoopCommand extends CompositeCommand {
return false;
}
// Check rank to use command
if (getIslands().getIsland(getWorld(), user).getRank(user) < getPlugin().getSettings().getRankCommand(getUsage())) {
Island island = getIslands().getIsland(getWorld(), user);
if (island.getRank(user) < getPlugin().getSettings().getRankCommand(getUsage())) {
user.sendMessage("general.errors.no-permission");
return false;
}
@ -59,7 +60,7 @@ public class IslandTeamCoopCommand extends CompositeCommand {
return false;
}
// Check cooldown
if (getSettings().getCoopCooldown() > 0 && checkCooldown(user, targetUUID)) {
if (getSettings().getCoopCooldown() > 0 && checkCooldown(user, island.getUniqueId(), targetUUID.toString())) {
return false;
}
// Player cannot coop themselves

View File

@ -77,7 +77,7 @@ public class IslandTeamInviteCommand extends CompositeCommand {
return false;
}
// Check cool down
if (getSettings().getInviteCooldown() > 0 && checkCooldown(user, invitedPlayerUUID)) {
if (getSettings().getInviteCooldown() > 0 && checkCooldown(user, getIslands().getIsland(getWorld(), user).getUniqueId(), invitedPlayerUUID.toString())) {
return false;
}
// Player cannot invite someone already on a team

View File

@ -105,7 +105,10 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
// Add cooldown for this player and target
if (getSettings().getInviteCooldown() > 0 && getParent() != null) {
// Get the invite class from the parent
getParent().getSubCommand("invite").ifPresent(c -> c.setCooldown(user.getUniqueId(), targetUUID, getSettings().getInviteCooldown() * 60));
getParent().getSubCommand("invite").ifPresent(c -> c.setCooldown(
oldIsland.getUniqueId(),
targetUUID.toString(),
getSettings().getInviteCooldown() * 60));
}
}
}

View File

@ -62,6 +62,11 @@ public class IslandTeamLeaveCommand extends ConfirmableCommand {
if (getSettings().isUseEconomy() && getIWM().isOnLeaveResetMoney(getWorld())) {
getPlugin().getVault().ifPresent(vault -> vault.withdraw(user, vault.getBalance(user)));
}
// Add cooldown for this player and target
if (getSettings().getInviteCooldown() > 0 && getParent() != null) {
// Get the invite class from the parent
getParent().getSubCommand("invite").ifPresent(c -> c.setCooldown(island.getUniqueId(), user.getUniqueId().toString(), getSettings().getInviteCooldown() * 60));
}
user.sendMessage("general.success");
// Fire event
IslandBaseEvent e = TeamEvent.builder()

View File

@ -44,7 +44,8 @@ public class IslandTeamTrustCommand extends CompositeCommand {
return false;
}
// Check rank to use command
if (getIslands().getIsland(getWorld(), user).getRank(user) < getPlugin().getSettings().getRankCommand(getUsage())) {
Island island = getIslands().getIsland(getWorld(), user);
if (island.getRank(user) < getPlugin().getSettings().getRankCommand(getUsage())) {
user.sendMessage("general.errors.no-permission");
return false;
}
@ -54,7 +55,7 @@ public class IslandTeamTrustCommand extends CompositeCommand {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
return (getSettings().getTrustCooldown() <= 0 || !checkCooldown(user, targetUUID)) && trustCmd(user, targetUUID);
return (getSettings().getTrustCooldown() <= 0 || !checkCooldown(user, island.getUniqueId(), targetUUID.toString())) && trustCmd(user, targetUUID);
}
private boolean trustCmd(User user, UUID targetUUID) {

View File

@ -86,7 +86,7 @@ public class IslandTeamUncoopCommand extends CompositeCommand {
// Set cooldown
if (getSettings().getCoopCooldown() > 0 && getParent() != null) {
getParent().getSubCommand("coop").ifPresent(subCommand ->
subCommand.setCooldown(user.getUniqueId(), targetUUID, getSettings().getCoopCooldown() * 60));
subCommand.setCooldown(island.getUniqueId(), targetUUID.toString(), getSettings().getCoopCooldown() * 60));
}
return true;
} else {

View File

@ -86,7 +86,7 @@ public class IslandTeamUntrustCommand extends CompositeCommand {
// Set cooldown
if (getSettings().getTrustCooldown() > 0 && getParent() != null) {
getParent().getSubCommand("trust").ifPresent(subCommand ->
subCommand.setCooldown(user.getUniqueId(), targetUUID, getSettings().getTrustCooldown() * 60));
subCommand.setCooldown(island.getUniqueId(), targetUUID.toString(), getSettings().getTrustCooldown() * 60));
}
return true;
} else {

View File

@ -33,6 +33,7 @@ import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
@ -140,6 +141,11 @@ public class IslandTeamKickCommandTest {
when(server.getPluginManager()).thenReturn(pim);
when(Bukkit.getServer()).thenReturn(server);
// Island
Island island = mock(Island.class);
when(island.getUniqueId()).thenReturn("uniqueid");
when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island);
}
/**
@ -296,6 +302,6 @@ public class IslandTeamKickCommandTest {
// 10 minutes = 600 seconds
when(s.getInviteCooldown()).thenReturn(10);
testExecuteNoConfirmation();
Mockito.verify(subCommand).setCooldown(uuid, notUUID, 600);
Mockito.verify(subCommand).setCooldown("uniqueid", notUUID.toString(), 600);
}
}

View File

@ -7,6 +7,7 @@ import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Bukkit;
@ -19,6 +20,7 @@ import org.bukkit.scheduler.BukkitScheduler;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
@ -29,6 +31,7 @@ import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
@ -49,6 +52,8 @@ public class IslandTeamLeaveCommandTest {
private IslandsManager im;
private IslandWorldManager iwm;
private Player player;
@Mock
private CompositeCommand subCommand;
/**
* @throws java.lang.Exception
@ -81,6 +86,8 @@ public class IslandTeamLeaveCommandTest {
// Parent command has no aliases
ic = mock(CompositeCommand.class);
when(ic.getSubCommandAliases()).thenReturn(new HashMap<>());
Optional<CompositeCommand> optionalCommand = Optional.of(subCommand);
when(ic.getSubCommand(Mockito.anyString())).thenReturn(optionalCommand);
// Player has island to begin with
im = mock(IslandsManager.class);
@ -107,6 +114,11 @@ public class IslandTeamLeaveCommandTest {
PluginManager pim = mock(PluginManager.class);
when(server.getPluginManager()).thenReturn(pim);
when(Bukkit.getServer()).thenReturn(server);
// Island
Island island = mock(Island.class);
when(island.getUniqueId()).thenReturn("uniqueid");
when(im.getIsland(Mockito.any(), Mockito.any(User.class))).thenReturn(island);
}
/**
@ -194,4 +206,15 @@ public class IslandTeamLeaveCommandTest {
Mockito.verify(enderChest).clear();
Mockito.verify(inv).clear();
}
/**
* Test method for {@link IslandTeamLeaveCommand#execute(User, String, java.util.List)}
*/
@Test
public void testCooldown() {
// 10 minutes = 600 seconds
when(s.getInviteCooldown()).thenReturn(10);
testExecuteNoConfirmation();
Mockito.verify(subCommand).setCooldown("uniqueid", uuid.toString(), 600);
}
}