Refactor web editor session handling

This commit is contained in:
Luck 2020-09-07 18:06:40 +01:00
parent 198b86d7c3
commit 020aff66aa
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
17 changed files with 505 additions and 395 deletions

View File

@ -27,6 +27,8 @@ package me.lucko.luckperms.common.backup;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.http.AbstractHttpClient;
import me.lucko.luckperms.common.http.UnsuccessfulRequestException;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
@ -40,8 +42,6 @@ import me.lucko.luckperms.common.util.ProgressLogger;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import me.lucko.luckperms.common.util.gson.JArray;
import me.lucko.luckperms.common.util.gson.JObject;
import me.lucko.luckperms.common.web.AbstractHttpClient;
import me.lucko.luckperms.common.web.UnsuccessfulRequestException;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;

View File

@ -25,8 +25,6 @@
package me.lucko.luckperms.common.commands.generic.other;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.ArgumentPermissions;
@ -49,7 +47,7 @@ import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.storage.misc.NodeEntry;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.verbose.event.MetaCheckEvent;
import me.lucko.luckperms.common.web.WebEditor;
import me.lucko.luckperms.common.webeditor.WebEditorRequest;
import net.luckperms.api.node.Node;
import net.luckperms.api.query.QueryOptions;
@ -119,8 +117,8 @@ public class HolderEditor<T extends PermissionHolder> extends ChildCommand<T> {
Message.EDITOR_START.send(sender);
JsonObject payload = WebEditor.formPayload(holders, Collections.emptyList(), sender, label, plugin);
return WebEditor.post(payload, sender, plugin);
return WebEditorRequest.generate(holders, Collections.emptyList(), sender, label, plugin)
.createSession(plugin, sender);
}
}

View File

@ -25,49 +25,22 @@
package me.lucko.luckperms.common.commands.misc;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.SingleCommand;
import me.lucko.luckperms.common.command.access.ArgumentPermissions;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.command.utils.MessageUtils;
import me.lucko.luckperms.common.command.utils.StorageAssistant;
import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl;
import me.lucko.luckperms.common.http.UnsuccessfulRequestException;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.manager.group.GroupManager;
import me.lucko.luckperms.common.node.utils.NodeJsonSerializer;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationFormatter;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.Uuids;
import me.lucko.luckperms.common.web.UnsuccessfulRequestException;
import me.lucko.luckperms.common.web.WebEditor;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.event.cause.CreationCause;
import net.luckperms.api.event.cause.DeletionCause;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.node.Node;
import me.lucko.luckperms.common.webeditor.WebEditorResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public class ApplyEditsCommand extends SingleCommand {
public ApplyEditsCommand(LocaleManager locale) {
@ -85,12 +58,12 @@ public class ApplyEditsCommand extends SingleCommand {
JsonObject data;
try {
data = WebEditor.readDataFromBytebin(plugin.getBytebin(), code);
data = plugin.getBytebin().getJsonContent(code).getAsJsonObject();
} catch (UnsuccessfulRequestException e) {
Message.EDITOR_HTTP_REQUEST_FAILURE.send(sender, e.getResponse().code(), e.getResponse().message());
return CommandResult.STATE_ERROR;
} catch (IOException e) {
new RuntimeException("Error uploading data to bytebin", e).printStackTrace();
new RuntimeException("Error reading data from bytebin", e).printStackTrace();
Message.EDITOR_HTTP_UNKNOWN_FAILURE.send(sender);
return CommandResult.STATE_ERROR;
}
@ -100,271 +73,10 @@ public class ApplyEditsCommand extends SingleCommand {
return CommandResult.FAILURE;
}
boolean work = false;
if (data.has("changes")) {
JsonArray changes = data.get("changes").getAsJsonArray();
for (JsonElement change : changes) {
if (readChanges(change.getAsJsonObject(), sender, plugin)) {
work = true;
}
}
}
if (data.has("groupDeletions")) {
JsonArray groupDeletions = data.get("groupDeletions").getAsJsonArray();
for (JsonElement groupDeletion : groupDeletions) {
if (readGroupDeletion(groupDeletion, sender, plugin)) {
work = true;
}
}
}
if (data.has("trackDeletions")) {
JsonArray trackDeletions = data.get("trackDeletions").getAsJsonArray();
for (JsonElement trackDeletion : trackDeletions) {
if (readTrackDeletion(trackDeletion, sender, plugin)) {
work = true;
}
}
}
if (!work) {
Message.APPLY_EDITS_TARGET_NO_CHANGES_PRESENT.send(sender);
}
new WebEditorResponse(data).apply(plugin, sender);
return CommandResult.SUCCESS;
}
private boolean readChanges(JsonObject data, Sender sender, LuckPermsPlugin plugin) {
String type = data.get("type").getAsString();
if (type.equals("user") || type.equals("group")) {
return readHolderChanges(data, sender, plugin);
} else if (type.equals("track")) {
return readTrackChanges(data, sender, plugin);
} else {
Message.APPLY_EDITS_UNKNOWN_TYPE.send(sender, type);
return false;
}
}
private boolean readHolderChanges(JsonObject data, Sender sender, LuckPermsPlugin plugin) {
String type = data.get("type").getAsString();
String id = data.get("id").getAsString();
PermissionHolder holder;
if (type.equals("user")) {
// user
UUID uuid = Uuids.parse(id);
if (uuid == null) {
Message.APPLY_EDITS_TARGET_USER_NOT_UUID.send(sender, id);
return false;
}
holder = plugin.getStorage().loadUser(uuid, null).join();
if (holder == null) {
Message.APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD.send(sender, uuid.toString());
return false;
}
} else {
// group
holder = plugin.getStorage().loadGroup(id).join().orElse(null);
if (holder == null) {
holder = plugin.getStorage().createAndLoadGroup(id, CreationCause.WEB_EDITOR).join();
}
}
if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), holder) || ArgumentPermissions.checkGroup(plugin, sender, holder, ImmutableContextSetImpl.EMPTY)) {
Message.COMMAND_NO_PERMISSION.send(sender);
return false;
}
Set<Node> before = holder.normalData().asSet();
Set<Node> after = new HashSet<>(NodeJsonSerializer.deserializeNodes(data.getAsJsonArray("nodes")));
Set<Node> diffAdded = getAdded(before, after);
Set<Node> diffRemoved = getRemoved(before, after);
int additions = diffAdded.size();
int deletions = diffRemoved.size();
if (additions == 0 && deletions == 0) {
return false;
}
holder.setNodes(DataType.NORMAL, after);
for (Node n : diffAdded) {
LoggedAction.build().source(sender).target(holder)
.description("webeditor", "add", n.getKey(), n.getValue(), n.getContexts())
.build().submit(plugin, sender);
}
for (Node n : diffRemoved) {
LoggedAction.build().source(sender).target(holder)
.description("webeditor", "remove", n.getKey(), n.getValue(), n.getContexts())
.build().submit(plugin, sender);
}
String additionsSummary = "addition" + (additions == 1 ? "" : "s");
String deletionsSummary = "deletion" + (deletions == 1 ? "" : "s");
Message.APPLY_EDITS_SUCCESS.send(sender, type, holder.getFormattedDisplayName());
Message.APPLY_EDITS_SUCCESS_SUMMARY.send(sender, additions, additionsSummary, deletions, deletionsSummary);
for (Node n : diffAdded) {
Message.APPLY_EDITS_DIFF_ADDED.send(sender, formatNode(plugin.getLocaleManager(), n));
}
for (Node n : diffRemoved) {
Message.APPLY_EDITS_DIFF_REMOVED.send(sender, formatNode(plugin.getLocaleManager(), n));
}
StorageAssistant.save(holder, sender, plugin);
return true;
}
private boolean readTrackChanges(JsonObject data, Sender sender, LuckPermsPlugin plugin) {
String id = data.get("id").getAsString();
Track track = plugin.getStorage().loadTrack(id).join().orElse(null);
if (track == null) {
track = plugin.getStorage().createAndLoadTrack(id, CreationCause.WEB_EDITOR).join();
}
if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), track)) {
Message.COMMAND_NO_PERMISSION.send(sender);
return false;
}
List<String> before = track.getGroups();
List<String> after = new ArrayList<>();
data.getAsJsonArray("groups").forEach(e -> after.add(e.getAsString()));
if (before.equals(after)) {
return false;
}
Set<String> diffAdded = getAdded(before, after);
Set<String> diffRemoved = getRemoved(before, after);
int additions = diffAdded.size();
int deletions = diffRemoved.size();
track.setGroups(after);
if (hasBeenReordered(before, after, diffAdded, diffRemoved)) {
LoggedAction.build().source(sender).target(track)
.description("webeditor", "reorder", after)
.build().submit(plugin, sender);
}
for (String n : diffAdded) {
LoggedAction.build().source(sender).target(track)
.description("webeditor", "add", n)
.build().submit(plugin, sender);
}
for (String n : diffRemoved) {
LoggedAction.build().source(sender).target(track)
.description("webeditor", "remove", n)
.build().submit(plugin, sender);
}
String additionsSummary = "addition" + (additions == 1 ? "" : "s");
String deletionsSummary = "deletion" + (deletions == 1 ? "" : "s");
Message.APPLY_EDITS_SUCCESS.send(sender, "track", track.getName());
Message.APPLY_EDITS_SUCCESS_SUMMARY.send(sender, additions, additionsSummary, deletions, deletionsSummary);
Message.APPLY_EDITS_DIFF_REMOVED.send(sender, before);
Message.APPLY_EDITS_DIFF_ADDED.send(sender, after);
StorageAssistant.save(track, sender, plugin);
return true;
}
private boolean readGroupDeletion(JsonElement data, Sender sender, LuckPermsPlugin plugin) {
String groupName = data.getAsString();
if (groupName.equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME)) {
Message.DELETE_GROUP_ERROR_DEFAULT.send(sender);
return true;
}
Group group = plugin.getStorage().loadGroup(groupName).join().orElse(null);
if (group == null) {
return false;
}
if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), group) || ArgumentPermissions.checkGroup(plugin, sender, group, ImmutableContextSetImpl.EMPTY)) {
Message.COMMAND_NO_PERMISSION.send(sender);
return false;
}
try {
plugin.getStorage().deleteGroup(group, DeletionCause.COMMAND).get();
} catch (Exception e) {
e.printStackTrace();
Message.DELETE_ERROR.send(sender, group.getFormattedDisplayName());
return true;
}
Message.DELETE_SUCCESS.send(sender, group.getFormattedDisplayName());
LoggedAction.build().source(sender).targetName(groupName).targetType(Action.Target.Type.GROUP)
.description("webeditor", "delete")
.build().submit(plugin, sender);
return true;
}
private boolean readTrackDeletion(JsonElement data, Sender sender, LuckPermsPlugin plugin) {
String trackName = data.getAsString();
Track track = plugin.getStorage().loadTrack(trackName).join().orElse(null);
if (track == null) {
return false;
}
if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), track)) {
Message.COMMAND_NO_PERMISSION.send(sender);
return false;
}
try {
plugin.getStorage().deleteTrack(track, DeletionCause.COMMAND).get();
} catch (Exception e) {
e.printStackTrace();
Message.DELETE_ERROR.send(sender, track.getName());
return true;
}
Message.DELETE_SUCCESS.send(sender, trackName);
LoggedAction.build().source(sender).targetName(trackName).targetType(Action.Target.Type.TRACK)
.description("webeditor", "delete")
.build().submit(plugin, sender);
return true;
}
private static String formatNode(LocaleManager localeManager, Node n) {
return n.getKey() + " &7(" + (n.getValue() ? "&a" : "&c") + n.getValue() + "&7)" + MessageUtils.getAppendableNodeContextString(localeManager, n) +
(n.hasExpiry() ? " &7(" + DurationFormatter.CONCISE.format(n.getExpiryDuration()) + ")" : "");
}
private static <T> Set<T> getAdded(Collection<T> before, Collection<T> after) {
Set<T> added = new LinkedHashSet<>(after);
added.removeAll(before);
return added;
}
private static <T> Set<T> getRemoved(Collection<T> before, Collection<T> after) {
Set<T> removed = new LinkedHashSet<>(before);
removed.removeAll(after);
return removed;
}
private static <T> boolean hasBeenReordered(List<T> before, List<T> after, Collection<T> diffAdded, Collection<T> diffRemoved) {
after = new ArrayList<>(after);
before = new ArrayList<>(before);
after.removeAll(diffAdded);
before.removeAll(diffRemoved);
return !before.equals(after);
}
@Override
public boolean shouldDisplay() {
return false;

View File

@ -25,8 +25,6 @@
package me.lucko.luckperms.common.commands.misc;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.SingleCommand;
import me.lucko.luckperms.common.command.access.ArgumentPermissions;
@ -47,7 +45,7 @@ import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.storage.misc.NodeEntry;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.verbose.event.MetaCheckEvent;
import me.lucko.luckperms.common.web.WebEditor;
import me.lucko.luckperms.common.webeditor.WebEditorRequest;
import net.luckperms.api.node.Node;
import net.luckperms.api.query.QueryOptions;
@ -171,8 +169,8 @@ public class EditorCommand extends SingleCommand {
Message.EDITOR_START.send(sender);
JsonObject payload = WebEditor.formPayload(holders, tracks, sender, label, plugin);
return WebEditor.post(payload, sender, plugin);
return WebEditorRequest.generate(holders, tracks, sender, label, plugin)
.createSession(plugin, sender);
}
private enum Type {

View File

@ -32,6 +32,7 @@ import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.SingleCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.http.UnsuccessfulRequestException;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
import me.lucko.luckperms.common.locale.message.Message;
@ -39,8 +40,6 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import me.lucko.luckperms.common.web.UnsuccessfulRequestException;
import me.lucko.luckperms.common.web.WebEditor;
import java.io.BufferedReader;
import java.io.IOException;
@ -118,7 +117,7 @@ public class ImportCommand extends SingleCommand {
}
try {
data = WebEditor.readDataFromBytebin(plugin.getBytebin(), code);
data = plugin.getBytebin().getJsonContent(code).getAsJsonObject();
} catch (UnsuccessfulRequestException e) {
Message.IMPORT_HTTP_REQUEST_FAILURE.send(sender, e.getResponse().code(), e.getResponse().message());
return CommandResult.STATE_ERROR;

View File

@ -31,6 +31,7 @@ import me.lucko.luckperms.common.command.abstraction.SingleCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.http.UnsuccessfulRequestException;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
import me.lucko.luckperms.common.locale.message.Message;
@ -40,7 +41,6 @@ import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.treeview.TreeView;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.Uuids;
import me.lucko.luckperms.common.web.UnsuccessfulRequestException;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;

View File

@ -32,6 +32,7 @@ import me.lucko.luckperms.common.command.tabcomplete.CompletionSupplier;
import me.lucko.luckperms.common.command.tabcomplete.TabCompleter;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.http.UnsuccessfulRequestException;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
import me.lucko.luckperms.common.locale.message.Message;
@ -42,7 +43,6 @@ import me.lucko.luckperms.common.verbose.InvalidFilterException;
import me.lucko.luckperms.common.verbose.VerboseFilter;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import me.lucko.luckperms.common.verbose.VerboseListener;
import me.lucko.luckperms.common.web.UnsuccessfulRequestException;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;

View File

@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.web;
package me.lucko.luckperms.common.http;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;

View File

@ -23,15 +23,24 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.web;
package me.lucko.luckperms.common.http;
import com.google.gson.JsonElement;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class BytebinClient extends AbstractHttpClient {
@ -134,6 +143,34 @@ public class BytebinClient extends AbstractHttpClient {
makeHttpRequest(request).close();
}
/**
* GETs json content from bytebin
*
* @param id the id of the content
* @return the data
* @throws IOException if an error occurs
*/
public JsonElement getJsonContent(String id) throws IOException, UnsuccessfulRequestException {
Request request = new Request.Builder()
.header("User-Agent", this.userAgent)
.url(this.url + id)
.build();
try (Response response = makeHttpRequest(request)) {
try (ResponseBody responseBody = response.body()) {
if (responseBody == null) {
throw new RuntimeException("No response");
}
try (InputStream inputStream = responseBody.byteStream()) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
return GsonProvider.normal().fromJson(reader, JsonElement.class);
}
}
}
}
}
public static final class Content {
private final String key;
private final boolean modifiable;

View File

@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.web;
package me.lucko.luckperms.common.http;
import okhttp3.Response;

View File

@ -39,6 +39,7 @@ import me.lucko.luckperms.common.event.AbstractEventBus;
import me.lucko.luckperms.common.event.EventDispatcher;
import me.lucko.luckperms.common.event.gen.GeneratedEventClass;
import me.lucko.luckperms.common.extension.SimpleExtensionManager;
import me.lucko.luckperms.common.http.BytebinClient;
import me.lucko.luckperms.common.inheritance.InheritanceGraphFactory;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.message.Message;
@ -53,7 +54,6 @@ import me.lucko.luckperms.common.storage.implementation.file.watcher.FileWatcher
import me.lucko.luckperms.common.tasks.SyncTask;
import me.lucko.luckperms.common.treeview.PermissionRegistry;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import me.lucko.luckperms.common.web.BytebinClient;
import net.luckperms.api.LuckPerms;

View File

@ -35,6 +35,7 @@ import me.lucko.luckperms.common.context.ContextManager;
import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.event.EventDispatcher;
import me.lucko.luckperms.common.extension.SimpleExtensionManager;
import me.lucko.luckperms.common.http.BytebinClient;
import me.lucko.luckperms.common.inheritance.InheritanceGraphFactory;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
@ -53,7 +54,6 @@ import me.lucko.luckperms.common.storage.implementation.file.watcher.FileWatcher
import me.lucko.luckperms.common.tasks.SyncTask;
import me.lucko.luckperms.common.treeview.PermissionRegistry;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import me.lucko.luckperms.common.web.BytebinClient;
import net.luckperms.api.query.QueryOptions;

View File

@ -29,14 +29,14 @@ import com.google.common.base.Splitter;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.cacheddata.type.PermissionCache;
import me.lucko.luckperms.common.http.AbstractHttpClient;
import me.lucko.luckperms.common.http.BytebinClient;
import me.lucko.luckperms.common.http.UnsuccessfulRequestException;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import me.lucko.luckperms.common.util.gson.JObject;
import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent;
import me.lucko.luckperms.common.web.AbstractHttpClient;
import me.lucko.luckperms.common.web.BytebinClient;
import me.lucko.luckperms.common.web.UnsuccessfulRequestException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

View File

@ -57,6 +57,13 @@ public class JArray implements JElement {
return this;
}
public JArray addAll(Iterable<String> iterable) {
for (String s : iterable) {
add(s);
}
return this;
}
public JArray add(JElement value) {
if (value == null) {
return add(JsonNull.INSTANCE);

View File

@ -29,6 +29,9 @@ import com.google.gson.JsonObject;
import me.lucko.luckperms.common.calculator.result.TristateResult;
import me.lucko.luckperms.common.command.utils.MessageUtils;
import me.lucko.luckperms.common.http.AbstractHttpClient;
import me.lucko.luckperms.common.http.BytebinClient;
import me.lucko.luckperms.common.http.UnsuccessfulRequestException;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationFormatter;
@ -40,9 +43,6 @@ import me.lucko.luckperms.common.util.gson.JObject;
import me.lucko.luckperms.common.verbose.event.MetaCheckEvent;
import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent;
import me.lucko.luckperms.common.verbose.event.VerboseEvent;
import me.lucko.luckperms.common.web.AbstractHttpClient;
import me.lucko.luckperms.common.web.BytebinClient;
import me.lucko.luckperms.common.web.UnsuccessfulRequestException;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;

View File

@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.web;
package me.lucko.luckperms.common.webeditor;
import com.google.common.base.Preconditions;
import com.google.gson.JsonObject;
@ -32,6 +32,8 @@ import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.ContextSetJsonSerializer;
import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl;
import me.lucko.luckperms.common.http.AbstractHttpClient;
import me.lucko.luckperms.common.http.UnsuccessfulRequestException;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.Track;
@ -49,15 +51,8 @@ import net.kyori.text.event.HoverEvent;
import net.kyori.text.format.TextColor;
import net.luckperms.api.context.ImmutableContextSet;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
@ -65,27 +60,21 @@ import java.util.List;
import java.util.zip.GZIPOutputStream;
/**
* Utility methods for interacting with the LuckPerms web permission editor.
* Encapsulates a request to the web editor.
*/
public final class WebEditor {
private WebEditor() {}
public class WebEditorRequest {
private static JObject writeData(PermissionHolder holder) {
return new JObject()
.add("type", holder.getType().toString())
.add("id", holder.getObjectName())
.add("displayName", holder.getPlainDisplayName())
.add("nodes", NodeJsonSerializer.serializeNodes(holder.normalData().asList()));
}
private static JObject writeData(Track track) {
return new JObject()
.add("type", "track")
.add("id", track.getName())
.add("groups", new JArray().consume(a -> track.getGroups().forEach(a::add)));
}
public static JsonObject formPayload(List<PermissionHolder> holders, List<Track> tracks, Sender sender, String cmdLabel, LuckPermsPlugin plugin) {
/**
* Generates a web editor request payload.
*
* @param holders the holders to edit
* @param tracks the tracks to edit
* @param sender the sender who is creating the session
* @param cmdLabel the command label used by LuckPerms
* @param plugin the plugin
* @return a payload
*/
public static WebEditorRequest generate(List<PermissionHolder> holders, List<Track> tracks, Sender sender, String cmdLabel, LuckPermsPlugin plugin) {
Preconditions.checkArgument(!holders.isEmpty(), "holders is empty");
ImmutableContextSet.Builder potentialContexts = new ImmutableContextSetImpl.BuilderImpl();
@ -95,55 +84,83 @@ public final class WebEditor {
}
// form the payload data
return new JObject()
.add("metadata", new JObject()
.add("commandAlias", cmdLabel)
.add("uploader", new JObject()
.add("name", sender.getNameWithLocation())
.add("uuid", sender.getUniqueId().toString())
)
.add("time", System.currentTimeMillis())
.add("pluginVersion", plugin.getBootstrap().getVersion())
)
return new WebEditorRequest(holders, tracks, sender, cmdLabel, potentialContexts.build(), plugin);
}
/**
* The encoded json object this payload is made up of
*/
private final JsonObject payload;
private WebEditorRequest(List<PermissionHolder> holders, List<Track> tracks, Sender sender, String cmdLabel, ImmutableContextSet potentialContexts, LuckPermsPlugin plugin) {
this.payload = new JObject()
.add("metadata", formMetadata(sender, cmdLabel, plugin.getBootstrap().getVersion()))
.add("permissionHolders", new JArray()
.consume(arr -> {
for (PermissionHolder holder : holders) {
arr.add(writeData(holder));
arr.add(formPermissionHolder(holder));
}
})
)
.add("tracks", new JArray()
.consume(arr -> {
for (Track track : tracks) {
arr.add(writeData(track));
arr.add(formTrack(track));
}
})
)
.add("knownPermissions", new JArray()
.consume(arr -> {
for (String perm : plugin.getPermissionRegistry().rootAsList()) {
arr.add(perm);
}
})
)
.consume(o -> {
o.add("potentialContexts", ContextSetJsonSerializer.serialize(potentialContexts.build()));
})
.add("knownPermissions", new JArray().addAll(plugin.getPermissionRegistry().rootAsList()))
.add("potentialContexts", ContextSetJsonSerializer.serialize(potentialContexts))
.toJson();
}
public static CommandResult post(JsonObject payload, Sender sender, LuckPermsPlugin plugin) {
// upload the payload data to gist
private static JObject formMetadata(Sender sender, String cmdLabel, String pluginVersion) {
return new JObject()
.add("commandAlias", cmdLabel)
.add("uploader", new JObject()
.add("name", sender.getNameWithLocation())
.add("uuid", sender.getUniqueId().toString())
)
.add("time", System.currentTimeMillis())
.add("pluginVersion", pluginVersion);
}
private static JObject formPermissionHolder(PermissionHolder holder) {
return new JObject()
.add("type", holder.getType().toString())
.add("id", holder.getObjectName())
.add("displayName", holder.getPlainDisplayName())
.add("nodes", NodeJsonSerializer.serializeNodes(holder.normalData().asList()));
}
private static JObject formTrack(Track track) {
return new JObject()
.add("type", "track")
.add("id", track.getName())
.add("groups", new JArray().addAll(track.getGroups()));
}
public byte[] encode() {
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
try (Writer writer = new OutputStreamWriter(new GZIPOutputStream(bytesOut), StandardCharsets.UTF_8)) {
GsonProvider.prettyPrinting().toJson(payload, writer);
GsonProvider.prettyPrinting().toJson(this.payload, writer);
} catch (IOException e) {
e.printStackTrace();
}
return bytesOut.toByteArray();
}
/**
* Creates a web editor session, and sends the URL to the sender.
*
* @param plugin the plugin
* @param sender the sender creating the session
* @return the command result
*/
public CommandResult createSession(LuckPermsPlugin plugin, Sender sender) {
String pasteId;
try {
pasteId = plugin.getBytebin().postContent(bytesOut.toByteArray(), AbstractHttpClient.JSON_TYPE, false).key();
pasteId = plugin.getBytebin().postContent(encode(), AbstractHttpClient.JSON_TYPE, false).key();
} catch (UnsuccessfulRequestException e) {
Message.EDITOR_HTTP_REQUEST_FAILURE.send(sender, e.getResponse().code(), e.getResponse().message());
return CommandResult.STATE_ERROR;
@ -167,25 +184,4 @@ public final class WebEditor {
return CommandResult.SUCCESS;
}
public static JsonObject readDataFromBytebin(BytebinClient bytebin, String id) throws IOException, UnsuccessfulRequestException {
Request request = new Request.Builder()
.header("User-Agent", bytebin.getUserAgent())
.url(bytebin.getUrl() + id)
.build();
try (Response response = bytebin.makeHttpRequest(request)) {
try (ResponseBody responseBody = response.body()) {
if (responseBody == null) {
throw new RuntimeException("No response");
}
try (InputStream inputStream = responseBody.byteStream()) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
return GsonProvider.normal().fromJson(reader, JsonObject.class);
}
}
}
}
}
}

View File

@ -0,0 +1,363 @@
/*
* 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.webeditor;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.command.access.ArgumentPermissions;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.MessageUtils;
import me.lucko.luckperms.common.command.utils.StorageAssistant;
import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.manager.group.GroupManager;
import me.lucko.luckperms.common.node.utils.NodeJsonSerializer;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationFormatter;
import me.lucko.luckperms.common.util.Uuids;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.event.cause.CreationCause;
import net.luckperms.api.event.cause.DeletionCause;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.node.Node;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* Encapsulates a response from the web editor.
*/
public class WebEditorResponse {
/**
* The encoded json object this payload is made up of
*/
private final JsonObject payload;
public WebEditorResponse(JsonObject payload) {
this.payload = payload;
}
/**
* Applies the response to storage, sending appropriate informational messages to the sender.
*
* @param plugin the plugin
* @param sender the sender who is applying the session
*/
public void apply(LuckPermsPlugin plugin, Sender sender) {
Session session = new Session(plugin, sender);
boolean work = false;
if (this.payload.has("changes")) {
JsonArray changes = this.payload.get("changes").getAsJsonArray();
for (JsonElement change : changes) {
if (session.applyChange(change.getAsJsonObject())) {
work = true;
}
}
}
if (this.payload.has("groupDeletions")) {
JsonArray groupDeletions = this.payload.get("groupDeletions").getAsJsonArray();
for (JsonElement groupDeletion : groupDeletions) {
if (session.applyGroupDelete(groupDeletion)) {
work = true;
}
}
}
if (this.payload.has("trackDeletions")) {
JsonArray trackDeletions = this.payload.get("trackDeletions").getAsJsonArray();
for (JsonElement trackDeletion : trackDeletions) {
if (session.applyTrackDelete(trackDeletion)) {
work = true;
}
}
}
if (!work) {
Message.APPLY_EDITS_TARGET_NO_CHANGES_PRESENT.send(sender);
}
}
/**
* Represents the application of a given editor session on this platform.
*/
private static class Session {
private final LuckPermsPlugin plugin;
private final Sender sender;
Session(LuckPermsPlugin plugin, Sender sender) {
this.plugin = plugin;
this.sender = sender;
}
private boolean applyChange(JsonObject changeInfo) {
String type = changeInfo.get("type").getAsString();
if (type.equals("user") || type.equals("group")) {
return applyHolderChange(changeInfo);
} else if (type.equals("track")) {
return applyTrackChange(changeInfo);
} else {
Message.APPLY_EDITS_UNKNOWN_TYPE.send(this.sender, type);
return false;
}
}
private boolean applyHolderChange(JsonObject changeInfo) {
String type = changeInfo.get("type").getAsString();
String id = changeInfo.get("id").getAsString();
PermissionHolder holder;
if (type.equals("user")) {
// user
UUID uuid = Uuids.parse(id);
if (uuid == null) {
Message.APPLY_EDITS_TARGET_USER_NOT_UUID.send(this.sender, id);
return false;
}
holder = this.plugin.getStorage().loadUser(uuid, null).join();
if (holder == null) {
Message.APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD.send(this.sender, uuid.toString());
return false;
}
} else {
// group
holder = this.plugin.getStorage().loadGroup(id).join().orElse(null);
if (holder == null) {
holder = this.plugin.getStorage().createAndLoadGroup(id, CreationCause.WEB_EDITOR).join();
}
}
if (ArgumentPermissions.checkModifyPerms(this.plugin, this.sender, CommandPermission.APPLY_EDITS, holder) || ArgumentPermissions.checkGroup(this.plugin, this.sender, holder, ImmutableContextSetImpl.EMPTY)) {
Message.COMMAND_NO_PERMISSION.send(this.sender);
return false;
}
Set<Node> before = holder.normalData().asSet();
Set<Node> after = new HashSet<>(NodeJsonSerializer.deserializeNodes(changeInfo.getAsJsonArray("nodes")));
Set<Node> diffAdded = getAdded(before, after);
Set<Node> diffRemoved = getRemoved(before, after);
int additions = diffAdded.size();
int deletions = diffRemoved.size();
if (additions == 0 && deletions == 0) {
return false;
}
holder.setNodes(DataType.NORMAL, after);
for (Node n : diffAdded) {
LoggedAction.build().source(this.sender).target(holder)
.description("webeditor", "add", n.getKey(), n.getValue(), n.getContexts())
.build().submit(this.plugin, this.sender);
}
for (Node n : diffRemoved) {
LoggedAction.build().source(this.sender).target(holder)
.description("webeditor", "remove", n.getKey(), n.getValue(), n.getContexts())
.build().submit(this.plugin, this.sender);
}
String additionsSummary = "addition" + (additions == 1 ? "" : "s");
String deletionsSummary = "deletion" + (deletions == 1 ? "" : "s");
Message.APPLY_EDITS_SUCCESS.send(this.sender, type, holder.getFormattedDisplayName());
Message.APPLY_EDITS_SUCCESS_SUMMARY.send(this.sender, additions, additionsSummary, deletions, deletionsSummary);
for (Node n : diffAdded) {
Message.APPLY_EDITS_DIFF_ADDED.send(this.sender, formatNode(this.plugin.getLocaleManager(), n));
}
for (Node n : diffRemoved) {
Message.APPLY_EDITS_DIFF_REMOVED.send(this.sender, formatNode(this.plugin.getLocaleManager(), n));
}
StorageAssistant.save(holder, this.sender, this.plugin);
return true;
}
private boolean applyTrackChange(JsonObject changeInfo) {
String id = changeInfo.get("id").getAsString();
Track track = this.plugin.getStorage().loadTrack(id).join().orElse(null);
if (track == null) {
track = this.plugin.getStorage().createAndLoadTrack(id, CreationCause.WEB_EDITOR).join();
}
if (ArgumentPermissions.checkModifyPerms(this.plugin, this.sender, CommandPermission.APPLY_EDITS, track)) {
Message.COMMAND_NO_PERMISSION.send(this.sender);
return false;
}
List<String> before = track.getGroups();
List<String> after = new ArrayList<>();
changeInfo.getAsJsonArray("groups").forEach(e -> after.add(e.getAsString()));
if (before.equals(after)) {
return false;
}
Set<String> diffAdded = getAdded(before, after);
Set<String> diffRemoved = getRemoved(before, after);
int additions = diffAdded.size();
int deletions = diffRemoved.size();
track.setGroups(after);
if (hasBeenReordered(before, after, diffAdded, diffRemoved)) {
LoggedAction.build().source(this.sender).target(track)
.description("webeditor", "reorder", after)
.build().submit(this.plugin, this.sender);
}
for (String n : diffAdded) {
LoggedAction.build().source(this.sender).target(track)
.description("webeditor", "add", n)
.build().submit(this.plugin, this.sender);
}
for (String n : diffRemoved) {
LoggedAction.build().source(this.sender).target(track)
.description("webeditor", "remove", n)
.build().submit(this.plugin, this.sender);
}
String additionsSummary = "addition" + (additions == 1 ? "" : "s");
String deletionsSummary = "deletion" + (deletions == 1 ? "" : "s");
Message.APPLY_EDITS_SUCCESS.send(this.sender, "track", track.getName());
Message.APPLY_EDITS_SUCCESS_SUMMARY.send(this.sender, additions, additionsSummary, deletions, deletionsSummary);
Message.APPLY_EDITS_DIFF_REMOVED.send(this.sender, before);
Message.APPLY_EDITS_DIFF_ADDED.send(this.sender, after);
StorageAssistant.save(track, this.sender, this.plugin);
return true;
}
private boolean applyGroupDelete(JsonElement changeInfo) {
String groupName = changeInfo.getAsString();
if (groupName.equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME)) {
Message.DELETE_GROUP_ERROR_DEFAULT.send(this.sender);
return true;
}
Group group = this.plugin.getStorage().loadGroup(groupName).join().orElse(null);
if (group == null) {
return false;
}
if (ArgumentPermissions.checkModifyPerms(this.plugin, this.sender, CommandPermission.APPLY_EDITS, group) || ArgumentPermissions.checkGroup(this.plugin, this.sender, group, ImmutableContextSetImpl.EMPTY)) {
Message.COMMAND_NO_PERMISSION.send(this.sender);
return false;
}
try {
this.plugin.getStorage().deleteGroup(group, DeletionCause.COMMAND).get();
} catch (Exception e) {
e.printStackTrace();
Message.DELETE_ERROR.send(this.sender, group.getFormattedDisplayName());
return true;
}
Message.DELETE_SUCCESS.send(this.sender, group.getFormattedDisplayName());
LoggedAction.build().source(this.sender).targetName(groupName).targetType(Action.Target.Type.GROUP)
.description("webeditor", "delete")
.build().submit(this.plugin, this.sender);
return true;
}
private boolean applyTrackDelete(JsonElement changeInfo) {
String trackName = changeInfo.getAsString();
Track track = this.plugin.getStorage().loadTrack(trackName).join().orElse(null);
if (track == null) {
return false;
}
if (ArgumentPermissions.checkModifyPerms(this.plugin, this.sender, CommandPermission.APPLY_EDITS, track)) {
Message.COMMAND_NO_PERMISSION.send(this.sender);
return false;
}
try {
this.plugin.getStorage().deleteTrack(track, DeletionCause.COMMAND).get();
} catch (Exception e) {
e.printStackTrace();
Message.DELETE_ERROR.send(this.sender, track.getName());
return true;
}
Message.DELETE_SUCCESS.send(this.sender, trackName);
LoggedAction.build().source(this.sender).targetName(trackName).targetType(Action.Target.Type.TRACK)
.description("webeditor", "delete")
.build().submit(this.plugin, this.sender);
return true;
}
private static String formatNode(LocaleManager localeManager, Node n) {
return n.getKey() + " &7(" + (n.getValue() ? "&a" : "&c") + n.getValue() + "&7)" + MessageUtils.getAppendableNodeContextString(localeManager, n) +
(n.hasExpiry() ? " &7(" + DurationFormatter.CONCISE.format(n.getExpiryDuration()) + ")" : "");
}
private static <T> Set<T> getAdded(Collection<T> before, Collection<T> after) {
Set<T> added = new LinkedHashSet<>(after);
added.removeAll(before);
return added;
}
private static <T> Set<T> getRemoved(Collection<T> before, Collection<T> after) {
Set<T> removed = new LinkedHashSet<>(before);
removed.removeAll(after);
return removed;
}
private static <T> boolean hasBeenReordered(List<T> before, List<T> after, Collection<T> diffAdded, Collection<T> diffRemoved) {
after = new ArrayList<>(after);
before = new ArrayList<>(before);
after.removeAll(diffAdded);
before.removeAll(diffRemoved);
return !before.equals(after);
}
}
}