Skip to content

Commit

Permalink
More progress on scopes. Revamp plugin messaging
Browse files Browse the repository at this point in the history
  • Loading branch information
A248 committed Aug 31, 2023
1 parent f01997b commit 485dbfb
Show file tree
Hide file tree
Showing 60 changed files with 1,360 additions and 633 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ Free software and high quality, LibertyBans is the best-designed punishment plug

Supported platforms:

* Spigot / Paper, including Folia
* BungeeCord
* Spigot / Paper (+Folia)
* BungeeCord / Waterfall
* Sponge
* Velocity

Expand All @@ -98,3 +98,5 @@ The developer API is extensive. LibertyBans does not recommend developers mess w
### License

LibertyBans is licensed under the GNU AGPL v3. See the license file for more information.

[![GNU AGPL Logo](https://www.gnu.org/graphics/agplv3-155x51.png)](https://www.gnu.org/licenses/agpl-3.0.en.html)
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public GlobalEnforcement enforcement(StandardGlobalEnforcement enforcement) {
return enforcement;
}

public LocalEnforcer enforcer(StandardLocalEnforcer enforcer) {
public LocalEnforcer enforcer(StandardLocalEnforcer<?> enforcer) {
return enforcer;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,16 +193,20 @@ interface Sponge {
@ConfHeader("Related to game servers such as Spigot, Paper, and Sponge")
interface GameServers {

@ConfKey("kick-via-plugin-messaging")
@ConfKey("use-plugin-messaging")
@ConfComments({
"This option is relevant for backend servers running within a network (BungeeCord or Velocity).",
"It instructs the proxy to kick the player from the network via plugin messaging.",
"It enables the use of plugin messaging, such as for:",
" - Kicking the player from the entire network",
" - Detecting the name of the backend server for use with server scopes",
" - Synchronizing punishments across instances, depending on the mode in the sql.yml",
"",
"If enabled, the player will NOT be kicked by the backend server, so you MUST use a proxy",
"otherwise players will not be kicked at all."
"DO NOT enable this option if you do not run a network. Otherwise, you create a security vulnerability",
"whereby players can pretend to be coming from a proxy, evading kicks and sending sync messages.",
"After changing this option, please perform a restart (/libertybans restart)."
})
@DefaultBoolean(false)
boolean kickViaPluginMessaging();
boolean usePluginMessaging();

}

Expand All @@ -212,6 +216,16 @@ interface GameServers {
@ConfHeader("Related to proxies such as BungeeCord and Velocity")
interface Proxies {

@ConfKey("multiple-proxy-instances")
@ConfComments({
"Set this to true to indicate that you are running multiple proxy instances.",
"",
"It will instruct LibertyBans to perform additional synchronization measures, where applicable."
})
@DefaultBoolean(false)
// Currently unused, but may be utilized later
boolean multipleProxyInstances();

@ConfKey("enforce-server-switch")
@ConfComments({
"Server-scoped punishments will be enforced by preventing server switches for players connecting ",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,15 @@ interface ServerName {
@ConfKey("auto-detect")
@ConfComments({
"By default, we try to detect the name of this backend server using plugin messaging.",
"On a proxy, the detected name becomes 'proxy'.",
"Make sure 'use-plugin-messaging' is enabled in the config.yml for this detection to succeed.",
"",
"If running a proxy, the detected name becomes 'proxy'.",
"",
"Plugin messaging requires at least one player to have logged into the backend server.",
"Auto detection may fail, for example, if you ban someone through the console but no one has joined yet.",
"",
"To disable auto detection, set this to false then configure 'override-value' to the server name you wish to use."
"To disable auto detection, set this to false then configure 'override-value' to the server name you wish to use.",
"Re-enabling this option may require a restart (/libertybans restart)"
})
@ConfDefault.DefaultBoolean(true)
boolean autoDetect();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
import org.jooq.Condition;
import space.arim.libertybans.api.scope.ServerScope;
import space.arim.libertybans.core.scope.InternalScopeManager;
import space.arim.libertybans.core.scope.ScopeParsing;
import space.arim.libertybans.core.scope.ScopeType;

import static org.jooq.impl.DSL.noCondition;

Expand All @@ -32,7 +30,7 @@ public record ScopeCondition(ScopeFields scopeFields, InternalScopeManager scope

@Override
public Condition matchesValue(ServerScope scope) {
return new ScopeParsing().deconstruct(scope, (type, value) -> {
return scopeManager.deconstruct(scope, (type, value) -> {
Condition typeMatches = scopeFields.scopeType().eq(type);
Condition valueMatches = switch (type) {
case GLOBAL -> noCondition();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import space.arim.api.env.AudienceRepresenter;
import space.arim.libertybans.core.config.InternalFormatter;
import space.arim.libertybans.core.env.message.PluginMessage;
import space.arim.omnibus.util.ThisClass;
import space.arim.omnibus.util.concurrent.CentralisedFuture;
import space.arim.omnibus.util.concurrent.FactoryOfTheFuture;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;

Expand All @@ -36,6 +42,8 @@ public abstract class AbstractEnvEnforcer<P> implements EnvEnforcer<P> {
private final Interlocutor interlocutor;
private final AudienceRepresenter<? super P> audienceRepresenter;

private static final Logger logger = LoggerFactory.getLogger(ThisClass.get());

protected AbstractEnvEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter formatter,
Interlocutor interlocutor, AudienceRepresenter<? super P> audienceRepresenter) {
this.futuresFactory = Objects.requireNonNull(futuresFactory, "futuresFactory");
Expand Down Expand Up @@ -83,10 +91,30 @@ private CentralisedFuture<Void> sendToThoseWithPermissionNoPrefix(String permiss
}
};
}
return doForAllPlayers(callback);
return doForAllPlayers((players) -> players.forEach(callback));
}

@Override
public final <D> void sendPluginMessage(P player, PluginMessage<D, ?> pluginMessage, D data) {
if (!sendPluginMessageIfListening(player, pluginMessage, data)) {
logger.error(
"Attempted to send plugin message to {}, but the appropriate channel is not accepted. " +
"This suggests you enabled use-plugin-messaging in the config.yml, but the player " +
"is not connected to a network. Please address this critical security flaw immediately. " +
"It leaves your server vulnerable to clients spoofing the plugin messaging channel",
player
);
}
}

protected abstract CentralisedFuture<Void> doForAllPlayers(Consumer<P> callback);
/**
* Sends a plugin message to the given player if it is accepted by their client
*
* @param player the player to whom to send the message
* @param pluginMessage the plugin message
* @return true if sent, false if unsupported
*/
public abstract <D> boolean sendPluginMessageIfListening(P player, PluginMessage<D, ?> pluginMessage, D data);

@Override
public final void sendMessageNoPrefix(P player, ComponentLike message) {
Expand All @@ -95,10 +123,16 @@ public final void sendMessageNoPrefix(P player, ComponentLike message) {

@Override
public final CentralisedFuture<Void> enforceMatcher(TargetMatcher<P> matcher) {
return doForAllPlayers((player) -> {
if (matcher.matches(getUniqueIdFor(player), getAddressFor(player))) {
matcher.callback().accept(player);
return doForAllPlayers((players) -> {
List<P> matchedPlayers = new ArrayList<>();
// Some platforms do not provide guarantees about concurrent iteration in presence of kicks
// Proxies effectively must, but game server APIs like Bukkit and Sponge need not
for (P player : players) {
if (matcher.matches(getUniqueIdFor(player), getAddressFor(player))) {
matchedPlayers.add(player);
}
}
matchedPlayers.forEach(matcher.callback());
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
package space.arim.libertybans.core.env;

import java.net.InetAddress;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import space.arim.api.env.annote.PlatformPlayer;
import space.arim.libertybans.core.env.message.PluginMessage;
import space.arim.omnibus.util.concurrent.CentralisedFuture;

/**
Expand All @@ -45,6 +47,7 @@ public interface EnvEnforcer<@PlatformPlayer P> {
*
* @param permission the permission
* @param message the message
* @return a future completed when the operation is done
*/
CentralisedFuture<Void> sendToThoseWithPermission(String permission, ComponentLike message);

Expand All @@ -53,9 +56,18 @@ public interface EnvEnforcer<@PlatformPlayer P> {
*
* @param uuid the uuid
* @param callback the callback
* @return a future completed when the operation is done
*/
CentralisedFuture<Void> doForPlayerIfOnline(UUID uuid, Consumer<P> callback);

/**
* Completes an action for all players online
*
* @param action the action
* @return a future completed when the operation is done
*/
CentralisedFuture<Void> doForAllPlayers(Consumer<Collection<? extends P>> action);

/**
* Kicks the given player. <br>
* <br>
Expand All @@ -66,6 +78,14 @@ public interface EnvEnforcer<@PlatformPlayer P> {
*/
void kickPlayer(P player, Component message);

/**
* Sends a plugin message to the given player. Must be used only for proxies.
*
* @param player the player to whom to send the message
* @param pluginMessage the plugin message
*/
<D> void sendPluginMessage(P player, PluginMessage<D, ?> pluginMessage, D data);

/**
* Sends a message to the given player. Does not include a prefix. <br>
* <br>
Expand Down Expand Up @@ -103,6 +123,16 @@ public interface EnvEnforcer<@PlatformPlayer P> {
*/
InetAddress getAddressFor(P player);

/**
* Gets the name of a player. <br>
* <br>
* <b>Must be used within a callback.</b>
*
* @param player the player
* @return the name
*/
String getNameFor(P player);

/**
* Determines whether the player has a permission
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* LibertyBans
* Copyright © 2023 Anand Beh
*
* LibertyBans is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* LibertyBans 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with LibertyBans. If not, see <https://www.gnu.org/licenses/>
* and navigate to version 3 of the GNU Affero General Public License.
*/

package space.arim.libertybans.core.env;

import space.arim.libertybans.core.env.message.PluginMessage;

import java.util.function.Consumer;

public interface EnvMessageChannel<H> {

/**
* Installs a platform specific handler
*
* @param handler the handler
*/
void installHandler(H handler);

/**
* Uninstalls a platform specific handler
*
* @param handler the handler
*/
void uninstallHandler(H handler);

/**
* Wraps an acceptor as a platform specific handler. Should be called once.
*
* @param acceptor the acceptor
* @param pluginMessage the plugin message it handles
* @return the handler
* @param <R> the type of the handler
*/
<R> H createHandler(Consumer<R> acceptor, PluginMessage<?, R> pluginMessage);

static <H> void parameterize(EnvMessageChannel<H> messageChannel, Consumer<EnvMessageChannel<H>> action) {
action.accept(messageChannel);
}

final class NoOp implements EnvMessageChannel<Void> {

@Override
public void installHandler(Void handler) {

}

@Override
public void uninstallHandler(Void handler) {

}

@Override
public <R> Void createHandler(Consumer<R> acceptor, PluginMessage<?, R> pluginMessage) {
return null;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* LibertyBans
* Copyright © 2023 Anand Beh
*
* LibertyBans is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* LibertyBans 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with LibertyBans. If not, see <https://www.gnu.org/licenses/>
* and navigate to version 3 of the GNU Affero General Public License.
*/

package space.arim.libertybans.core.env;

public enum InstanceType {
GAME_SERVER,
PROXY,
STANDALONE
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* and navigate to version 3 of the GNU Affero General Public License.
*/

package space.arim.libertybans.env.spigot;
package space.arim.libertybans.core.env;

import space.arim.libertybans.core.env.message.PluginMessage;
import space.arim.libertybans.core.env.message.PluginMessageInput;
Expand All @@ -33,9 +33,9 @@
import java.io.UncheckedIOException;
import java.util.Optional;

record PluginMessageData<D, R>(PluginMessage<D, R> pluginMessage) {
public record PluginMessageAsBytes<D, R>(PluginMessage<D, R> pluginMessage) {

byte[] generateBytes(D data) {
public byte[] generateBytes(D data) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DataOutputStream dataOutput = new DataOutputStream(outputStream)) {

Expand All @@ -47,7 +47,7 @@ byte[] generateBytes(D data) {
}
}

Optional<R> readBytes(byte[] data) {
public Optional<R> readBytes(byte[] data) {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
DataInputStream dataInput = new DataInputStream(inputStream)) {

Expand Down
Loading

0 comments on commit 485dbfb

Please sign in to comment.