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 fa41b8e7b..c4fb40898 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 @@ -38,6 +38,7 @@ import me.lucko.luckperms.common.commands.impl.misc.ApplyEditsCommand; import me.lucko.luckperms.common.commands.impl.misc.BulkUpdateCommand; import me.lucko.luckperms.common.commands.impl.misc.CheckCommand; import me.lucko.luckperms.common.commands.impl.misc.DebugCommand; +import me.lucko.luckperms.common.commands.impl.misc.EditorCommand; import me.lucko.luckperms.common.commands.impl.misc.ExportCommand; import me.lucko.luckperms.common.commands.impl.misc.ImportCommand; import me.lucko.luckperms.common.commands.impl.misc.InfoCommand; @@ -109,6 +110,7 @@ public class CommandManager { .add(new LogMainCommand(locale)) .add(new SyncCommand(locale)) .add(new InfoCommand(locale)) + .add(new EditorCommand(locale)) .add(new DebugCommand(locale)) .add(new VerboseCommand(locale)) .add(new TreeCommand(locale)) diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/CommandPermission.java b/common/src/main/java/me/lucko/luckperms/common/commands/CommandPermission.java index 92f3aeaf9..904311be1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/CommandPermission.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/CommandPermission.java @@ -41,6 +41,7 @@ public enum CommandPermission { SYNC("sync", NONE), INFO("info", NONE), + EDITOR("editor", NONE), DEBUG("debug", NONE), VERBOSE("verbose", NONE), TREE("tree", NONE), 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 d714f4db2..75df6789a 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 @@ -26,9 +26,7 @@ package me.lucko.luckperms.common.commands.impl.generic.other; import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; import me.lucko.luckperms.common.commands.ArgumentPermissions; import me.lucko.luckperms.common.commands.CommandPermission; @@ -40,8 +38,6 @@ 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.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; @@ -52,6 +48,7 @@ import net.kyori.text.event.ClickEvent; import net.kyori.text.event.HoverEvent; import net.kyori.text.format.TextColor; +import java.util.Collections; import java.util.List; public class HolderEditor extends SubCommand { @@ -69,26 +66,7 @@ public class HolderEditor extends SubCommand { Message.EDITOR_START.send(sender); // form the payload data - JsonObject payload = new JsonObject(); - payload.addProperty("who", WebEditorUtils.getHolderIdentifier(holder)); - payload.addProperty("whoFriendly", holder.getFriendlyName()); - if (holder.getType().isUser()) { - payload.addProperty("whoUuid", ((User) holder).getUuid().toString()); - } - payload.addProperty("cmdAlias", label); - payload.addProperty("uploadedBy", sender.getNameWithLocation()); - payload.addProperty("uploadedByUuid", sender.getUuid().toString()); - payload.addProperty("time", System.currentTimeMillis()); - - // attach the holders permissions - 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(); - for (String perm : plugin.getPermissionVault().rootAsList()) { - knownPermsArray.add(new JsonPrimitive(perm)); - } - payload.add("knownPermissions", knownPermsArray); + JsonObject payload = WebEditorUtils.formPayload(Collections.singletonList(holder), sender, label, plugin); // upload the payload data to gist String gistId = WebEditorUtils.postToGist(new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(payload)); 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 6f2eaf448..6d036e182 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 @@ -26,6 +26,8 @@ package me.lucko.luckperms.common.commands.impl.misc; import com.google.common.collect.Maps; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import me.lucko.luckperms.api.Node; @@ -61,7 +63,6 @@ public class ApplyEditsCommand extends SingleCommand { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, List args, String label) { String code = args.get(0); - String who = args.size() == 2 ? args.get(1) : null; if (code.isEmpty()) { Message.APPLY_EDITS_INVALID_CODE.send(sender, code); @@ -74,39 +75,55 @@ public class ApplyEditsCommand extends SingleCommand { return CommandResult.FAILURE; } - if (who == null) { - if (!data.has("who") || data.get("who").getAsString().isEmpty()) { - Message.APPLY_EDITS_NO_TARGET.send(sender); - return CommandResult.STATE_ERROR; + if (data.has("tabs") && data.get("tabs").isJsonArray()) { + JsonArray rows = data.get("tabs").getAsJsonArray(); + for (JsonElement row : rows) { + read(row.getAsJsonObject(), code, sender, plugin); } - - who = data.get("who").getAsString(); + } else { + read(data, code, sender, plugin); } + return CommandResult.SUCCESS; + } + + private boolean read(JsonObject data, String code, Sender sender, LuckPermsPlugin plugin) { + if (!data.has("who") || data.get("who").getAsString().isEmpty()) { + Message.APPLY_EDITS_NO_TARGET.send(sender); + return false; + } + + String who = data.get("who").getAsString(); 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; + return false; } if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), holder)) { Message.COMMAND_NO_PERMISSION.send(sender); - return CommandResult.NO_PERMISSION; + return false; + } + + Set nodes = WebEditorUtils.deserializePermissions(data.getAsJsonArray("nodes")); + + Set before = new HashSet<>(holder.getEnduringNodes().values()); + Set after = nodes.stream().map(NodeModel::toNode).collect(Collectors.toSet()); + + Map.Entry, Set> diff = diff(before, after); + int additions = diff.getKey().size(); + int deletions = diff.getValue().size(); + + if (additions == 0 && deletions == 0) { + return false; } ExtendedLogEntry.build().actor(sender).acted(holder) .action("applyedits", code) .build().submit(plugin, sender); - Set rawNodes = WebEditorUtils.deserializePermissions(data.getAsJsonArray("nodes")); + holder.setEnduringNodes(after); - 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"); @@ -118,9 +135,8 @@ public class ApplyEditsCommand extends SingleCommand { for (Node n : diff.getValue()) { Message.APPLY_EDITS_DIFF_REMOVED.send(sender, formatNode(n)); } - SharedSubCommand.save(holder, sender, plugin); - return CommandResult.SUCCESS; + return true; } private static String formatNode(Node n) { diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/misc/EditorCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/misc/EditorCommand.java new file mode 100644 index 000000000..edf184f8b --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/misc/EditorCommand.java @@ -0,0 +1,131 @@ +/* + * 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.commands.impl.misc; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; + +import me.lucko.luckperms.common.commands.ArgumentPermissions; +import me.lucko.luckperms.common.commands.CommandPermission; +import me.lucko.luckperms.common.commands.CommandResult; +import me.lucko.luckperms.common.commands.abstraction.SingleCommand; +import me.lucko.luckperms.common.commands.sender.Sender; +import me.lucko.luckperms.common.commands.utils.ArgumentUtils; +import me.lucko.luckperms.common.config.ConfigKeys; +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.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; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; + +import java.util.ArrayList; +import java.util.List; + +public class EditorCommand extends SingleCommand { + public EditorCommand(LocaleManager locale) { + super(CommandSpec.EDITOR.spec(locale), "Editor", CommandPermission.EDITOR, Predicates.notInRange(0, 1)); + } + + @Override + public CommandResult execute(LuckPermsPlugin plugin, Sender sender, List args, String label) { + Type type = Type.ALL; + + // parse type + String typeString = ArgumentUtils.handleStringOrElse(0, args, null); + if (typeString != null) { + try { + type = Type.valueOf(typeString.toUpperCase()); + } catch (IllegalArgumentException e) { + // ignored + } + } + + // collect holders + List holders = new ArrayList<>(); + if (type.includingGroups) { + holders.addAll(plugin.getGroupManager().getAll().values()); + } + if (type.includingUsers) { + holders.addAll(plugin.getUserManager().getAll().values()); + } + + // remove holders which the sender doesn't have perms to view + holders.removeIf(holder -> ArgumentPermissions.checkViewPerms(plugin, sender, getPermission().get(), holder)); + + // they don't have perms to view any of them + if (holders.isEmpty()) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + + Message.EDITOR_START.send(sender); + + // form the payload data + JsonObject payload = WebEditorUtils.formPayload(holders, sender, label, plugin); + + // upload the payload data to gist + String gistId = WebEditorUtils.postToGist(new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(payload)); + if (gistId == null) { + Message.EDITOR_UPLOAD_FAILURE.send(sender); + return CommandResult.STATE_ERROR; + } + + // form a url for the editor + String url = plugin.getConfiguration().get(ConfigKeys.WEB_EDITOR_URL_PATTERN) + "?" + gistId; + + Message.EDITOR_URL.send(sender); + + Component message = TextComponent.builder(url).color(TextColor.AQUA) + .clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)) + .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to open the editor.").color(TextColor.GRAY))) + .build(); + + sender.sendMessage(message); + return CommandResult.SUCCESS; + } + + private enum Type { + ALL(true, true), + USERS(true, false), + GROUPS(false, true); + + private final boolean includingUsers; + private final boolean includingGroups; + + Type(boolean includingUsers, boolean includingGroups) { + this.includingUsers = includingUsers; + this.includingGroups = includingGroups; + } + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/CommandSpec.java b/common/src/main/java/me/lucko/luckperms/common/locale/CommandSpec.java index eeaa97645..ba2bd4610 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/CommandSpec.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/CommandSpec.java @@ -49,6 +49,11 @@ public enum CommandSpec { SYNC("Sync changes with the storage", "/%s sync"), INFO("Print general plugin info", "/%s info"), + EDITOR("Creates a new editor session", "/%s editor [type]", + Arg.list( + Arg.create("type", false, "the types to load into the editor. ('all', 'users' or 'groups')") + ) + ), DEBUG("Produce debugging output", "/%s debug"), VERBOSE("Manage verbose permission checking", "/%s verbose [filter]", Arg.list( 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 1dabb5e50..99ebf8484 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 @@ -124,7 +124,7 @@ public enum Message { APPLY_EDITS_INVALID_CODE("&cInvalid code. &7({})", true), APPLY_EDITS_UNABLE_TO_READ("&cUnable to read data using the given code. &7({})", true), - APPLY_EDITS_NO_TARGET("&cUnable to parse the target of the edit. Please supply it as an extra argument.", true), + APPLY_EDITS_NO_TARGET("&cUnable to parse the target of the edit.", true), APPLY_EDITS_TARGET_GROUP_NOT_EXISTS("&cTarget group &4{}&c does not exist.", true), 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), 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 index 1c134479c..8d6fa2d35 100644 --- a/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorUtils.java +++ b/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorUtils.java @@ -25,10 +25,12 @@ package me.lucko.luckperms.common.webeditor; +import com.google.common.base.Preconditions; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import me.lucko.luckperms.api.context.ImmutableContextSet; import me.lucko.luckperms.common.commands.sender.Sender; @@ -53,6 +55,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.UUID; import java.util.stream.Stream; @@ -69,7 +72,51 @@ public final class WebEditorUtils { private static final String USER_ID_PATTERN = "user/"; private static final String GROUP_ID_PATTERN = "group/"; - public static String getHolderIdentifier(PermissionHolder holder) { + private static void writeData(PermissionHolder holder, JsonObject payload) { + payload.addProperty("who", getHolderIdentifier(holder)); + payload.addProperty("whoFriendly", holder.getFriendlyName()); + if (holder.getType().isUser()) { + payload.addProperty("whoUuid", ((User) holder).getUuid().toString()); + } + + // attach the holders permissions + payload.add("nodes", WebEditorUtils.serializePermissions(holder.getEnduringNodes().values().stream().map(NodeModel::fromNode))); + } + + public static JsonObject formPayload(List holders, Sender sender, String cmdLabel, LuckPermsPlugin plugin) { + Preconditions.checkArgument(!holders.isEmpty(), "holders is empty"); + + // form the payload data + JsonObject payload = new JsonObject(); + + payload.addProperty("cmdAlias", cmdLabel); + payload.addProperty("uploadedBy", sender.getNameWithLocation()); + payload.addProperty("uploadedByUuid", sender.getUuid().toString()); + payload.addProperty("time", System.currentTimeMillis()); + + if (holders.size() == 1) { + writeData(holders.get(0), payload); + } else { + JsonArray tabs = new JsonArray(); + for (PermissionHolder holder : holders) { + JsonObject o = new JsonObject(); + writeData(holder, o); + tabs.add(o); + } + payload.add("tabs", tabs); + } + + // attach an array of all permissions known to the server, to use for tab completion in the editor + JsonArray knownPermsArray = new JsonArray(); + for (String perm : plugin.getPermissionVault().rootAsList()) { + knownPermsArray.add(new JsonPrimitive(perm)); + } + payload.add("knownPermissions", knownPermsArray); + + return payload; + } + + private static String getHolderIdentifier(PermissionHolder holder) { if (holder.getType().isUser()) { User user = ((User) holder); return USER_ID_PATTERN + user.getUuid().toString();