Implement initial web editor support

This commit is contained in:
Luck 2017-05-13 20:36:05 +01:00
parent 942fd3c4ce
commit 2e9561371d
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
8 changed files with 414 additions and 1 deletions

View File

@ -36,6 +36,7 @@ import me.lucko.luckperms.common.commands.impl.group.GroupMainCommand;
import me.lucko.luckperms.common.commands.impl.group.ListGroups;
import me.lucko.luckperms.common.commands.impl.log.LogMainCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationMainCommand;
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.ExportCommand;
@ -105,6 +106,7 @@ public class CommandManager {
.add(new ReloadConfigCommand())
.add(new BulkUpdateCommand())
.add(new MigrationMainCommand())
.add(new ApplyEditsCommand())
.add(new CreateGroup())
.add(new DeleteGroup())
.add(new ListGroups())

View File

@ -108,7 +108,7 @@ public abstract class SharedSubCommand {
return user ? userPermission.isAuthorized(sender) : groupPermission.isAuthorized(sender);
}
protected static void save(PermissionHolder holder, Sender sender, LuckPermsPlugin plugin) {
public static void save(PermissionHolder holder, Sender sender, LuckPermsPlugin plugin) {
if (holder instanceof User) {
User user = ((User) holder);
SubCommand.save(user, sender, plugin);

View File

@ -0,0 +1,191 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.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.stream.JsonWriter;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.constants.Message;
import me.lucko.luckperms.common.constants.Permission;
import me.lucko.luckperms.common.core.NodeModel;
import me.lucko.luckperms.common.core.PriorityComparator;
import me.lucko.luckperms.common.core.model.Group;
import me.lucko.luckperms.common.core.model.PermissionHolder;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import io.github.mkremins.fanciful.ChatColor;
import io.github.mkremins.fanciful.FancyMessage;
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.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
public HolderEditor(boolean user) {
super("editor", "Opens the web permission editor", user ? Permission.USER_EDITOR : Permission.GROUP_EDITOR,
Predicates.alwaysFalse(), null
);
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, T holder, List<String> args, String label) throws CommandException {
JsonObject data = new JsonObject();
Set<NodeModel> nodes = holder.getNodes().values().stream().map(NodeModel::fromNode).collect(Collectors.toCollection(LinkedHashSet::new));
data.add("nodes", serializePermissions(nodes));
data.addProperty("who", id(holder));
String dataUrl = paste(new GsonBuilder().setPrettyPrinting().create().toJson(data));
if (dataUrl == null) {
Message.EDITOR_UPLOAD_FAILURE.send(sender);
return CommandResult.STATE_ERROR;
}
List<String> parts = Splitter.on('/').splitToList(dataUrl);
String id = parts.get(4) + "/" + parts.get(6);
String url = "https://lpedit.lucko.me/?" + id;
Message.EDITOR_URL.send(sender);
sender.sendMessage(new FancyMessage(url).color(ChatColor.getByChar('b')).link(url));
return CommandResult.SUCCESS;
}
private static String id(PermissionHolder holder) {
if (holder instanceof User) {
User user = ((User) holder);
return "user/" + user.getUuid().toString();
} else {
Group group = ((Group) holder);
return "group/" + 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("luckperms-data.json")
.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("luckperms-data.json").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(Set<NodeModel> nodes) {
List<JsonObject> data = new ArrayList<>();
for (NodeModel node : nodes) {
JsonObject attributes = new JsonObject();
attributes.addProperty("permission", node.getPermission());
attributes.addProperty("value", node.isValue());
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", node.getContextsAsJson());
}
data.add(attributes);
}
data.sort((o1, o2) -> PriorityComparator.get().compareStrings(
o1.get("permission").getAsString(),
o2.get("permission").getAsString()
));
JsonArray arr = new JsonArray();
for (JsonObject o : data) {
arr.add(o);
}
return arr;
}
}

View File

@ -31,6 +31,7 @@ import me.lucko.luckperms.common.commands.abstraction.Command;
import me.lucko.luckperms.common.commands.abstraction.MainCommand;
import me.lucko.luckperms.common.commands.impl.generic.meta.CommandMeta;
import me.lucko.luckperms.common.commands.impl.generic.other.HolderClear;
import me.lucko.luckperms.common.commands.impl.generic.other.HolderEditor;
import me.lucko.luckperms.common.commands.impl.generic.other.HolderShowTracks;
import me.lucko.luckperms.common.commands.impl.generic.parent.CommandParent;
import me.lucko.luckperms.common.commands.impl.generic.permission.CommandPermission;
@ -49,6 +50,7 @@ public class GroupMainCommand extends MainCommand<Group> {
.add(new CommandPermission<>(false))
.add(new CommandParent<>(false))
.add(new CommandMeta<>(false))
.add(new HolderEditor<>(false))
.add(new GroupListMembers())
.add(new GroupSetWeight())
.add(new HolderShowTracks<>(false))

View File

@ -0,0 +1,201 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.common.base.Splitter;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.common.commands.Arg;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.abstraction.SharedSubCommand;
import me.lucko.luckperms.common.commands.abstraction.SingleCommand;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.Message;
import me.lucko.luckperms.common.constants.Permission;
import me.lucko.luckperms.common.core.NodeModel;
import me.lucko.luckperms.common.core.model.PermissionHolder;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
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.Set;
import java.util.UUID;
import java.util.stream.Collectors;
public class ApplyEditsCommand extends SingleCommand {
public ApplyEditsCommand() {
super("ApplyEdits", "Applies permission changes made from the web editor",
"/%s applyedits <code> [target]", Permission.APPLY_EDITS, Predicates.notInRange(1, 2),
Arg.list(
Arg.create("code", true, "the unique code for the data"),
Arg.create("target", false, "who to apply the data to")
)
);
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, List<String> args, String label) throws CommandException {
String code = args.get(0);
String who = args.size() == 2 ? args.get(1) : null;
if (!code.contains("/")) {
Message.APPLY_EDITS_INVALID_CODE.send(sender, code);
return CommandResult.INVALID_ARGS;
}
Iterator<String> 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();
Message.APPLY_EDITS_UNABLE_TO_READ.send(sender, code);
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;
}
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 = Util.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);
return CommandResult.STATE_ERROR;
}
Set<NodeModel> nodes = deserializePermissions(data.getAsJsonArray("nodes"));
holder.setNodes(nodes.stream().map(NodeModel::toNode).collect(Collectors.toSet()));
Message.APPLY_EDITS_SUCCESS.send(sender, nodes.size(), holder.getFriendlyName());
SharedSubCommand.save(holder, sender, plugin);
return CommandResult.SUCCESS;
}
@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<NodeModel> deserializePermissions(JsonArray permissionsSection) {
Set<NodeModel> 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 = NodeModel.deserializeContextSet(contexts).makeImmutable();
}
nodes.add(NodeModel.of(permission, value, server, world, expiry, context));
}
return nodes;
}
}

View File

@ -31,6 +31,7 @@ import me.lucko.luckperms.common.commands.abstraction.Command;
import me.lucko.luckperms.common.commands.abstraction.MainCommand;
import me.lucko.luckperms.common.commands.impl.generic.meta.CommandMeta;
import me.lucko.luckperms.common.commands.impl.generic.other.HolderClear;
import me.lucko.luckperms.common.commands.impl.generic.other.HolderEditor;
import me.lucko.luckperms.common.commands.impl.generic.other.HolderShowTracks;
import me.lucko.luckperms.common.commands.impl.generic.parent.CommandParent;
import me.lucko.luckperms.common.commands.impl.generic.permission.CommandPermission;
@ -51,6 +52,7 @@ public class UserMainCommand extends MainCommand<User> {
.add(new CommandPermission<>(true))
.add(new CommandParent<>(true))
.add(new CommandMeta<>(true))
.add(new HolderEditor<>(true))
.add(new UserSwitchPrimaryGroup())
.add(new UserPromote())
.add(new UserDemote())

View File

@ -120,6 +120,18 @@ public enum Message {
SEARCH_SHOWING_USERS_WITH_PAGE("&bShowing user entries: {0}", true),
SEARCH_SHOWING_GROUPS_WITH_PAGE("&bShowing group entries: {0}", true),
APPLY_EDITS_INVALID_CODE("&aInvalid code. &7({0})", true),
APPLY_EDITS_UNABLE_TO_READ("&aUnable to read data using the given code. &7({0})", true),
APPLY_EDITS_NO_TARGET("&aUnable to parse the target of the edit. Please supply it as an extra argument.", true),
APPLY_EDITS_TARGET_GROUP_NOT_EXISTS("&aTarget group &b{0}&a does not exist.", true),
APPLY_EDITS_TARGET_USER_NOT_UUID("&aTarget user &b{0}&a is not a valid uuid.", true),
APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD("&aUnable to load target user &b{0}&a.", true),
APPLY_EDITS_TARGET_UNKNOWN("&aInvalid target. &7({0})", true),
APPLY_EDITS_SUCCESS("&aSuccessfully applied &b{0}&a nodes to &b{1}&a.", true),
EDITOR_UPLOAD_FAILURE("&cUnable to upload permission data to the editor.", true),
EDITOR_URL("&aEditor URL:", true),
CHECK_RESULT("&aPermission check result on user &b{0}&a for permission &b{1}&a: &f{2}", true),
CREATE_SUCCESS("&b{0}&a was successfully created.", true),

View File

@ -48,6 +48,7 @@ public enum Permission {
EXPORT(list("export"), Type.NONE),
RELOAD_CONFIG(list("reloadconfig"), Type.NONE),
BULK_UPDATE(list("bulkupdate"), Type.NONE),
APPLY_EDITS(list("applyedits"), Type.NONE),
MIGRATION(list("migration"), Type.NONE),
CREATE_GROUP(list("creategroup"), Type.NONE),
@ -89,6 +90,7 @@ public enum Permission {
USER_META_REMOVETEMP_PREFIX(list("meta.removetempprefix", "removetempprefix"), Type.USER),
USER_META_REMOVETEMP_SUFFIX(list("meta.removetempsuffix", "removetempsuffix"), Type.USER),
USER_META_CLEAR(list("meta.clear", "clearmeta"), Type.USER),
USER_EDITOR(list("editor"), Type.USER),
USER_SWITCHPRIMARYGROUP(list("switchprimarygroup", "setprimarygroup"), Type.USER),
USER_SHOWTRACKS(list("showtracks"), Type.USER),
USER_PROMOTE(list("promote"), Type.USER),
@ -126,6 +128,7 @@ public enum Permission {
GROUP_META_REMOVETEMP_PREFIX(list("meta.removetempprefix", "removetempprefix"), Type.GROUP),
GROUP_META_REMOVETEMP_SUFFIX(list("meta.removetempsuffix", "removetempsuffix"), Type.GROUP),
GROUP_META_CLEAR(list("meta.clear", "clearmeta"), Type.GROUP),
GROUP_EDITOR(list("editor"), Type.GROUP),
GROUP_LISTMEMBERS(list("listmembers"), Type.GROUP),
GROUP_SHOWTRACKS(list("showtracks"), Type.GROUP),
GROUP_SETWEIGHT(list("setweight"), Type.GROUP),