Skip to content

Commit

Permalink
Implement scoped punishments with complete API (#146)
Browse files Browse the repository at this point in the history
* Add a default punishing scope and allow it to be used everywhere
* Punishing and listing commands now accept scope arguments
  -server=<server>, -category=<category>, and -scope=<scope>
  where <scope> is of the form 'global', 'server:<server>', or
  'category:<category>'
* Increment database revision number. Keep compatibility with
  legacy scope field for now. Previously, this scope field was
  accessible via the API only.
* Detect server name scope using plugin messaging on Bukkit and
  Sponge. Rework some of the existing plugin messaging and move
  the 'kick-via-plugin-messaging' option to a better location.
  Add a security warning if option is improperly enabled.
* When running on a proxy, enforce server-scoped punishments on
  the server switch event. Add flag to disable this enforcement
  for performance reasons.
* Add documentation for server scopes. Update documentation on
  plugin comparisons.
  • Loading branch information
A248 committed Sep 12, 2023
1 parent eeda754 commit 94591a8
Show file tree
Hide file tree
Showing 180 changed files with 4,436 additions and 1,789 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
@@ -1,37 +1,37 @@
/*
* LibertyBans-api
* Copyright © 2020 Anand Beh <https://www.arim.space>
*
* LibertyBans-api is free software: you can redistribute it and/or modify
/*
* 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-api is distributed in the hope that it will be useful,
*
* 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-api. If not, see <https://www.gnu.org/licenses/>
* 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.api.scope;

import java.util.Optional;
import java.util.Set;

/**
* Factory which produces {@link ServerScope}s
*
* @author A248
*
*/
public interface ScopeManager {

/**
* Gets a scope applying to a specific server. <br>
* <br>
* The server string must be non{@literal -}empty and less long than an
* implementation{@literal -}dependent length. The current limit is set at 32
* characters but this may change.
* The server string must not be empty and must be less long than a certain length dependent
* on the implementation. The current limit is set at 32 characters but this may change.
*
* @param server the server
* @return a scope applying to the server
Expand All @@ -40,17 +40,70 @@ public interface ScopeManager {
ServerScope specificScope(String server);

/**
* Gets the global scope, applying to all servers
* Gets a scope applying to a user defined category. <br>
* <br>
* The category string must not be empty and must be less long than a certain length dependent
* on the implementation. The current limit is set at 32 characters but this may change.
*
* @param category the category
* @return a scope applying to the category
* @throws IllegalArgumentException if {@code category} is empty or too big
*/
ServerScope category(String category);

/**
* Gets the global scope, applying to all servers and categories
*
* @return the global scope
*/
ServerScope globalScope();

/**
* Gets a scope applying to the current server
* Gets a scope applying to the current server. This will yield a scope which, when used to punish players,
* will enforce their punishments on the current server only. <br>
* <br>
* This is usually not the appropriate scope with which to <b>select</b> punishments (because only punishments
* made specifically for the server would be selected). Instead, see {@link #scopesApplicableToCurrentServer()}. <br>
* <br>
* The "current server" is defined with respect to an instance of the API implementation. Thus, using this method
* will apply to the proxy, backend server, or other application running the current instance. <br>
* <br>
* Please note that, in some circumstances, the current server scope will not be available, depending on user
* configuration. Use {@link #currentServerScopeOrFallback()} if you want to fallback to the scope specified
* in the user's configuration for this purpose.
*
* @return a scope applying to the current server
* @return if available, the scope applying to the current server only. On a proxy instance, using this scope will
* affect the entire proxy. On a backend server it will affect the backend server.
*/
Optional<ServerScope> currentServerScope();

/**
* Gets the current server scope if it is available (see {@link #currentServerScope()}. Otherwise,
* uses the server scope which the user has configured as a fallback for the current server scope
* when it is unavailable.
*
* @return the scope applying to the current server only, or the fallback if not available
*/
ServerScope currentServerScopeOrFallback();

/**
* Gets all scopes applying to the current server as defined by user configuration. This will necessarily
* include both the global scope ({@link #globalScope()}) and the current server scope
* ({@link #currentServerScope()}. <br>
* <br>
* The "current server" is defined with respect to an instance of the API implementation. Thus, using this method
* will yield scopes applying to the proxy, backend server, or other application running this instance.
*
* @return all scopes applicable to the current server
*/
Set<ServerScope> scopesApplicableToCurrentServer();

/**
* The scope configured as the default with which to punish. For example, if an operator punishes a victim without
* specifying a scope explicitly, this scope is used.
*
* @return the default punishing scope
*/
ServerScope currentServerScope();
ServerScope defaultPunishingScope();

}
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
/*
* LibertyBans-api
* Copyright © 2020 Anand Beh <https://www.arim.space>
*
* LibertyBans-api is free software: you can redistribute it and/or modify
/*
* 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-api is distributed in the hope that it will be useful,
*
* 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-api. If not, see <https://www.gnu.org/licenses/>
* 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.api.scope;
Expand All @@ -32,7 +32,11 @@ public interface ServerScope {
*
* @param server the server name
* @return true if applicable, false otherwise
* @deprecated For categorical scopes produced from {@link ScopeManager#category(String)}, it may not be known
* whether a scope applies to a certain server, because the configuration for the other server is not accessible
* by the current API instance.
*/
@Deprecated
boolean appliesTo(String server);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
package space.arim.libertybans.core.addon.it;

import jakarta.inject.Singleton;
import space.arim.libertybans.core.env.EnvMessageChannel;
import space.arim.libertybans.core.env.EnvServerNameDetection;
import space.arim.libertybans.core.importing.PlatformImportSource;
import space.arim.libertybans.core.selector.cache.MuteCache;
import space.arim.libertybans.core.selector.cache.OnDemandMuteCache;
Expand All @@ -37,6 +39,14 @@ public MuteCache muteCache(OnDemandMuteCache muteCache) {
return muteCache;
}

public EnvMessageChannel<?> messageChannel(EnvMessageChannel.NoOp messageChannel) {
return messageChannel;
}

public EnvServerNameDetection serverNameDetection() {
return (scopeManager) -> {};
}

public PlatformImportSource platformImportSource() {
throw new UnsupportedOperationException("PlatformImportSource not available");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import space.arim.libertybans.core.env.EnvEnforcer;
import space.arim.libertybans.core.env.EnvUserResolver;
import space.arim.libertybans.core.env.Environment;
import space.arim.libertybans.core.env.InstanceType;
import space.arim.libertybans.core.env.PlatformListener;
import space.arim.omnibus.util.concurrent.impl.IndifferentFactoryOfTheFuture;
import space.arim.omnibus.util.concurrent.impl.SimplifiedEnhancedExecutor;
Expand Down Expand Up @@ -87,6 +88,7 @@ public void execute(Runnable command) {
this.injector = new InjectorBuilder()
.bindInstance(Server.class, server)
.bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), folder)
.bindInstance(InstanceType.class, InstanceType.STANDALONE)
.bindInstance(PlatformHandle.class, handle)
.bindInstance(Environment.class, environment)
.bindInstance(EnvEnforcer.class, envEnforcer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@
import space.arim.dazzleconf.annote.ConfKey;
import space.arim.dazzleconf.annote.SubSection;
import space.arim.libertybans.api.PunishmentType;
import space.arim.libertybans.api.scope.ServerScope;
import space.arim.libertybans.core.addon.AddonConfig;
import space.arim.libertybans.core.commands.extra.DurationParser;
import space.arim.libertybans.core.config.ParsedDuration;
import space.arim.libertybans.core.config.PunishmentAdditionSection;
import space.arim.libertybans.core.config.VictimPermissionSection;
import space.arim.libertybans.core.scope.ConfiguredScope;
import space.arim.libertybans.core.scope.GlobalScope;

import java.util.Map;

Expand Down Expand Up @@ -130,11 +133,15 @@ static Map<String, Track.Ladder> defaultTracks() {
record SimpleLadder(boolean countActive,
Map<Integer, Track.Ladder.Progression> progressions) implements Track.Ladder {}

record SimpleProgression(PunishmentType type, String reason, ParsedDuration duration, String scope)
record SimpleProgression(PunishmentType type, String reason, ParsedDuration duration, ConfiguredScope scope)
implements Track.Ladder.Progression {

SimpleProgression(PunishmentType type, String reason, String duration) {
this(type, reason, new ParsedDuration(duration, new DurationParser().parse(duration)), "");
this(
type, reason,
new ParsedDuration(duration, new DurationParser().parse(duration)),
ConfiguredScope.defaultPunishingScope()
);
}
}
return Map.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import space.arim.dazzleconf.annote.SubSection;
import space.arim.libertybans.api.PunishmentType;
import space.arim.libertybans.core.config.ParsedDuration;
import space.arim.libertybans.core.scope.ConfiguredScope;

import java.util.Map;

Expand All @@ -45,8 +46,7 @@ interface Progression {

ParsedDuration duration();

@ConfValidator(ForcedEmptyScopeValidator.class)
String scope();
ConfiguredScope scope();

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public CalculationResult compute(EscalationTrack escalationTrack, Victim victim,
var progression = findProgression(existingPunishments + 1);
return new PunishmentDetailsCalculator.CalculationResult(
progression.type(), progression.reason(),
progression.duration().duration(), scopeManager.globalScope()
progression.duration().duration(), progression.scope().actualize(scopeManager)
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* LibertyBans
* Copyright © 2022 Anand Beh
* 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
Expand All @@ -25,10 +25,9 @@
import space.arim.dazzleconf.annote.ConfKey;
import space.arim.dazzleconf.annote.SubSection;
import space.arim.libertybans.api.PunishmentType;
import space.arim.libertybans.api.scope.ServerScope;
import space.arim.libertybans.core.addon.AddonConfig;
import space.arim.libertybans.core.config.ParsedDuration;
import space.arim.libertybans.core.scope.ScopeImpl;
import space.arim.libertybans.core.scope.ConfiguredScope;

import java.time.Duration;
import java.util.Map;
Expand Down Expand Up @@ -68,10 +67,7 @@ public interface WarnActionsConfig extends AddonConfig {
"Punishments to perform.",
"",
"Each punishment is performed as if by the console. The setting broadcast-notification controls whether",
"punishment notifications will be broadcast as usual; if false, no notifications are sent.",
"",
"Note that 'scope' is currently useless -- it is reserved for a future plugin feature.",
"In LibertyBans 1.1.0, a feature will be added to enable punishments scoped to specific backend servers."
"punishment notifications will be broadcast as usual; if false, no notifications are sent."
})
@ConfDefault.DefaultObject("autoPunishmentsDefaults")
Map<Integer, @SubSection WarnActionPunishment> autoPunishments();
Expand All @@ -95,8 +91,8 @@ public ParsedDuration duration() {
}

@Override
public ServerScope scope() {
return ScopeImpl.GLOBAL;
public ConfiguredScope scope() {
return ConfiguredScope.defaultPunishingScope();
}

@Override
Expand All @@ -121,8 +117,8 @@ public ParsedDuration duration() {
}

@Override
public ServerScope scope() {
return ScopeImpl.GLOBAL;
public ConfiguredScope scope() {
return ConfiguredScope.defaultPunishingScope();
}

@Override
Expand All @@ -141,7 +137,7 @@ interface WarnActionPunishment {

ParsedDuration duration();

ServerScope scope();
ConfiguredScope scope();

@ConfKey("broadcast-notification")
boolean broadcastNotification();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* LibertyBans
* Copyright © 2022 Anand Beh
* 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
Expand Down Expand Up @@ -34,6 +34,7 @@
import space.arim.libertybans.api.punish.EnforcementOptions;
import space.arim.libertybans.api.punish.Punishment;
import space.arim.libertybans.api.punish.PunishmentDrafter;
import space.arim.libertybans.api.scope.ScopeManager;
import space.arim.libertybans.api.select.PunishmentSelector;
import space.arim.libertybans.core.config.InternalFormatter;
import space.arim.libertybans.core.env.EnvEnforcer;
Expand All @@ -48,17 +49,19 @@ public final class WarnActionsListener implements AsynchronousEventConsumer<Post

private final FactoryOfTheFuture futuresFactory;
private final PunishmentDrafter drafter;
private final ScopeManager scopeManager;
private final PunishmentSelector selector;
private final InternalFormatter formatter;
private final EnvEnforcer<?> envEnforcer;
private final WarnActionsAddon addon;

@Inject
public WarnActionsListener(FactoryOfTheFuture futuresFactory, PunishmentDrafter drafter,
public WarnActionsListener(FactoryOfTheFuture futuresFactory, PunishmentDrafter drafter, ScopeManager scopeManager,
PunishmentSelector selector, InternalFormatter formatter,
EnvEnforcer<?> envEnforcer, WarnActionsAddon addon) {
this.futuresFactory = futuresFactory;
this.drafter = drafter;
this.scopeManager = scopeManager;
this.selector = selector;
this.formatter = formatter;
this.envEnforcer = envEnforcer;
Expand Down Expand Up @@ -131,7 +134,7 @@ private CentralisedFuture<?> handleAutoPunish() {
.operator(ConsoleOperator.INSTANCE)
.reason(additionalPunishment.reason())
.duration(additionalPunishment.duration().duration())
.scope(additionalPunishment.scope())
.scope(additionalPunishment.scope().actualize(scopeManager))
.build();
EnforcementOptions enforcementOptions = draftPunishment
.enforcementOptionsBuilder()
Expand Down
Loading

0 comments on commit 94591a8

Please sign in to comment.