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.
* 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.
  • Loading branch information
A248 committed Aug 19, 2023
1 parent fb14929 commit c8af256
Show file tree
Hide file tree
Showing 139 changed files with 3,385 additions and 1,344 deletions.
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 @@ -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
Expand Up @@ -33,13 +33,13 @@
import space.arim.libertybans.api.punish.DraftPunishment;
import space.arim.libertybans.api.punish.DraftPunishmentBuilder;
import space.arim.libertybans.api.punish.PunishmentDrafter;
import space.arim.libertybans.api.scope.ServerScope;
import space.arim.libertybans.core.addon.AddonCenter;
import space.arim.libertybans.core.addon.shortcutreasons.ShortcutReasonsAddon;
import space.arim.libertybans.core.addon.shortcutreasons.ShortcutReasonsConfig;
import space.arim.libertybans.core.addon.shortcutreasons.ShortcutReasonsListener;
import space.arim.libertybans.core.env.CmdSender;
import space.arim.libertybans.core.event.PunishEventImpl;
import space.arim.libertybans.core.scope.ScopeImpl;
import space.arim.omnibus.DefaultOmnibus;
import space.arim.omnibus.Omnibus;

Expand Down Expand Up @@ -93,7 +93,8 @@ public void noMatch(@Mock DraftPunishment draftPunishment) {

@Test
public void simpleSubstitute(@Mock DraftPunishment draftPunishment,
@Mock DraftPunishment newPunishment, @Mock DraftPunishmentBuilder newBuilder) {
@Mock DraftPunishment newPunishment, @Mock DraftPunishmentBuilder newBuilder,
@Mock ServerScope globalScope) {
when(config.shortcutIdentifier()).thenReturn("#");
when(config.shortcuts()).thenReturn(Map.of(
"hacking", "hello hackers",
Expand All @@ -104,15 +105,15 @@ public void simpleSubstitute(@Mock DraftPunishment draftPunishment,
when(draftPunishment.getVictim()).thenReturn(AddressVictim.of(new byte[4]));
when(draftPunishment.getOperator()).thenReturn(ConsoleOperator.INSTANCE);
when(draftPunishment.getReason()).thenReturn("#hacking");
when(draftPunishment.getScope()).thenReturn(ScopeImpl.GLOBAL);
when(draftPunishment.getScope()).thenReturn(globalScope);

when(drafter.draftBuilder()).thenReturn(newBuilder);
when(newBuilder.type(PunishmentType.KICK)).thenReturn(newBuilder);
when(newBuilder.victim(AddressVictim.of(new byte[4]))).thenReturn(newBuilder);
when(newBuilder.operator(ConsoleOperator.INSTANCE)).thenReturn(newBuilder);
when(newBuilder.reason("hello hackers")).thenReturn(newBuilder);
when(newBuilder.duration(any())).thenReturn(newBuilder);
when(newBuilder.scope(ScopeImpl.GLOBAL)).thenReturn(newBuilder);
when(newBuilder.scope(globalScope)).thenReturn(newBuilder);
when(newBuilder.build()).thenReturn(newPunishment);

var event = fireEvent(draftPunishment);
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 c8af256

Please sign in to comment.