Fix/v6/click event actions (#3178)

Co-authored-by: NotMyFault <mc.cache@web.de>
Co-authored-by: dordsor21 <dordsor21@gmail.com>
This commit is contained in:
Hannes Greule 2021-08-08 13:28:43 +02:00 committed by GitHub
parent 5ab8d50b86
commit 3b2a04e4db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 414 additions and 24 deletions

View File

@ -27,6 +27,7 @@ package com.plotsquared.core.command;
import com.plotsquared.core.PlotSquared;
import com.plotsquared.core.configuration.Settings;
import com.plotsquared.core.configuration.caption.CaptionUtility;
import com.plotsquared.core.configuration.caption.StaticCaption;
import com.plotsquared.core.configuration.caption.Templates;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
@ -335,10 +336,11 @@ public final class FlagCommand extends Command {
return;
}
boolean force = event.getEventResult() == Result.FORCE;
final String value = StringMan.join(Arrays.copyOfRange(args, 1, args.length), " ");
String value = StringMan.join(Arrays.copyOfRange(args, 1, args.length), " ");
if (!force && !checkPermValue(player, plotFlag, args[0], value)) {
return;
}
value = CaptionUtility.stripClickEvents(plotFlag, value);
final PlotFlag<?, ?> parsed;
try {
parsed = plotFlag.parse(value);

View File

@ -26,12 +26,14 @@
package com.plotsquared.core.configuration;
import com.plotsquared.core.configuration.file.YamlConfiguration;
import net.kyori.adventure.text.event.ClickEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class Settings extends Config {
@ -498,10 +500,12 @@ public class Settings extends Config {
"notify-enter, notify-leave, greeting or farewell flag."})
public static boolean NOTIFICATION_AS_ACTIONBAR = false;
@Comment({"Whether to strip any possible <click_event> components from user-defined messages, e.g. plot greeting",
"This can allow players to use commands to give themselves ranks as commands ran in this fashion cannot be prevent by " +
"permissions etc."})
public static boolean REMOVE_USER_DEFINED_CLICK_EVENTS = true;
@Comment({"The click event actions that should be removed from user input in e.g. plot flags like 'greeting'.",
"Actions like 'RUN_COMMAND' may be used maliciously as players could trick staff into clicking on messages",
"triggering destructive commands."})
public static List<String> CLICK_EVENT_ACTIONS_TO_REMOVE = Arrays.stream(ClickEvent.Action.values())
.map(Enum::name)
.collect(Collectors.toList());
}

View File

@ -25,12 +25,42 @@
*/
package com.plotsquared.core.configuration.caption;
import com.plotsquared.core.configuration.Settings;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.flag.PlotFlag;
import com.plotsquared.core.plot.flag.implementations.DescriptionFlag;
import com.plotsquared.core.plot.flag.implementations.FarewellFlag;
import com.plotsquared.core.plot.flag.implementations.GreetingFlag;
import com.plotsquared.core.plot.flag.types.StringFlag;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Set;
import static com.plotsquared.core.configuration.caption.ComponentTransform.nested;
import static com.plotsquared.core.configuration.caption.ComponentTransform.stripClicks;
public class CaptionUtility {
// flags which values are parsed by minimessage
private static final Set<Class<? extends StringFlag<?>>> MINI_MESSAGE_FLAGS = Set.of(
GreetingFlag.class,
FarewellFlag.class,
DescriptionFlag.class
);
private static final ComponentTransform CLICK_STRIP_TRANSFORM = nested(
stripClicks(
Settings.Chat.CLICK_EVENT_ACTIONS_TO_REMOVE.stream()
.map(ClickEvent.Action::valueOf)
.toArray(ClickEvent.Action[]::new)
)
);
/**
* Format a chat message but keep the formatting keys
*
@ -66,4 +96,42 @@ public class CaptionUtility {
return chatContext.getMessage();
}
/**
* Strips configured click events from a MiniMessage string.
*
* @param miniMessageString the message from which the specified click events should be removed from.
* @return the string without the click events that are configured to be removed.
*
* @see Settings.Chat#CLICK_EVENT_ACTIONS_TO_REMOVE
*/
public static String stripClickEvents(final @NonNull String miniMessageString) {
// parse, transform and serialize again
Component component = MiniMessage.get().parse(miniMessageString);
component = CLICK_STRIP_TRANSFORM.transform(component);
return MiniMessage.get().serialize(component);
}
/**
* Strips configured MiniMessage click events from a plot flag value.
* This is used before letting the string be parsed by the plot flag.
* This method works the same way as {@link #stripClickEvents(String)} but will only
* strip click events from messages that target flags that are meant to contain MiniMessage strings.
*
* @param flag the flag the message is targeted for.
* @param miniMessageString the message from which the specified click events should be removed from.
* @return the string without the click events that are configured to be removed.
*
* @see Settings.Chat#CLICK_EVENT_ACTIONS_TO_REMOVE
* @see #stripClickEvents(String)
*/
public static String stripClickEvents(
final @NonNull PlotFlag<?, ?> flag,
final @NonNull String miniMessageString
) {
if (MINI_MESSAGE_FLAGS.contains(flag.getClass())) {
return stripClickEvents(miniMessageString);
}
return miniMessageString;
}
}

View File

@ -0,0 +1,50 @@
/*
* _____ _ _ _____ _
* | __ \| | | | / ____| | |
* | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| |
* | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` |
* | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| |
* |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_|
* | |
* |_|
* PlotSquared plot management system for Minecraft
* Copyright (C) 2021 IntellectualSites
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.core.configuration.caption;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.NotNull;
import java.util.EnumSet;
import java.util.Set;
final class ClickStripTransform implements ComponentTransform {
private final Set<ClickEvent.@NotNull Action> actionsToStrip;
public ClickStripTransform(final Set<ClickEvent.@NotNull Action> actionsToStrip) {
this.actionsToStrip = EnumSet.copyOf(actionsToStrip);
}
@Override
public @NonNull Component transform(@NonNull final Component original) {
var clickEvent = original.clickEvent();
if (clickEvent == null || !actionsToStrip.contains(clickEvent.action())) return original;
return original.clickEvent(null); // remove it
}
}

View File

@ -0,0 +1,67 @@
/*
* _____ _ _ _____ _
* | __ \| | | | / ____| | |
* | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| |
* | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` |
* | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| |
* |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_|
* | |
* |_|
* PlotSquared plot management system for Minecraft
* Copyright (C) 2021 IntellectualSites
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.core.configuration.caption;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Set;
public interface ComponentTransform {
/**
* Creates a transform that applies the given transform on all child components and the
* component itself. The children are transformed before the component itself is transformed.
*
* @param transform the transform to apply.
* @return a new transform which is applied on all child components and the component itself.
*/
static ComponentTransform nested(ComponentTransform transform) {
return new NestedComponentTransform(transform);
}
/**
* Creates a transform that removes click events of the given actions from a component.
* Note: To remove click events from children too, the returned transform must be wrapped
* using {@link #nested(ComponentTransform)}.
*
* @param actionsToRemove the actions used to filter which click events should be removed.
* @return a new transform that removes click events from a component.
*/
static ComponentTransform stripClicks(ClickEvent.Action... actionsToRemove) {
return new ClickStripTransform(Set.of(actionsToRemove));
}
/**
* Applies this transform on the given component and returns the result.
*
* @param original the component to transform.
* @return the transformed component.
*/
@NonNull Component transform(@NonNull Component original);
}

View File

@ -0,0 +1,54 @@
/*
* _____ _ _ _____ _
* | __ \| | | | / ____| | |
* | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| |
* | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` |
* | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| |
* |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_|
* | |
* |_|
* PlotSquared plot management system for Minecraft
* Copyright (C) 2021 IntellectualSites
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.core.configuration.caption;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.stream.Collectors;
/**
* A transform that applies a nested transform on all child components and the component itself.
*/
final class NestedComponentTransform implements ComponentTransform {
private final ComponentTransform transform;
public NestedComponentTransform(final ComponentTransform transform) {
this.transform = transform;
}
@Override
public @NonNull Component transform(final @NonNull Component original) {
return this.transform.transform(original.children(transformChildren(original.children())));
}
private List<Component> transformChildren(List<Component> children) {
return children.stream().map(this::transform).collect(Collectors.toList());
}
}

View File

@ -30,6 +30,7 @@ import com.plotsquared.core.PlotSquared;
import com.plotsquared.core.configuration.ConfigurationSection;
import com.plotsquared.core.configuration.Settings;
import com.plotsquared.core.configuration.Storage;
import com.plotsquared.core.configuration.caption.CaptionUtility;
import com.plotsquared.core.configuration.file.YamlConfiguration;
import com.plotsquared.core.inject.annotations.WorldConfig;
import com.plotsquared.core.listener.PlotListener;
@ -2033,7 +2034,7 @@ public class SQLManager implements AbstractDB {
while (resultSet.next()) {
id = resultSet.getInt("plot_id");
final String flag = resultSet.getString("flag");
final String value = resultSet.getString("value");
String value = resultSet.getString("value");
final Plot plot = plots.get(id);
if (plot != null) {
final PlotFlag<?, ?> plotFlag =
@ -2041,6 +2042,7 @@ public class SQLManager implements AbstractDB {
if (plotFlag == null) {
plot.getFlagContainer().addUnknownFlag(flag, value);
} else {
value = CaptionUtility.stripClickEvents(plotFlag, value);
try {
plot.getFlagContainer().addFlag(plotFlag.parse(value));
} catch (final FlagParseException e) {

View File

@ -171,9 +171,6 @@ public class PlotListener {
String greeting = plot.getFlag(GreetingFlag.class);
if (!greeting.isEmpty()) {
if (Settings.Chat.REMOVE_USER_DEFINED_CLICK_EVENTS) {
greeting = greeting.replaceAll(".([c-lC-L]{5}):([a-uA-U_]{11}):[^\\/]*[^>]*>>", "").replace("</click>", "");
}
if (!Settings.Chat.NOTIFICATION_AS_ACTIONBAR) {
plot.format(StaticCaption.of(greeting), player, false).thenAcceptAsync(player::sendMessage);
} else {
@ -395,9 +392,6 @@ public class PlotListener {
String farewell = plot.getFlag(FarewellFlag.class);
if (!farewell.isEmpty()) {
if (Settings.Chat.REMOVE_USER_DEFINED_CLICK_EVENTS) {
farewell = farewell.replaceAll(".([c-lC-L]{5}):([a-uA-U_]{11}):[^\\/]*[^>]*>", "").replace("</click>", "");
}
if (!Settings.Chat.NOTIFICATION_AS_ACTIONBAR) {
plot.format(StaticCaption.of(farewell), player, false).thenAcceptAsync(player::sendMessage);
} else {

View File

@ -2790,9 +2790,6 @@ public class Plot {
String description = this.getFlag(DescriptionFlag.class);
if (description.isEmpty()) {
description = TranslatableCaption.of("info.plot_no_description").getComponent(player);
} else if (Settings.Chat.REMOVE_USER_DEFINED_CLICK_EVENTS) {
description = description.replaceAll(".([c-lC-L]{5}):([a-uA-U_]{11}):[^\\/]*[^>]*>", "").replace("</click>",
"");
}
Component flags;

View File

@ -27,6 +27,7 @@ package com.plotsquared.core.plot.flag;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.plotsquared.core.configuration.caption.CaptionUtility;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -316,8 +317,9 @@ public class FlagContainer {
) {
if (plotFlagUpdateType != PlotFlagUpdateType.FLAG_REMOVED && this.unknownFlags
.containsKey(flag.getName())) {
final String value = this.unknownFlags.remove(flag.getName());
String value = this.unknownFlags.remove(flag.getName());
if (value != null) {
value = CaptionUtility.stripClickEvents(flag, value);
try {
this.addFlag(flag.parse(value));
} catch (final Exception ignored) {

View File

@ -26,10 +26,10 @@
package com.plotsquared.core.plot.flag.implementations;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.plot.flag.PlotFlag;
import com.plotsquared.core.plot.flag.types.StringFlag;
import org.checkerframework.checker.nullness.qual.NonNull;
public class DescriptionFlag extends PlotFlag<String, DescriptionFlag> {
public class DescriptionFlag extends StringFlag<DescriptionFlag> {
public static final DescriptionFlag DESCRIPTION_FLAG_EMPTY = new DescriptionFlag("");

View File

@ -28,10 +28,10 @@ package com.plotsquared.core.plot.flag.implementations;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.plot.Plot;
import com.plotsquared.core.plot.flag.InternalFlag;
import com.plotsquared.core.plot.flag.PlotFlag;
import com.plotsquared.core.plot.flag.types.StringFlag;
import org.checkerframework.checker.nullness.qual.NonNull;
public class DoneFlag extends PlotFlag<String, DoneFlag> implements InternalFlag {
public class DoneFlag extends StringFlag<DoneFlag> implements InternalFlag {
/**
* Construct a new flag instance.

View File

@ -26,10 +26,10 @@
package com.plotsquared.core.plot.flag.implementations;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.plot.flag.PlotFlag;
import com.plotsquared.core.plot.flag.types.StringFlag;
import org.checkerframework.checker.nullness.qual.NonNull;
public class FarewellFlag extends PlotFlag<String, FarewellFlag> {
public class FarewellFlag extends StringFlag<FarewellFlag> {
public static final FarewellFlag FAREWELL_FLAG_EMPTY = new FarewellFlag("");

View File

@ -26,10 +26,10 @@
package com.plotsquared.core.plot.flag.implementations;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.plot.flag.PlotFlag;
import com.plotsquared.core.plot.flag.types.StringFlag;
import org.checkerframework.checker.nullness.qual.NonNull;
public class GreetingFlag extends PlotFlag<String, GreetingFlag> {
public class GreetingFlag extends StringFlag<GreetingFlag> {
public static final GreetingFlag GREETING_FLAG_EMPTY = new GreetingFlag("");

View File

@ -0,0 +1,49 @@
/*
* _____ _ _ _____ _
* | __ \| | | | / ____| | |
* | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| |
* | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` |
* | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| |
* |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_|
* | |
* |_|
* PlotSquared plot management system for Minecraft
* Copyright (C) 2021 IntellectualSites
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.core.plot.flag.types;
import com.plotsquared.core.configuration.caption.Caption;
import com.plotsquared.core.plot.flag.PlotFlag;
import org.checkerframework.checker.nullness.qual.NonNull;
public abstract class StringFlag<F extends StringFlag<F>> extends PlotFlag<String, F> {
/**
* Construct a new flag instance.
*
* @param value Flag value
* @param flagCategory The flag category
* @param flagDescription A caption describing the flag functionality
*/
protected StringFlag(
final @NonNull String value,
final @NonNull Caption flagCategory,
final @NonNull Caption flagDescription
) {
super(value, flagCategory, flagDescription);
}
}

View File

@ -0,0 +1,96 @@
/*
* _____ _ _ _____ _
* | __ \| | | | / ____| | |
* | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| |
* | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` |
* | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| |
* |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_|
* | |
* |_|
* PlotSquared plot management system for Minecraft
* Copyright (C) 2021 IntellectualSites
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.core.configuration.caption;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.EnumSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
class ClickStripTransformTest {
@Test
@DisplayName("Remove click event of specific action correctly")
void removeClickEvent() {
var commonAction = ClickEvent.Action.OPEN_FILE;
var transform = new ClickStripTransform(EnumSet.of(commonAction));
var component = Component.text("Hello")
.clickEvent(ClickEvent.clickEvent(
commonAction,
"World"
)
);
var transformedComponent = transform.transform(component);
assertNull(transformedComponent.clickEvent());
}
@Test
@DisplayName("Don't remove click events of other action types")
void ignoreClickEvent() {
var actionToRemove = ClickEvent.Action.SUGGEST_COMMAND;
var transform = new ClickStripTransform(EnumSet.of(actionToRemove));
var originalClickEvent = ClickEvent.clickEvent(
ClickEvent.Action.CHANGE_PAGE,
"World"
);
var component = Component.text("Hello")
.clickEvent(originalClickEvent);
var transformedComponent = transform.transform(component);
assertEquals(originalClickEvent, transformedComponent.clickEvent());
}
@Test
@DisplayName("Remove nested click events correctly")
void removeNestedClickEvent() {
// nested transform is required to apply on children
var transform = new NestedComponentTransform(new ClickStripTransform(EnumSet.allOf(ClickEvent.Action.class)));
var inner = Component
// some arbitrary values that should remain
.text("World")
.color(NamedTextColor.AQUA)
.hoverEvent(HoverEvent.showText(Component.text("ABC")))
.decorate(TextDecoration.OBFUSCATED)
.insertion("DEF");
var component = Component.text("Hello ")
.append(
inner.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://example.org"))
);
var transformedComponent = transform.transform(component);
assertFalse(transformedComponent.children().isEmpty()); // child still exists
assertEquals(inner, transformedComponent.children().get(0)); // only the click event has changed
assertNull(transformedComponent.children().get(0).clickEvent());
}
}

View File

@ -81,6 +81,7 @@ allprojects {
dependencies {
// Tests
testImplementation("junit:junit:4.13.2")
testImplementation("org.junit.jupiter:junit-jupiter:5.7.2")
}
plugins.withId("java") {
@ -215,7 +216,11 @@ allprojects {
named("build") {
dependsOn(named("shadowJar"))
}
test {
useJUnitPlatform()
}
}
}
tasks {