mirror of
https://github.com/TotalFreedomMC/TF-EssentialsX.git
synced 2025-01-05 23:08:23 +00:00
Add permissions for individual colors (#1441)
* Add tests for existing format behavior * Replace formatting implementation * Add permissions for individual color codes Resolves #415 * Use format code names * Fix escaping * Mockito: test scope only * Explicitly check the .magic permission Once I switch to checking if a perm's set in the loop, the explicit check is needed for an * perm. * Add support for removing individual colors * Use `obfuscated` as the name for §k `magic` is still accepted as the group name, so this is not a breaking change.
This commit is contained in:
parent
5a0b7285d4
commit
7a73301a37
8 changed files with 236 additions and 27 deletions
|
@ -22,6 +22,8 @@ public interface IUser {
|
|||
|
||||
boolean isAuthorized(IEssentialsCommand cmd, String permissionPrefix);
|
||||
|
||||
boolean isPermissionSet(String node);
|
||||
|
||||
void healCooldown() throws Exception;
|
||||
|
||||
void giveMoney(BigDecimal value) throws MaxMoneyException;
|
||||
|
|
|
@ -97,6 +97,11 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPermissionSet(final String node) {
|
||||
return isPermSetCheck(node);
|
||||
}
|
||||
|
||||
private boolean isAuthorizedCheck(final String node) {
|
||||
|
||||
if (base instanceof OfflinePlayer) {
|
||||
|
@ -116,6 +121,24 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isPermSetCheck(final String node) {
|
||||
if (base instanceof OfflinePlayer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return ess.getPermissionsHandler().isPermissionSet(base, node);
|
||||
} catch (Exception ex) {
|
||||
if (ess.getSettings().isDebug()) {
|
||||
ess.getLogger().log(Level.SEVERE, "Permission System Error: " + ess.getPermissionsHandler().getName() + " returned: " + ex.getMessage(), ex);
|
||||
} else {
|
||||
ess.getLogger().log(Level.SEVERE, "Permission System Error: " + ess.getPermissionsHandler().getName() + " returned: " + ex.getMessage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void healCooldown() throws Exception {
|
||||
final Calendar now = new GregorianCalendar();
|
||||
|
|
|
@ -16,6 +16,9 @@ public interface IPermissionsHandler {
|
|||
|
||||
boolean hasPermission(Player base, String node);
|
||||
|
||||
// Does not check for * permissions
|
||||
boolean isPermissionSet(Player base, String node);
|
||||
|
||||
String getPrefix(Player base);
|
||||
|
||||
String getSuffix(Player base);
|
||||
|
|
|
@ -62,6 +62,11 @@ public class PermissionsHandler implements IPermissionsHandler {
|
|||
return handler.hasPermission(base, node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPermissionSet(final Player base, final String node) {
|
||||
return handler.isPermissionSet(base, node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefix(final Player base) {
|
||||
final long start = System.nanoTime();
|
||||
|
|
|
@ -49,6 +49,11 @@ public class SuperpermsHandler implements IPermissionsHandler {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPermissionSet(final Player base, final String node) {
|
||||
return base.isPermissionSet(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefix(final Player base) {
|
||||
return null;
|
||||
|
|
|
@ -3,23 +3,25 @@ package com.earth2me.essentials.utils;
|
|||
import net.ess3.api.IUser;
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
public class FormatUtil {
|
||||
private static final Set<ChatColor> COLORS = EnumSet.of(ChatColor.BLACK, ChatColor.DARK_BLUE, ChatColor.DARK_GREEN, ChatColor.DARK_AQUA, ChatColor.DARK_RED, ChatColor.DARK_PURPLE, ChatColor.GOLD, ChatColor.GRAY, ChatColor.DARK_GRAY, ChatColor.BLUE, ChatColor.GREEN, ChatColor.AQUA, ChatColor.RED, ChatColor.LIGHT_PURPLE, ChatColor.YELLOW, ChatColor.WHITE);
|
||||
private static final Set<ChatColor> FORMATS = EnumSet.of(ChatColor.BOLD, ChatColor.STRIKETHROUGH, ChatColor.UNDERLINE, ChatColor.ITALIC, ChatColor.RESET);
|
||||
private static final Set<ChatColor> MAGIC = EnumSet.of(ChatColor.MAGIC);
|
||||
|
||||
//Vanilla patterns used to strip existing formats
|
||||
static final transient Pattern VANILLA_COLOR_PATTERN = Pattern.compile("\u00a7+[0-9A-Fa-f]");
|
||||
static final transient Pattern VANILLA_MAGIC_PATTERN = Pattern.compile("\u00a7+[Kk]");
|
||||
static final transient Pattern VANILLA_FORMAT_PATTERN = Pattern.compile("\u00a7+[L-ORl-or]");
|
||||
private static final Pattern STRIP_ALL_PATTERN = Pattern.compile("\u00a7+([0-9a-fk-orA-FK-OR])");
|
||||
//Essentials '&' convention colour codes
|
||||
static final transient Pattern REPLACE_ALL_PATTERN = Pattern.compile("(?<!&)&([0-9a-fk-orA-FK-OR])");
|
||||
static final transient Pattern REPLACE_COLOR_PATTERN = Pattern.compile("(?<!&)&([0-9a-fA-F])");
|
||||
static final transient Pattern REPLACE_MAGIC_PATTERN = Pattern.compile("(?<!&)&([Kk])");
|
||||
static final transient Pattern REPLACE_FORMAT_PATTERN = Pattern.compile("(?<!&)&([l-orL-OR])");
|
||||
static final transient Pattern REPLACE_PATTERN = Pattern.compile("&&(?=[0-9a-fk-orA-FK-OR])");
|
||||
private static final Pattern REPLACE_ALL_PATTERN = Pattern.compile("(&)?&([0-9a-fk-orA-FK-OR])");
|
||||
//Used to prepare xmpp output
|
||||
static final transient Pattern LOGCOLOR_PATTERN = Pattern.compile("\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]");
|
||||
static final transient Pattern URL_PATTERN = Pattern.compile("((?:(?:https?)://)?[\\w-_\\.]{2,})\\.([a-zA-Z]{2,3}(?:/\\S+)?)");
|
||||
private static final Pattern LOGCOLOR_PATTERN = Pattern.compile("\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]");
|
||||
private static final Pattern URL_PATTERN = Pattern.compile("((?:(?:https?)://)?[\\w-_\\.]{2,})\\.([a-zA-Z]{2,3}(?:/\\S+)?)");
|
||||
public static final Pattern IPPATTERN = Pattern.compile("^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
|
||||
|
||||
//This method is used to simply strip the native minecraft colour codes
|
||||
|
@ -55,33 +57,88 @@ public class FormatUtil {
|
|||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
return replaceColor(input, REPLACE_ALL_PATTERN);
|
||||
return replaceColor(input, EnumSet.allOf(ChatColor.class));
|
||||
}
|
||||
|
||||
static String replaceColor(final String input, final Pattern pattern) {
|
||||
return REPLACE_PATTERN.matcher(pattern.matcher(input).replaceAll("\u00a7$1")).replaceAll("&");
|
||||
static String replaceColor(final String input, final Set<ChatColor> supported) {
|
||||
StringBuffer builder = new StringBuffer();
|
||||
Matcher matcher = REPLACE_ALL_PATTERN.matcher(input);
|
||||
searchLoop: while (matcher.find()) {
|
||||
boolean isEscaped = (matcher.group(1) != null);
|
||||
if (!isEscaped) {
|
||||
char code = matcher.group(2).toLowerCase(Locale.ROOT).charAt(0);
|
||||
for (ChatColor color : supported) {
|
||||
if (color.getChar() == code) {
|
||||
matcher.appendReplacement(builder, "\u00a7$2");
|
||||
continue searchLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Don't change & to section sign (or replace two &'s with one)
|
||||
matcher.appendReplacement(builder, "&$2");
|
||||
}
|
||||
matcher.appendTail(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
static String stripColor(final String input, final Set<ChatColor> strip) {
|
||||
StringBuffer builder = new StringBuffer();
|
||||
Matcher matcher = STRIP_ALL_PATTERN.matcher(input);
|
||||
searchLoop: while (matcher.find()) {
|
||||
char code = matcher.group(1).toLowerCase(Locale.ROOT).charAt(0);
|
||||
for (ChatColor color : strip) {
|
||||
if (color.getChar() == code) {
|
||||
matcher.appendReplacement(builder, "");
|
||||
continue searchLoop;
|
||||
}
|
||||
}
|
||||
// Don't replace
|
||||
matcher.appendReplacement(builder, "$0");
|
||||
}
|
||||
matcher.appendTail(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
//This is the general permission sensitive message format function, does not touch urls.
|
||||
public static String formatString(final IUser user, final String permBase, final String input) {
|
||||
if (input == null) {
|
||||
public static String formatString(final IUser user, final String permBase, String message) {
|
||||
if (message == null) {
|
||||
return null;
|
||||
}
|
||||
String message;
|
||||
EnumSet<ChatColor> supported = EnumSet.noneOf(ChatColor.class);
|
||||
if (user.isAuthorized(permBase + ".color")) {
|
||||
message = replaceColor(input, REPLACE_COLOR_PATTERN);
|
||||
} else {
|
||||
message = stripColor(input, VANILLA_COLOR_PATTERN);
|
||||
}
|
||||
if (user.isAuthorized(permBase + ".magic")) {
|
||||
message = replaceColor(message, REPLACE_MAGIC_PATTERN);
|
||||
} else {
|
||||
message = stripColor(message, VANILLA_MAGIC_PATTERN);
|
||||
supported.addAll(COLORS);
|
||||
}
|
||||
if (user.isAuthorized(permBase + ".format")) {
|
||||
message = replaceColor(message, REPLACE_FORMAT_PATTERN);
|
||||
} else {
|
||||
message = stripColor(message, VANILLA_FORMAT_PATTERN);
|
||||
supported.addAll(FORMATS);
|
||||
}
|
||||
if (user.isAuthorized(permBase + ".magic")) {
|
||||
supported.addAll(MAGIC);
|
||||
}
|
||||
for (ChatColor chatColor : ChatColor.values()) {
|
||||
String colorName = chatColor.name();
|
||||
if (chatColor == ChatColor.MAGIC) {
|
||||
// Bukkit's name doesn't match with vanilla's
|
||||
colorName = "obfuscated";
|
||||
}
|
||||
|
||||
final String node = permBase + "." + colorName.toLowerCase(Locale.ROOT);
|
||||
// Only handle individual colors that are explicitly added or removed.
|
||||
if (!user.isPermissionSet(node)) {
|
||||
continue;
|
||||
}
|
||||
if (user.isAuthorized(node)) {
|
||||
supported.add(chatColor);
|
||||
} else {
|
||||
supported.remove(chatColor);
|
||||
}
|
||||
}
|
||||
EnumSet<ChatColor> strip = EnumSet.complementOf(supported);
|
||||
|
||||
if (!supported.isEmpty()) {
|
||||
message = replaceColor(message, supported);
|
||||
}
|
||||
if (!strip.isEmpty()) {
|
||||
message = stripColor(message, strip);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package com.earth2me.essentials.utils;
|
||||
|
||||
import net.ess3.api.IUser;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class FormatUtilTest {
|
||||
|
||||
@Test
|
||||
public void testFormatCase() {
|
||||
checkFormatPerms("&aT&Aest", "&aT&Aest");
|
||||
checkFormatPerms("§aT§Aest", "Test");
|
||||
|
||||
checkFormatPerms("&aT&Aest", "§aT§Aest", "color");
|
||||
checkFormatPerms("§aT§Aest", "§aT§Aest", "color");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatCategoryPerms() {
|
||||
checkFormatPerms("Test", "Test");
|
||||
checkFormatPerms("Test", "Test", "color", "format");
|
||||
|
||||
checkFormatPerms("&1C&2o&3l&4o&5r&6m&7a&8t&9i&ac", "&1C&2o&3l&4o&5r&6m&7a&8t&9i&ac"); // Unchanged
|
||||
checkFormatPerms("§1C§2o§3l§4o§5r§6m§7a§8t§9i§ac", "Colormatic"); // Removed
|
||||
checkFormatPerms("&1C&2o&3l&4o&5r&6m&7a&8t&9i&ac", "§1C§2o§3l§4o§5r§6m§7a§8t§9i§ac", "color"); // Converted
|
||||
checkFormatPerms("§1C§2o§3l§4o§5r§6m§7a§8t§9i§ac", "§1C§2o§3l§4o§5r§6m§7a§8t§9i§ac", "color"); // Unchanged
|
||||
|
||||
checkFormatPerms("&kFUNKY LOL", "§kFUNKY LOL", "magic"); // Converted
|
||||
checkFormatPerms("§kFUNKY LOL", "§kFUNKY LOL", "magic"); // Unchanged
|
||||
|
||||
// Magic isn't included in the format group
|
||||
checkFormatPerms("&kFUNKY LOL", "&kFUNKY LOL"); // Unchanged
|
||||
checkFormatPerms("§kFUNKY LOL", "FUNKY LOL"); // Removed
|
||||
checkFormatPerms("&kFUNKY LOL", "&kFUNKY LOL", "format"); // Unchanged
|
||||
checkFormatPerms("§kFUNKY LOL", "FUNKY LOL", "format"); // Removed
|
||||
|
||||
checkFormatPerms("&f<est", "&f<est");
|
||||
checkFormatPerms("§f§ltest", "test");
|
||||
checkFormatPerms("&f<est", "§f<est", "color");
|
||||
checkFormatPerms("§f§ltest", "§ftest", "color");
|
||||
checkFormatPerms("&f<est", "&f§ltest", "format");
|
||||
checkFormatPerms("§f§ltest", "§ltest", "format");
|
||||
checkFormatPerms("&f<est", "§f§ltest", "color", "format");
|
||||
checkFormatPerms("§f§ltest", "§f§ltest", "color", "format");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatCodePerms() {
|
||||
checkFormatPerms("&1Te&2st", "&1Te&2st");
|
||||
checkFormatPerms("§1Te§2st", "Test");
|
||||
|
||||
checkFormatPerms("&1Te&2st", "§1Te&2st", "dark_blue");
|
||||
checkFormatPerms("§1Te§2st", "§1Test", "dark_blue");
|
||||
|
||||
checkFormatPerms("&1Te&2st", "&1Te§2st", "dark_green");
|
||||
checkFormatPerms("§1Te§2st", "Te§2st", "dark_green");
|
||||
|
||||
checkFormatPerms("&1Te&2st", "§1Te§2st", "dark_blue", "dark_green");
|
||||
checkFormatPerms("§1Te§2st", "§1Te§2st", "dark_blue", "dark_green");
|
||||
|
||||
// Obfuscated behaves the same as magic
|
||||
checkFormatPerms("&kFUNKY LOL", "§kFUNKY LOL", "obfuscated");
|
||||
checkFormatPerms("§kFUNKY LOL", "§kFUNKY LOL", "obfuscated");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatAddRemovePerms() {
|
||||
checkFormatPerms("&1Te&2st&ling", "&1Te§2st&ling", "color", "-dark_blue");
|
||||
checkFormatPerms("§1Te§2st§ling", "Te§2sting", "color", "-dark_blue");
|
||||
|
||||
// Nothing happens when negated without being previously present
|
||||
checkFormatPerms("&1Te&2st&ling", "&1Te§2st&ling", "color", "-dark_blue", "-bold");
|
||||
checkFormatPerms("§1Te§2st§ling", "Te§2sting", "color", "-dark_blue", "-bold");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatEscaping() {
|
||||
// Don't do anything to non-format codes
|
||||
checkFormatPerms("True & false", "True & false");
|
||||
checkFormatPerms("True && false", "True && false");
|
||||
|
||||
// Formats are only unescaped if you have the right perms
|
||||
checkFormatPerms("This is &&a message", "This is &&a message");
|
||||
checkFormatPerms("This is &&a message", "This is &a message", "color");
|
||||
|
||||
// Can't put an & before a non-escaped format
|
||||
checkFormatPerms("This is &&&a message", "This is &&&a message");
|
||||
checkFormatPerms("This is &&&a message", "This is &&a message", "color");
|
||||
}
|
||||
|
||||
private void checkFormatPerms(String input, String expectedOutput, String... perms) {
|
||||
IUser user = mock(IUser.class);
|
||||
for (String perm : perms) {
|
||||
if (perm.startsWith("-")) {
|
||||
// Negated perms
|
||||
perm = perm.substring(1);
|
||||
when(user.isAuthorized("essentials.chat." + perm)).thenReturn(false);
|
||||
} else {
|
||||
when(user.isAuthorized("essentials.chat." + perm)).thenReturn(true);
|
||||
}
|
||||
|
||||
when(user.isPermissionSet("essentials.chat." + perm)).thenReturn(true);
|
||||
}
|
||||
assertEquals(expectedOutput, FormatUtil.formatString(user, "essentials.chat", input));
|
||||
}
|
||||
}
|
6
pom.xml
6
pom.xml
|
@ -67,6 +67,12 @@
|
|||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>2.8.47</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
Loading…
Reference in a new issue