diff --git a/.locale/en_US.yml b/.locale/en_US.yml index 395cc47c8..6854029b9 100644 --- a/.locale/en_US.yml +++ b/.locale/en_US.yml @@ -31,7 +31,6 @@ user-invalid-entry: "&4{0}&c is not a valid username/uuid." group-invalid-entry: "&4{0}&c is not a valid group name." track-invalid-entry: "&4{0}&c is not a valid track name." server-world-invalid-entry: "&cServer/world names can only contain alphanumeric characters and cannot exceed 36 characters in length." -use-inherit-command: "&cUse the 'parent add' and 'parent remove' commands instead of specifying the node." verbose-invalid-filter: "&4{0}&c is not a valid verbose filter." verbose-on: "&bVerbose logging &aenabled &bfor checks matching &aANY&b." verbose-on-query: "&bVerbose logging &aenabled &bfor checks matching &a{0}&b." @@ -57,7 +56,10 @@ apply-edits-target-group-not-exists: "&cTarget group &4{0}&c does not exist." apply-edits-target-user-not-uuid: "&cTarget user &4{0}&c is not a valid uuid." apply-edits-target-user-unable-to-load: "&cUnable to load target user &4{0}&c." apply-edits-target-unknown: "&cInvalid target. &7({0})" -apply-edits-success: "&aSuccessfully applied &b{0}&a nodes to &b{1}&a." +apply-edits-success: "&aWeb editor data was applied to &b{0}&a successfully." +apply-edits-success-summary: "&7(&a{0} &7{1} and &c{2} &7{3})" +apply-edits-diff-added: "&a+ &f{0}" +apply-edits-diff-removed: "&c- &f{0}" editor-upload-failure: "&cUnable to upload permission data to the editor." editor-url: "&aEditor URL:" check-result: "&aPermission check result on user &b{0}&a for permission &b{1}&a: &f{2}" @@ -234,6 +236,11 @@ group-info-general: > {PREFIX}&f- &3Suffixes: &a{6}\n {PREFIX}&f- &3Meta: &a{7} group-set-weight: "&aSet weight to &b{0}&a for group &b{1}&a." +group-set-display-name-doesnt-have: "&b{0}&a doesn't have a display name set." +group-set-display-name-already-has: "&b{0}&a already has a display name of &b{1}&a." +group-set-display-name-already-in-use: "&aThe display name &b{0}&a is already being used by &b{1}&a." +group-set-display-name: "&aSet display name to &b{0}&a for group &b{1}&a." +group-set-display-name-removed: "&aRemoved display name for group &b{0}&a." track-info: > {PREFIX}&b&l> &bShowing Track: &f{0}\n {PREFIX}&f- &7Path: &f{1} diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/CommandManager.java b/common/src/main/java/me/lucko/luckperms/common/commands/CommandManager.java index d63f1924d..093e80d32 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/CommandManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/CommandManager.java @@ -362,6 +362,7 @@ public class CommandManager { case "e": args.remove(2); args.add(2, "editor"); + break; // Provide backwards compatibility case "setprimarygroup": diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/other/HolderEditor.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/other/HolderEditor.java index 024e24ebd..b9a9038be 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/other/HolderEditor.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/other/HolderEditor.java @@ -25,13 +25,10 @@ package me.lucko.luckperms.common.commands.impl.generic.other; -import com.google.common.base.Splitter; -import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; -import com.google.gson.stream.JsonWriter; import me.lucko.luckperms.common.commands.ArgumentPermissions; import me.lucko.luckperms.common.commands.CommandException; @@ -40,16 +37,15 @@ import me.lucko.luckperms.common.commands.abstraction.SubCommand; import me.lucko.luckperms.common.commands.sender.Sender; import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.constants.CommandPermission; -import me.lucko.luckperms.common.contexts.ContextSetJsonSerializer; import me.lucko.luckperms.common.locale.CommandSpec; import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.Message; -import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.node.NodeModel; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.utils.Predicates; +import me.lucko.luckperms.common.webeditor.WebEditorUtils; import net.kyori.text.Component; import net.kyori.text.TextComponent; @@ -57,21 +53,9 @@ import net.kyori.text.event.ClickEvent; import net.kyori.text.event.HoverEvent; import net.kyori.text.format.TextColor; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.StringWriter; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.stream.Stream; public class HolderEditor extends SubCommand { - private static final String USER_ID_PATTERN = "user/"; - private static final String GROUP_ID_PATTERN = "group/"; - private static final String FILE_NAME = "luckperms-data.json"; - public HolderEditor(LocaleManager locale, boolean user) { super(CommandSpec.HOLDER_EDITOR.spec(locale), "editor", user ? CommandPermission.USER_EDITOR : CommandPermission.GROUP_EDITOR, Predicates.alwaysFalse()); } @@ -85,7 +69,7 @@ public class HolderEditor extends SubCommand { // form the payload data JsonObject payload = new JsonObject(); - payload.addProperty("who", id(holder)); + payload.addProperty("who", WebEditorUtils.getHolderIdentifier(holder)); payload.addProperty("whoFriendly", holder.getFriendlyName()); if (holder instanceof User) { payload.addProperty("whoUuid", ((User) holder).getUuid().toString()); @@ -96,7 +80,7 @@ public class HolderEditor extends SubCommand { payload.addProperty("time", System.currentTimeMillis()); // attach the holders permissions - payload.add("nodes", serializePermissions(holder.getEnduringNodes().values().stream().map(NodeModel::fromNode))); + payload.add("nodes", WebEditorUtils.serializePermissions(holder.getEnduringNodes().values().stream().map(NodeModel::fromNode))); // attach an array of all permissions known to the server, to use for tab completion in the editor JsonArray knownPermsArray = new JsonArray(); @@ -106,18 +90,14 @@ public class HolderEditor extends SubCommand { payload.add("knownPermissions", knownPermsArray); // upload the payload data to gist - String dataUrl = paste(new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(payload)); - if (dataUrl == null) { + String gistId = WebEditorUtils.postToGist(new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(payload)); + if (gistId == null) { Message.EDITOR_UPLOAD_FAILURE.send(sender); return CommandResult.STATE_ERROR; } - // extract the information we need from the gist url - List parts = Splitter.on('/').splitToList(dataUrl); - String id = "?" + parts.get(4) + "/" + parts.get(6); - // form a url for the editor - String url = plugin.getConfiguration().get(ConfigKeys.WEB_EDITOR_URL_PATTERN) + id; + String url = plugin.getConfiguration().get(ConfigKeys.WEB_EDITOR_URL_PATTERN) + "?" + gistId; Message.EDITOR_URL.send(sender); @@ -130,89 +110,4 @@ public class HolderEditor extends SubCommand { return CommandResult.SUCCESS; } - private static String id(PermissionHolder holder) { - if (holder instanceof User) { - User user = ((User) holder); - return USER_ID_PATTERN + user.getUuid().toString(); - } else { - Group group = ((Group) holder); - return GROUP_ID_PATTERN + group.getName(); - } - } - - private static String paste(String content) { - HttpURLConnection connection = null; - try { - connection = (HttpURLConnection) new URL("https://api.github.com/gists").openConnection(); - connection.setRequestMethod("POST"); - connection.setDoInput(true); - connection.setDoOutput(true); - - try (OutputStream os = connection.getOutputStream()) { - StringWriter sw = new StringWriter(); - new JsonWriter(sw).beginObject() - .name("description").value("LuckPerms Web Permissions Editor Data") - .name("public").value(false) - .name("files") - .beginObject().name(FILE_NAME) - .beginObject().name("content").value(content) - .endObject() - .endObject() - .endObject(); - - os.write(sw.toString().getBytes(StandardCharsets.UTF_8)); - } - - if (connection.getResponseCode() >= 400) { - throw new RuntimeException("Connection returned response code: " + connection.getResponseCode() + " - " + connection.getResponseMessage()); - } - - try (InputStream inputStream = connection.getInputStream()) { - try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { - JsonObject response = new Gson().fromJson(reader, JsonObject.class); - return response.get("files").getAsJsonObject().get(FILE_NAME).getAsJsonObject().get("raw_url").getAsString(); - } - } - - } catch (Exception e) { - e.printStackTrace(); - return null; - } finally { - if (connection != null) { - try { - connection.disconnect(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } - - private static JsonArray serializePermissions(Stream nodes) { - JsonArray arr = new JsonArray(); - nodes.forEach(node -> { - JsonObject attributes = new JsonObject(); - attributes.addProperty("permission", node.getPermission()); - attributes.addProperty("value", node.getValue()); - - if (!node.getServer().equals("global")) { - attributes.addProperty("server", node.getServer()); - } - - if (!node.getWorld().equals("global")) { - attributes.addProperty("world", node.getWorld()); - } - - if (node.getExpiry() != 0L) { - attributes.addProperty("expiry", node.getExpiry()); - } - - if (!node.getContexts().isEmpty()) { - attributes.add("context", ContextSetJsonSerializer.serializeContextSet(node.getContexts())); - } - - arr.add(attributes); - }); - return arr; - } } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/parent/ParentInfo.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/parent/ParentInfo.java index fd358847f..f2deba3cb 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/parent/ParentInfo.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/parent/ParentInfo.java @@ -85,6 +85,7 @@ public class ParentInfo extends SharedSubCommand { List page = new ArrayList<>(); for (Node node : nodes) { if (!node.isGroupNode()) continue; + if (!node.getValuePrimitive()) continue; if (node.isTemporary()) continue; page.add(node); } @@ -105,6 +106,7 @@ public class ParentInfo extends SharedSubCommand { List page = new ArrayList<>(); for (Node node : nodes) { if (!node.isGroupNode()) continue; + if (!node.getValuePrimitive()) continue; if (node.isPermanent()) continue; page.add(node); } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionInfo.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionInfo.java index b181076c4..779c906e7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionInfo.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionInfo.java @@ -121,14 +121,10 @@ public class PermissionInfo extends SharedSubCommand { context: if (index != -1) { String key = filter.substring(0, index); - if (key.equals("")) { - break context; - } + if (key.equals("")) break context; String value = filter.substring(index + 1); - if (value.equals("")) { - break context; - } + if (value.equals("")) break context; contextFilter = Maps.immutableEntry(key, value); } @@ -140,7 +136,9 @@ public class PermissionInfo extends SharedSubCommand { List l = new ArrayList<>(); for (Node node : nodes) { - if (node.isGroupNode() || node.isPrefix() || node.isSuffix() || node.isMeta()) continue; + if ((node.isGroupNode() && node.getValuePrimitive()) || node.isPrefix() || node.isSuffix() || node.isMeta()) continue; + + // check against filters if (nodeFilter != null && !node.getPermission().startsWith(nodeFilter)) continue; if (contextFilter != null && !node.getFullContexts().hasIgnoreCase(contextFilter.getKey(), contextFilter.getValue())) continue; if (temp != node.isTemporary()) continue; diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionSet.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionSet.java index 0fbab3854..a7570864c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionSet.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionSet.java @@ -62,7 +62,7 @@ public class PermissionSet extends SharedSubCommand { } String node = ArgumentUtils.handleString(0, args); - boolean b = ArgumentUtils.handleBoolean(1, args); + boolean value = ArgumentUtils.handleBoolean(1, args); MutableContextSet context = ArgumentUtils.handleContext(2, args, plugin); if (ArgumentPermissions.checkContext(plugin, sender, permission, context)) { @@ -75,13 +75,13 @@ public class PermissionSet extends SharedSubCommand { return CommandResult.NO_PERMISSION; } - DataMutateResult result = holder.setPermission(NodeFactory.newBuilder(node).setValue(b).withExtraContext(context).build()); + DataMutateResult result = holder.setPermission(NodeFactory.newBuilder(node).setValue(value).withExtraContext(context).build()); if (result.asBoolean()) { - Message.SETPERMISSION_SUCCESS.send(sender, node, b, holder.getFriendlyName(), CommandUtils.contextSetToString(context)); + Message.SETPERMISSION_SUCCESS.send(sender, node, value, holder.getFriendlyName(), CommandUtils.contextSetToString(context)); ExtendedLogEntry.build().actor(sender).acted(holder) - .action("permission", "set", node, b, context) + .action("permission", "set", node, value, context) .build().submit(plugin, sender); save(holder, sender, plugin); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionSetTemp.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionSetTemp.java index 764431ee2..5d31bb670 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionSetTemp.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionSetTemp.java @@ -67,7 +67,7 @@ public class PermissionSetTemp extends SharedSubCommand { } String node = ArgumentUtils.handleString(0, args); - boolean b = ArgumentUtils.handleBoolean(1, args); + boolean value = ArgumentUtils.handleBoolean(1, args); long duration = ArgumentUtils.handleDuration(2, args); MutableContextSet context = ArgumentUtils.handleContext(3, args, plugin); @@ -82,14 +82,14 @@ public class PermissionSetTemp extends SharedSubCommand { } TemporaryModifier modifier = plugin.getConfiguration().get(ConfigKeys.TEMPORARY_ADD_BEHAVIOUR); - Map.Entry result = holder.setPermission(NodeFactory.newBuilder(node).setValue(b).withExtraContext(context).setExpiry(duration).build(), modifier); + Map.Entry result = holder.setPermission(NodeFactory.newBuilder(node).setValue(value).withExtraContext(context).setExpiry(duration).build(), modifier); if (result.getKey().asBoolean()) { duration = result.getValue().getExpiryUnixTime(); - Message.SETPERMISSION_TEMP_SUCCESS.send(sender, node, b, holder.getFriendlyName(), DateUtil.formatDateDiff(duration), CommandUtils.contextSetToString(context)); + Message.SETPERMISSION_TEMP_SUCCESS.send(sender, node, value, holder.getFriendlyName(), DateUtil.formatDateDiff(duration), CommandUtils.contextSetToString(context)); ExtendedLogEntry.build().actor(sender).acted(holder) - .action("permission", "settemp", node, b, duration, context) + .action("permission", "settemp", node, value, duration, context) .build().submit(plugin, sender); save(holder, sender, plugin); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogGroupHistory.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogGroupHistory.java index 1bd7e11ce..b440dff2a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogGroupHistory.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogGroupHistory.java @@ -52,7 +52,7 @@ public class LogGroupHistory extends SubCommand { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Log log, List args, String label) throws CommandException { String group = args.get(0).toLowerCase(); - int page = -999; + int page = Integer.MIN_VALUE; if (args.size() == 2) { try { @@ -75,7 +75,7 @@ public class LogGroupHistory extends SubCommand { return CommandResult.STATE_ERROR; } - if (page == -999) { + if (page == Integer.MIN_VALUE) { page = maxPage; } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogSearch.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogSearch.java index db56defd5..0e78478f9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogSearch.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogSearch.java @@ -51,7 +51,7 @@ public class LogSearch extends SubCommand { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Log log, List args, String label) throws CommandException { - int page = -999; + int page = Integer.MIN_VALUE; if (args.size() > 1) { try { page = Integer.parseInt(args.get(args.size() - 1)); @@ -68,7 +68,7 @@ public class LogSearch extends SubCommand { return CommandResult.STATE_ERROR; } - if (page == -999) { + if (page == Integer.MIN_VALUE) { page = maxPage; } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogTrackHistory.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogTrackHistory.java index a3dda772b..90b4dbabd 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogTrackHistory.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogTrackHistory.java @@ -52,7 +52,7 @@ public class LogTrackHistory extends SubCommand { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Log log, List args, String label) throws CommandException { String track = args.get(0).toLowerCase(); - int page = -999; + int page = Integer.MIN_VALUE; if (args.size() == 2) { try { @@ -75,7 +75,7 @@ public class LogTrackHistory extends SubCommand { return CommandResult.STATE_ERROR; } - if (page == -999) { + if (page == Integer.MIN_VALUE) { page = maxPage; } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogUserHistory.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogUserHistory.java index c4c8a76f1..36bbe7c16 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogUserHistory.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/log/LogUserHistory.java @@ -55,7 +55,7 @@ public class LogUserHistory extends SubCommand { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Log log, List args, String label) throws CommandException { String target = args.get(0); - int page = -999; + int page = Integer.MIN_VALUE; if (args.size() == 2) { try { @@ -95,7 +95,7 @@ public class LogUserHistory extends SubCommand { } } - if (page == -999) { + if (page == Integer.MIN_VALUE) { page = log.getUserHistoryMaxPages(uuid); } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/misc/ApplyEditsCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/misc/ApplyEditsCommand.java index c55195be7..de83388a4 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/misc/ApplyEditsCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/misc/ApplyEditsCommand.java @@ -25,13 +25,10 @@ package me.lucko.luckperms.common.commands.impl.misc; -import com.google.common.base.Splitter; -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; +import com.google.common.collect.Maps; import com.google.gson.JsonObject; -import me.lucko.luckperms.api.context.ImmutableContextSet; +import me.lucko.luckperms.api.Node; import me.lucko.luckperms.common.actionlog.ExtendedLogEntry; import me.lucko.luckperms.common.commands.ArgumentPermissions; import me.lucko.luckperms.common.commands.CommandException; @@ -41,25 +38,20 @@ import me.lucko.luckperms.common.commands.abstraction.SingleCommand; import me.lucko.luckperms.common.commands.sender.Sender; import me.lucko.luckperms.common.commands.utils.CommandUtils; import me.lucko.luckperms.common.constants.CommandPermission; -import me.lucko.luckperms.common.contexts.ContextSetJsonSerializer; import me.lucko.luckperms.common.locale.CommandSpec; import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.node.NodeModel; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.utils.DateUtil; import me.lucko.luckperms.common.utils.Predicates; +import me.lucko.luckperms.common.webeditor.WebEditorUtils; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.HashSet; -import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.stream.Collectors; public class ApplyEditsCommand extends SingleCommand { @@ -72,27 +64,13 @@ public class ApplyEditsCommand extends SingleCommand { String code = args.get(0); String who = args.size() == 2 ? args.get(1) : null; - if (!code.contains("/")) { + if (code.isEmpty()) { Message.APPLY_EDITS_INVALID_CODE.send(sender, code); return CommandResult.INVALID_ARGS; } - Iterator codeParts = Splitter.on('/').limit(2).split(code).iterator(); - String part1 = codeParts.next(); - String part2 = codeParts.next(); - - if (part1.isEmpty() || part2.isEmpty()) { - Message.APPLY_EDITS_INVALID_CODE.send(sender, code); - return CommandResult.INVALID_ARGS; - } - - String url = "https://gist.githubusercontent.com/anonymous/" + part1 + "/raw/" + part2 + "/luckperms-data.json"; - JsonObject data; - - try { - data = read(url); - } catch (Exception e) { - e.printStackTrace(); + JsonObject data = WebEditorUtils.getDataFromGist(code); + if (data == null) { Message.APPLY_EDITS_UNABLE_TO_READ.send(sender, code); return CommandResult.FAILURE; } @@ -106,34 +84,9 @@ public class ApplyEditsCommand extends SingleCommand { who = data.get("who").getAsString(); } - PermissionHolder holder; - - if (who.startsWith("group/")) { - String group = who.substring("group/".length()); - holder = plugin.getGroupManager().getIfLoaded(group); - - if (holder == null) { - Message.APPLY_EDITS_TARGET_GROUP_NOT_EXISTS.send(sender, group); - return CommandResult.STATE_ERROR; - } - } else if (who.startsWith("user/")) { - String user = who.substring("user/".length()); - UUID uuid = CommandUtils.parseUuid(user); - if (uuid == null) { - Message.APPLY_EDITS_TARGET_USER_NOT_UUID.send(sender, user); - return CommandResult.STATE_ERROR; - } - holder = plugin.getUserManager().getIfLoaded(uuid); - if (holder == null) { - plugin.getStorage().loadUser(uuid, null).join(); - } - holder = plugin.getUserManager().getIfLoaded(uuid); - if (holder == null) { - Message.APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD.send(sender, uuid.toString()); - return CommandResult.STATE_ERROR; - } - } else { - Message.APPLY_EDITS_TARGET_UNKNOWN.send(sender, who); + PermissionHolder holder = WebEditorUtils.getHolderFromIdentifier(plugin, sender, who); + if (holder == null) { + // the #getHolderFromIdentifier method will send the error message onto the sender return CommandResult.STATE_ERROR; } @@ -143,66 +96,54 @@ public class ApplyEditsCommand extends SingleCommand { } ExtendedLogEntry.build().actor(sender).acted(holder) - .action("applyedits", part1 + "/" + part2) + .action("applyedits", code) .build().submit(plugin, sender); - Set nodes = deserializePermissions(data.getAsJsonArray("nodes")); - holder.setEnduringNodes(nodes.stream().map(NodeModel::toNode).collect(Collectors.toSet())); - Message.APPLY_EDITS_SUCCESS.send(sender, nodes.size(), holder.getFriendlyName()); + Set rawNodes = WebEditorUtils.deserializePermissions(data.getAsJsonArray("nodes")); + + Set before = new HashSet<>(holder.getEnduringNodes().values()); + Set nodes = rawNodes.stream().map(NodeModel::toNode).collect(Collectors.toSet()); + holder.setEnduringNodes(nodes); + + Map.Entry, Set> diff = diff(before, nodes); + int additions = diff.getKey().size(); + int deletions = diff.getValue().size(); + String additionsSummary = "addition" + (additions == 1 ? "" : "s"); + String deletionsSummary = "deletion" + (deletions == 1 ? "" : "s"); + + Message.APPLY_EDITS_SUCCESS.send(sender, holder.getFriendlyName()); + Message.APPLY_EDITS_SUCCESS_SUMMARY.send(sender, additions, additionsSummary, deletions, deletionsSummary); + for (Node n : diff.getKey()) { + Message.APPLY_EDITS_DIFF_ADDED.send(sender, formatNode(n)); + } + for (Node n : diff.getValue()) { + Message.APPLY_EDITS_DIFF_REMOVED.send(sender, formatNode(n)); + } + SharedSubCommand.save(holder, sender, plugin); return CommandResult.SUCCESS; } + private static String formatNode(Node n) { + return n.getPermission() + " &7(" + (n.getValuePrimitive() ? "&a" : "&c") + n.getValuePrimitive() + "&7)" + CommandUtils.getAppendableNodeContextString(n) + + (n.isTemporary() ? " &7(" + DateUtil.formatDateDiffShort(n.getExpiryUnixTime()) + ")" : ""); + } + + private static Map.Entry, Set> diff(Set before, Set after) { + // entries in before but not after are being removed + // entries in after but not before are being added + + Set added = new HashSet<>(after); + added.removeAll(before); + + Set removed = new HashSet<>(before); + removed.removeAll(after); + + return Maps.immutableEntry(added, removed); + } + @Override public boolean shouldDisplay() { return false; } - - private static JsonObject read(String address) throws IOException { - URL url = new URL(address); - try (InputStream in = url.openStream(); InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { - return new Gson().fromJson(reader, JsonObject.class); - } - } - - private static Set deserializePermissions(JsonArray permissionsSection) { - Set nodes = new HashSet<>(); - - for (JsonElement ent : permissionsSection) { - if (!ent.isJsonObject()) { - continue; - } - - JsonObject data = ent.getAsJsonObject(); - - String permission = data.get("permission").getAsString(); - boolean value = true; - String server = "global"; - String world = "global"; - long expiry = 0L; - ImmutableContextSet context = ImmutableContextSet.empty(); - - if (data.has("value")) { - value = data.get("value").getAsBoolean(); - } - if (data.has("server")) { - server = data.get("server").getAsString(); - } - if (data.has("world")) { - world = data.get("world").getAsString(); - } - if (data.has("expiry")) { - expiry = data.get("expiry").getAsLong(); - } - - if (data.has("context") && data.get("context").isJsonObject()) { - JsonObject contexts = data.get("context").getAsJsonObject(); - context = ContextSetJsonSerializer.deserializeContextSet(contexts).makeImmutable(); - } - - nodes.add(NodeModel.of(permission, value, server, world, expiry, context)); - } - - return nodes; - } } diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java index e5149f3be..d418f9bd1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java @@ -125,7 +125,10 @@ public enum Message { APPLY_EDITS_TARGET_USER_NOT_UUID("&cTarget user &4{}&c is not a valid uuid.", true), APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD("&cUnable to load target user &4{}&c.", true), APPLY_EDITS_TARGET_UNKNOWN("&cInvalid target. &7({})", true), - APPLY_EDITS_SUCCESS("&aSuccessfully applied &b{}&a nodes to &b{}&a.", true), + APPLY_EDITS_SUCCESS("&aWeb editor data was applied to &b{}&a successfully.", true), + APPLY_EDITS_SUCCESS_SUMMARY("&7(&a{} &7{} and &c{} &7{})", true), + APPLY_EDITS_DIFF_ADDED("&a+ &f{}", false), + APPLY_EDITS_DIFF_REMOVED("&c- &f{}", false), EDITOR_UPLOAD_FAILURE("&cUnable to upload permission data to the editor.", true), EDITOR_URL("&aEditor URL:", true), diff --git a/common/src/main/java/me/lucko/luckperms/common/utils/DateUtil.java b/common/src/main/java/me/lucko/luckperms/common/utils/DateUtil.java index c8ec12532..e579660e8 100644 --- a/common/src/main/java/me/lucko/luckperms/common/utils/DateUtil.java +++ b/common/src/main/java/me/lucko/luckperms/common/utils/DateUtil.java @@ -165,8 +165,13 @@ public class DateUtil { return DateUtil.formatDateDiff(now, then); } + public static String formatDateDiffShort(long seconds) { + long now = unixSecondsNow(); + return formatTimeShort(seconds - now); + } + public static String formatTimeShort(long seconds) { - if (seconds == 0) { + if (seconds <= 0) { return "0s"; } diff --git a/common/src/main/java/me/lucko/luckperms/common/utils/PasteUtils.java b/common/src/main/java/me/lucko/luckperms/common/utils/PasteUtils.java index 6d42a2b45..c0fc17f39 100644 --- a/common/src/main/java/me/lucko/luckperms/common/utils/PasteUtils.java +++ b/common/src/main/java/me/lucko/luckperms/common/utils/PasteUtils.java @@ -29,6 +29,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.stream.JsonWriter; +import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; @@ -57,8 +58,8 @@ public class PasteUtils { HttpURLConnection connection = null; try { connection = (HttpURLConnection) new URL(GIST_API).openConnection(); + connection.setRequestProperty("User-Agent", "luckperms"); connection.setRequestMethod("POST"); - connection.setDoInput(true); connection.setDoOutput(true); try (OutputStream os = connection.getOutputStream()) { @@ -85,9 +86,11 @@ public class PasteUtils { String pasteUrl; try (InputStream inputStream = connection.getInputStream()) { - try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { - JsonObject response = new Gson().fromJson(reader, JsonObject.class); - pasteUrl = response.get("html_url").getAsString(); + try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + try (BufferedReader reader = new BufferedReader(inputStreamReader)) { + JsonObject response = new Gson().fromJson(reader, JsonObject.class); + pasteUrl = response.get("html_url").getAsString(); + } } } @@ -95,7 +98,9 @@ public class PasteUtils { try { connection = (HttpURLConnection) new URL(SHORTEN_API).openConnection(); + connection.setRequestProperty("User-Agent", "luckperms"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + connection.setRequestMethod("GET"); connection.setDoOutput(true); try (OutputStream os = connection.getOutputStream()) { os.write(("url=" + pasteUrl).getBytes(StandardCharsets.UTF_8)); diff --git a/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorUtils.java b/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorUtils.java new file mode 100644 index 000000000..417dc211b --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorUtils.java @@ -0,0 +1,277 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.webeditor; + +import lombok.experimental.UtilityClass; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.stream.JsonWriter; + +import me.lucko.luckperms.api.context.ImmutableContextSet; +import me.lucko.luckperms.common.commands.sender.Sender; +import me.lucko.luckperms.common.commands.utils.CommandUtils; +import me.lucko.luckperms.common.contexts.ContextSetJsonSerializer; +import me.lucko.luckperms.common.locale.Message; +import me.lucko.luckperms.common.model.Group; +import me.lucko.luckperms.common.model.PermissionHolder; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.node.NodeModel; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; + +/** + * Utility methods for interacting with the LuckPerms web permission editor. + */ +@UtilityClass +public class WebEditorUtils { + + private static final String FILE_NAME = "luckperms-data.json"; + private static final String GIST_API_URL = "https://api.github.com/gists"; + + private static final String USER_ID_PATTERN = "user/"; + private static final String GROUP_ID_PATTERN = "group/"; + + public static String getHolderIdentifier(PermissionHolder holder) { + if (holder instanceof User) { + User user = ((User) holder); + return USER_ID_PATTERN + user.getUuid().toString(); + } else { + Group group = ((Group) holder); + return GROUP_ID_PATTERN + group.getName(); + } + } + + public static PermissionHolder getHolderFromIdentifier(LuckPermsPlugin plugin, Sender sender, String who) { + if (who.startsWith("group/")) { + String group = who.substring("group/".length()); + Group holder = plugin.getGroupManager().getIfLoaded(group); + if (holder == null) { + Message.APPLY_EDITS_TARGET_GROUP_NOT_EXISTS.send(sender, group); + } + return holder; + } else if (who.startsWith("user/")) { + String user = who.substring("user/".length()); + UUID uuid = CommandUtils.parseUuid(user); + if (uuid == null) { + Message.APPLY_EDITS_TARGET_USER_NOT_UUID.send(sender, user); + return null; + } + plugin.getStorage().loadUser(uuid, null).join(); + User holder = plugin.getUserManager().getIfLoaded(uuid); + if (holder == null) { + Message.APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD.send(sender, uuid.toString()); + } + return holder; + } else { + Message.APPLY_EDITS_TARGET_UNKNOWN.send(sender, who); + return null; + } + } + + public static String postToGist(String content) { + HttpURLConnection connection = null; + try { + connection = (HttpURLConnection) new URL(GIST_API_URL).openConnection(); + connection.addRequestProperty("User-Agent", "luckperms"); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + + try (OutputStream os = connection.getOutputStream()) { + StringWriter sw = new StringWriter(); + new JsonWriter(sw).beginObject() + .name("description").value("LuckPerms Web Permissions Editor Data") + .name("public").value(false) + .name("files") + .beginObject().name(FILE_NAME) + .beginObject().name("content").value(content) + .endObject() + .endObject() + .endObject(); + + os.write(sw.toString().getBytes(StandardCharsets.UTF_8)); + } + + if (connection.getResponseCode() >= 400) { + throw new RuntimeException("Connection returned response code: " + connection.getResponseCode() + " - " + connection.getResponseMessage()); + } + + try (InputStream inputStream = connection.getInputStream()) { + try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + try (BufferedReader reader = new BufferedReader(inputStreamReader)) { + JsonObject response = new Gson().fromJson(reader, JsonObject.class); + return response.get("id").getAsString(); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + if (connection != null) { + try { + connection.disconnect(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + public static JsonObject getDataFromGist(String id) { + HttpURLConnection connection = null; + try { + connection = (HttpURLConnection) new URL(GIST_API_URL + "/" + id).openConnection(); + connection.addRequestProperty("User-Agent", "luckperms"); + connection.setRequestMethod("GET"); + + if (connection.getResponseCode() >= 400) { + throw new RuntimeException("Connection returned response code: " + connection.getResponseCode() + " - " + connection.getResponseMessage()); + } + + try (InputStream inputStream = connection.getInputStream()) { + try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + try (BufferedReader reader = new BufferedReader(inputStreamReader)) { + JsonObject response = new Gson().fromJson(reader, JsonObject.class); + JsonObject files = response.get("files").getAsJsonObject(); + JsonObject permsFile = files.get(FILE_NAME).getAsJsonObject(); + + // uh.. + if (permsFile.get("truncated").getAsBoolean()) { + String rawUrlStr = permsFile.get("raw_url").getAsString(); + URL rawUrl = new URL(rawUrlStr); + try (InputStream rawInputStream = rawUrl.openStream()) { + try (InputStreamReader rawInputStreamReader = new InputStreamReader(rawInputStream, StandardCharsets.UTF_8)) { + try (BufferedReader rawReader = new BufferedReader(rawInputStreamReader)) { + return new Gson().fromJson(rawReader, JsonObject.class); + } + } + } + } else { + String content = permsFile.get("content").getAsString(); + return new Gson().fromJson(content, JsonObject.class); + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + if (connection != null) { + try { + connection.disconnect(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + public static JsonArray serializePermissions(Stream nodes) { + JsonArray arr = new JsonArray(); + nodes.forEach(node -> { + JsonObject attributes = new JsonObject(); + attributes.addProperty("permission", node.getPermission()); + attributes.addProperty("value", node.getValue()); + + if (!node.getServer().equals("global")) { + attributes.addProperty("server", node.getServer()); + } + + if (!node.getWorld().equals("global")) { + attributes.addProperty("world", node.getWorld()); + } + + if (node.getExpiry() != 0L) { + attributes.addProperty("expiry", node.getExpiry()); + } + + if (!node.getContexts().isEmpty()) { + attributes.add("context", ContextSetJsonSerializer.serializeContextSet(node.getContexts())); + } + + arr.add(attributes); + }); + return arr; + } + + public static Set deserializePermissions(JsonArray permissionsSection) { + Set nodes = new HashSet<>(); + + for (JsonElement ent : permissionsSection) { + if (!ent.isJsonObject()) { + continue; + } + + JsonObject data = ent.getAsJsonObject(); + + String permission = data.get("permission").getAsString(); + boolean value = true; + String server = "global"; + String world = "global"; + long expiry = 0L; + ImmutableContextSet context = ImmutableContextSet.empty(); + + if (data.has("value")) { + value = data.get("value").getAsBoolean(); + } + if (data.has("server")) { + server = data.get("server").getAsString(); + } + if (data.has("world")) { + world = data.get("world").getAsString(); + } + if (data.has("expiry")) { + expiry = data.get("expiry").getAsLong(); + } + + if (data.has("context") && data.get("context").isJsonObject()) { + JsonObject contexts = data.get("context").getAsJsonObject(); + context = ContextSetJsonSerializer.deserializeContextSet(contexts).makeImmutable(); + } + + nodes.add(NodeModel.of(permission, value, server, world, expiry, context)); + } + + return nodes; + } + +}