From 235dace127d6613e25878178ccb1dc71c33f2f26 Mon Sep 17 00:00:00 2001 From: A248 Date: Fri, 18 Aug 2023 17:41:45 -0500 Subject: [PATCH] Implement scoped punishments with complete API (#146) * Add a default punishing scope and allow it to be used everywhere * Punishing and listing commands now accept scope arguments -server=, -category=, and -scope= where is of the form 'global', 'server:', or '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. --- README.md | 6 +- .../libertybans/api/scope/ScopeManager.java | 89 ++++++-- .../libertybans/api/scope/ServerScope.java | 22 +- .../core/addon/it/AddonITModule.java | 10 + .../libertybans/core/addon/it/AddonsIT.java | 2 + .../core/addon/layouts/LayoutsConfig.java | 11 +- .../libertybans/core/addon/layouts/Track.java | 4 +- .../core/addon/layouts/TrackCalculator.java | 2 +- .../addon/warnactions/WarnActionsConfig.java | 20 +- .../warnactions/WarnActionsListener.java | 9 +- .../warnactions/WarnActionsListenerTest.java | 10 +- bans-core/pom.xml | 43 +++- .../core/it/jpmscompat/JpmsBindModule.java | 7 +- .../core/it/jpmscompat/JpmsLauncher.java | 6 +- bans-core/src/main/java/module-info.java | 2 + .../core/PillarOneBindModuleMinusConfigs.java | 8 +- .../core/commands/ArrayCommandPackage.java | 21 +- .../core/commands/CommandPackage.java | 16 +- .../core/commands/ListCommands.java | 11 +- .../core/commands/PunishCommands.java | 10 + .../core/commands/StringCommandPackage.java | 28 ++- .../core/commands/extra/ArgumentParser.java | 16 +- .../core/commands/extra/ParseScope.java | 60 +++++ .../extra/StandardArgumentParser.java | 74 ++++++- .../core/config/ConfigSerialisers.java | 8 +- .../arim/libertybans/core/config/Configs.java | 22 +- .../libertybans/core/config/Formatter.java | 2 +- .../libertybans/core/config/MainConfig.java | 72 ++++-- .../core/config/MessagesConfig.java | 21 +- .../libertybans/core/config/ScopeConfig.java | 127 +++++++++++ .../core/config/StandardConfigs.java | 32 ++- .../core/database/DatabaseSettings.java | 9 +- .../libertybans/core/database/Vendor.java | 29 ++- .../database/flyway/MigrateWithFlyway.java | 11 +- .../V34__Scope_identifier_sequence.java | 44 ++++ .../database/flyway/V38__Scope_migration.java | 81 +++++++ .../core/database/jooq/BatchExecute.java | 87 ++++++++ .../core/database/jooq/BatchTransfer.java | 59 +---- .../core/database/jooq/JooqClassloading.java | 15 +- .../database/jooq/ScopeTypeConverter.java | 18 +- .../database/sql/ApplicableViewFields.java | 30 ++- .../database/sql/MultiFieldCriterion.java | 46 ++++ .../core/database/sql/PunishmentFields.java | 14 +- .../core/database/sql/ScopeCondition.java | 43 ++++ .../core/database/sql/ScopeFields.java | 31 +++ .../database/sql/ScopeIdSequenceValue.java | 71 ++++++ .../core/database/sql/SequenceValue.java | 61 ++++-- .../core/database/sql/SimpleViewFields.java | 25 +-- .../core/database/sql/TableForType.java | 22 +- .../database/sql/TrackIdSequenceValue.java | 18 +- .../core/database/sql/VictimCondition.java | 7 +- .../database/sql/VictimIdSequenceValue.java | 12 +- .../core/env/AbstractEnvEnforcer.java | 44 +++- .../libertybans/core/env/EnvEnforcer.java | 30 +++ .../core/env/EnvMessageChannel.java | 74 +++++++ .../core/env/EnvServerNameDetection.java | 31 +++ .../core/env/EnvironmentManager.java | 19 +- .../libertybans/core/env/InstanceType.java | 26 +++ .../core/env/ParallelisedListener.java | 4 +- .../core/env/PluginMessageAsBytes.java | 73 +++++++ .../core/env/message/GetServer.java | 27 +-- .../core/env/message/KickPlayer.java | 47 ++++ .../core/env/message/PluginMessage.java | 56 +++++ .../core/env/message/PluginMessageInput.java | 28 +++ .../core/env/message/PluginMessageOutput.java | 28 +++ .../core/importing/SelfImportProcess.java | 8 +- .../punish/DraftPunishmentBuilderImpl.java | 3 +- .../libertybans/core/punish/Enaction.java | 19 +- .../arim/libertybans/core/punish/Enactor.java | 5 +- .../libertybans/core/punish/Modifier.java | 38 ++-- .../core/punish/PunishmentCreator.java | 21 +- .../arim/libertybans/core/punish/Revoker.java | 29 ++- .../core/punish/SecurePunishmentCreator.java | 54 +++-- .../core/punish/StandardLocalEnforcer.java | 206 +++++++++--------- ...copeSerializer.java => CategoryScope.java} | 25 +-- .../core/scope/ConfiguredScope.java | 94 ++++++++ .../libertybans/core/scope/GlobalScope.java | 38 ++++ .../core/scope/InternalScopeManager.java | 38 +++- .../libertybans/core/scope/ScopeImpl.java | 98 --------- .../libertybans/core/scope/ScopeParsing.java | 128 +++++++++++ .../libertybans/core/scope/ScopeType.java | 26 +++ .../arim/libertybans/core/scope/Scoper.java | 65 ------ .../core/scope/ServerNameListenerBase.java | 86 ++++++++ .../SpecificServerScope.java} | 37 +--- .../core/scope/StandardScopeManager.java | 167 ++++++++++++++ .../libertybans/core/selector/Gatekeeper.java | 8 +- .../libertybans/core/selector/Guardian.java | 19 +- .../libertybans/core/selector/IDImpl.java | 16 +- .../core/selector/IntelligentGuardian.java | 72 ++++-- .../core/selector/InternalSelector.java | 6 +- .../core/selector/SelectionBaseSQL.java | 24 +- .../SelectionByApplicabilityBuilderImpl.java | 7 + .../core/selector/SelectionOrderImpl.java | 28 +-- .../core/selector/SelectionResources.java | 2 + .../core/selector/SelectorImpl.java | 7 +- ...riteria.java => SingleFieldCriterion.java} | 108 ++++----- bans-core/src/main/resources/contributors | 1 + .../V36__Server_scopes.sql | 191 ++++++++++++++++ .../core/PillarOneReplacementModule.java | 5 +- .../core/commands/CommandPackageTest.java | 27 ++- .../core/commands/UnspecifiedReasonsTest.java | 17 +- .../extra/StandardArgumentParserTest.java | 134 ++++++++++-- .../core/config/FormatterTest.java | 6 +- .../core/config/SpecifiedConfigs.java | 5 + .../AdvancedBanImportSourceTest.java | 5 +- .../importing/BanManagerImportSourceTest.java | 5 +- .../importing/LiteBansImportSourceTest.java | 12 +- .../core/punish/IntelligentGuardianTest.java | 9 +- .../core/punish/PunishmentBuilderTest.java | 10 +- .../core/selector/SelectionBaseSQLTest.java | 15 +- .../core/service/SettableTime.java | 9 +- .../core/service/SettableTimeImpl.java | 10 +- .../space/arim/libertybans/it/ConfigSpec.java | 64 +----- .../it/ConfigSpecPossiblities.java | 75 +++---- .../arim/libertybans/it/DatabaseInstance.java | 15 +- .../InjectionInvocationContextProvider.java | 37 ++-- .../it/InjectorCleanupCallback.java | 8 +- .../arim/libertybans/it/PlatformSpecs.java | 65 ++++++ .../arim/libertybans/it/ResourceCreator.java | 8 +- .../arim/libertybans/it/SetServerType.java | 16 +- .../libertybans/it/env/QuackBindModule.java | 12 +- .../libertybans/it/env/QuackEnforcer.java | 18 +- .../platform/PlatformSpecsEqualityTest.java | 60 +++++ .../it/env/platform/QuackPlayer.java | 8 +- .../env/platform/ReceivedPluginMessage.java | 25 +++ .../test/applicable/OptimizedQueriesIT.java | 15 +- .../test/database/DatabaseRequirementsIT.java | 10 +- .../database/migrate08/MigrationResult.java | 125 ----------- .../migrate08/ZeroeightInterlocutor.java | 181 --------------- .../test/importing/AdvancedBanImportIT.java | 20 +- .../it/test/importing/BanManagerImportIT.java | 14 +- .../it/test/importing/LiteBansImportIT.java | 17 +- .../it/test/importing/SelfImportIT.java | 13 +- .../it/test/punish/EscalationIT.java | 10 +- .../it/test/select/SelectionIT.java | 73 ++++++- .../codegen/V0__Sequences.sql | 2 + .../env/bungee/AddressReporter.java | 20 +- .../env/bungee/BungeeBindModule.java | 12 +- .../env/bungee/BungeeEnforcer.java | 21 +- .../env/bungee/BungeeLauncher.java | 4 +- .../env/bungee/BungeeMessageChannel.java | 81 +++++++ .../env/bungee/ConnectionListener.java | 39 +++- .../env/spigot/ServerNameListener.java | 63 ++++++ .../env/spigot/SpigotBindModule.java | 12 +- .../env/spigot/SpigotEnforcer.java | 52 ++--- .../libertybans/env/spigot/SpigotEnv.java | 16 +- .../env/spigot/SpigotLauncher.java | 4 +- .../env/spigot/SpigotMessageChannel.java | 92 ++++++++ .../env/spigot/BukkitImportSourceTest.java | 23 +- .../libertybans/env/spigot/SpigotEnvTest.java | 6 +- .../libertybans/env/sponge/ChatListener.java | 4 +- .../env/sponge/ServerNameListener.java | 61 ++++++ .../env/sponge/SpongeBindModule.java | 12 +- .../env/sponge/SpongeEnforcer.java | 22 +- .../libertybans/env/sponge/SpongeEnv.java | 9 +- .../env/sponge/SpongeLauncher.java | 4 +- .../env/sponge/SpongeMessageChannel.java | 110 ++++++++++ .../libertybans/env/sponge/SpongeEnvTest.java | 5 +- bans-env/spongeplugin/pom.xml | 19 ++ .../env/standalone/StandaloneBindModule.java | 10 + .../env/standalone/StandaloneEnforcer.java | 17 ++ .../env/standalone/StandaloneLauncher.java | 2 + .../env/velocity/ChatListener.java | 56 +++-- .../env/velocity/CommandListener.java | 64 ------ .../env/velocity/ConnectionListener.java | 75 +++++-- .../env/velocity/VelocityAsyncListener.java | 119 ---------- .../env/velocity/VelocityBindModule.java | 12 +- .../env/velocity/VelocityEnforcer.java | 23 +- .../libertybans/env/velocity/VelocityEnv.java | 7 +- .../env/velocity/VelocityLauncher.java | 4 +- .../env/velocity/VelocityMessageChannel.java | 84 +++++++ .../env/velocity/ConnectionListenerTest.java | 37 +++- .../env/velocity/VelocityEnvTest.java | 13 +- docs/Comparison-to-LiteBans.md | 18 +- docs/Permissions.md | 6 + docs/Plugin-Comparison-Conclusions.md | 46 ++-- docs/Quick-Plugin-Comparison.md | 60 ++++- docs/Scoped-Punishments.md | 56 +++++ docs/_sidebar.md | 1 + pom.xml | 2 +- 180 files changed, 4439 insertions(+), 1789 deletions(-) create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ParseScope.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/config/ScopeConfig.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V34__Scope_identifier_sequence.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V38__Scope_migration.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchExecute.java rename bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/ForcedEmptyScopeValidator.java => bans-core/src/main/java/space/arim/libertybans/core/database/jooq/ScopeTypeConverter.java (62%) create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/database/sql/MultiFieldCriterion.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeCondition.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeFields.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeIdSequenceValue.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/env/EnvMessageChannel.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/env/EnvServerNameDetection.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/env/InstanceType.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/env/PluginMessageAsBytes.java rename bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ChannelRegistration.java => bans-core/src/main/java/space/arim/libertybans/core/env/message/GetServer.java (56%) create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/env/message/KickPlayer.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessage.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageInput.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageOutput.java rename bans-core/src/main/java/space/arim/libertybans/core/scope/{ScopeSerializer.java => CategoryScope.java} (58%) create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/scope/ConfiguredScope.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/scope/GlobalScope.java delete mode 100644 bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeImpl.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeParsing.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeType.java delete mode 100644 bans-core/src/main/java/space/arim/libertybans/core/scope/Scoper.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/scope/ServerNameListenerBase.java rename bans-core/src/main/java/space/arim/libertybans/core/{database/jooq/ServerScopeConverter.java => scope/SpecificServerScope.java} (51%) create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/scope/StandardScopeManager.java rename bans-core/src/main/java/space/arim/libertybans/core/selector/{Criteria.java => SingleFieldCriterion.java} (76%) create mode 100644 bans-core/src/main/resources/database-migrations/V36__Server_scopes.sql create mode 100644 bans-core/src/test/java/space/arim/libertybans/it/PlatformSpecs.java create mode 100644 bans-core/src/test/java/space/arim/libertybans/it/env/platform/PlatformSpecsEqualityTest.java create mode 100644 bans-core/src/test/java/space/arim/libertybans/it/env/platform/ReceivedPluginMessage.java delete mode 100644 bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/MigrationResult.java delete mode 100644 bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/ZeroeightInterlocutor.java create mode 100644 bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeMessageChannel.java create mode 100644 bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ServerNameListener.java create mode 100644 bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotMessageChannel.java create mode 100644 bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ServerNameListener.java create mode 100644 bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeMessageChannel.java delete mode 100644 bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/CommandListener.java delete mode 100644 bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityAsyncListener.java create mode 100644 bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityMessageChannel.java create mode 100644 docs/Scoped-Punishments.md diff --git a/README.md b/README.md index 9df742775..f0d6fea6b 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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) diff --git a/bans-api/src/main/java/space/arim/libertybans/api/scope/ScopeManager.java b/bans-api/src/main/java/space/arim/libertybans/api/scope/ScopeManager.java index dc6767ba7..47a685e2c 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/scope/ScopeManager.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/scope/ScopeManager.java @@ -1,27 +1,28 @@ -/* - * LibertyBans-api - * Copyright © 2020 Anand Beh - * - * 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 + * along with LibertyBans. If not, see * 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 { @@ -29,9 +30,8 @@ public interface ScopeManager { /** * Gets a scope applying to a specific server.
*
- * 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 @@ -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.
+ *
+ * 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.
+ *
+ * This is usually not the appropriate scope with which to select punishments (because only punishments + * made specifically for the server would be selected). Instead, see {@link #scopesApplicableToCurrentServer()}.
+ *
+ * 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.
+ *
+ * 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 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()}.
+ *
+ * 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 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(); } diff --git a/bans-api/src/main/java/space/arim/libertybans/api/scope/ServerScope.java b/bans-api/src/main/java/space/arim/libertybans/api/scope/ServerScope.java index 77ccd806d..762193a73 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/scope/ServerScope.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/scope/ServerScope.java @@ -1,19 +1,19 @@ -/* - * LibertyBans-api - * Copyright © 2020 Anand Beh - * - * 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 + * along with LibertyBans. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ package space.arim.libertybans.api.scope; @@ -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); /** diff --git a/bans-core-addons/addon-integration/src/test/java/space/arim/libertybans/core/addon/it/AddonITModule.java b/bans-core-addons/addon-integration/src/test/java/space/arim/libertybans/core/addon/it/AddonITModule.java index ee5b3d95a..52132f357 100644 --- a/bans-core-addons/addon-integration/src/test/java/space/arim/libertybans/core/addon/it/AddonITModule.java +++ b/bans-core-addons/addon-integration/src/test/java/space/arim/libertybans/core/addon/it/AddonITModule.java @@ -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; @@ -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"); } diff --git a/bans-core-addons/addon-integration/src/test/java/space/arim/libertybans/core/addon/it/AddonsIT.java b/bans-core-addons/addon-integration/src/test/java/space/arim/libertybans/core/addon/it/AddonsIT.java index 5009ac00d..5c58d2b1d 100644 --- a/bans-core-addons/addon-integration/src/test/java/space/arim/libertybans/core/addon/it/AddonsIT.java +++ b/bans-core-addons/addon-integration/src/test/java/space/arim/libertybans/core/addon/it/AddonsIT.java @@ -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; @@ -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) diff --git a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/LayoutsConfig.java b/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/LayoutsConfig.java index ead5f1535..22ae4938c 100644 --- a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/LayoutsConfig.java +++ b/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/LayoutsConfig.java @@ -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; @@ -130,11 +133,15 @@ static Map defaultTracks() { record SimpleLadder(boolean countActive, Map 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( diff --git a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/Track.java b/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/Track.java index d4a42ac54..cf696937d 100644 --- a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/Track.java +++ b/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/Track.java @@ -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; @@ -45,8 +46,7 @@ interface Progression { ParsedDuration duration(); - @ConfValidator(ForcedEmptyScopeValidator.class) - String scope(); + ConfiguredScope scope(); } } diff --git a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/TrackCalculator.java b/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/TrackCalculator.java index 08f760236..01c473658 100644 --- a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/TrackCalculator.java +++ b/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/TrackCalculator.java @@ -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) ); } diff --git a/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsConfig.java b/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsConfig.java index 71e21a729..3a11ffd5f 100644 --- a/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsConfig.java +++ b/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsConfig.java @@ -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 @@ -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; @@ -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 autoPunishments(); @@ -95,8 +91,8 @@ public ParsedDuration duration() { } @Override - public ServerScope scope() { - return ScopeImpl.GLOBAL; + public ConfiguredScope scope() { + return ConfiguredScope.defaultPunishingScope(); } @Override @@ -121,8 +117,8 @@ public ParsedDuration duration() { } @Override - public ServerScope scope() { - return ScopeImpl.GLOBAL; + public ConfiguredScope scope() { + return ConfiguredScope.defaultPunishingScope(); } @Override @@ -141,7 +137,7 @@ interface WarnActionPunishment { ParsedDuration duration(); - ServerScope scope(); + ConfiguredScope scope(); @ConfKey("broadcast-notification") boolean broadcastNotification(); diff --git a/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListener.java b/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListener.java index 5a32d97da..86adcc7d9 100644 --- a/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListener.java +++ b/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListener.java @@ -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 @@ -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; @@ -48,17 +49,19 @@ public final class WarnActionsListener implements AsynchronousEventConsumer 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; @@ -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() diff --git a/bans-core-addons/warn-actions/src/test/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListenerTest.java b/bans-core-addons/warn-actions/src/test/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListenerTest.java index 0f1ac0c41..647d36884 100644 --- a/bans-core-addons/warn-actions/src/test/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListenerTest.java +++ b/bans-core-addons/warn-actions/src/test/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListenerTest.java @@ -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 @@ -35,6 +35,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.scope.ServerScope; import space.arim.libertybans.api.select.PunishmentSelector; import space.arim.libertybans.api.select.SelectionOrder; @@ -43,6 +44,7 @@ import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.config.ParsedDuration; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.scope.ConfiguredScope; import space.arim.omnibus.DefaultOmnibus; import space.arim.omnibus.events.EventFireController; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; @@ -92,10 +94,10 @@ public WarnActionsListenerTest(@Mock PunishmentDrafter drafter, @Mock Punishment } @BeforeEach - public void setWarnActionsListener() { + public void setWarnActionsListener(@Mock ScopeManager scopeManager) { WarnActionsAddon addon = new WarnActionsAddon(addonCenter, new DefaultOmnibus(), () -> warnActionsListener); warnActionsListener = new WarnActionsListener( - futuresFactory, drafter, selector, formatter, envEnforcer, addon + futuresFactory, drafter, scopeManager, selector, formatter, envEnforcer, addon ); } @@ -172,7 +174,7 @@ public void theThirdWarnIsReached(@Mock SelectionOrderBuilder selectionBuilder, when(draftBuilder.reason("no reason at all")).thenReturn(draftBuilder); when(autoPunishment.duration()).thenReturn(new ParsedDuration("3h", Duration.ofHours(3L))); when(draftBuilder.duration(Duration.ofHours(3L))).thenReturn(draftBuilder); - when(autoPunishment.scope()).thenReturn(autoPunishmentScope); + when(autoPunishment.scope()).thenReturn(ConfiguredScope.create(autoPunishmentScope)); when(draftBuilder.scope(autoPunishmentScope)).thenReturn(draftBuilder); when(draftBuilder.build()).thenReturn(draftPunishment); when(draftPunishment.enforcementOptionsBuilder()).thenReturn(enforcementOptionsBuilder); diff --git a/bans-core/pom.xml b/bans-core/pom.xml index 25e0a745b..687455282 100644 --- a/bans-core/pom.xml +++ b/bans-core/pom.xml @@ -1,3 +1,22 @@ + + @@ -101,6 +120,9 @@ VARBINARY(16) BLOB ALTER VIEW + CAST(0 AS SMALLINT) + CAST( + AS CHARACTER VARYING(32)) filesystem:src/main/resources/database-migrations @@ -188,18 +210,18 @@ .*\.operator$ ^UUID$ - - space.arim.libertybans.api.scope.ServerScope - space.arim.libertybans.core.database.jooq.ServerScopeConverter - .*\.scope$ - ^CHARACTER\ VARYING\(32\)$ - space.arim.libertybans.api.punish.EscalationTrack space.arim.libertybans.core.database.jooq.EscalationTrackConverter .*\.track$ ^CHARACTER\ VARYING\(129\)$ + + space.arim.libertybans.core.scope.ScopeType + space.arim.libertybans.core.database.jooq.ScopeTypeConverter + ^(scopes\.type|punishments\.scope_type|simple_.*\.scope_type|applicable_.*\.scope_type)$ + ^SMALLINT$ + @@ -706,11 +728,6 @@ org.mockito mockito-junit-jupiter - - net.kyori - adventure-text-serializer-legacy - test - net.kyori adventure-text-serializer-plain @@ -782,6 +799,10 @@ space.arim.api arimapi-util-testing + + net.kyori + adventure-text-serializer-legacy + org.hsqldb diff --git a/bans-core/src/it/jpms-compat/src/main/java/space/arim/libertybans/core/it/jpmscompat/JpmsBindModule.java b/bans-core/src/it/jpms-compat/src/main/java/space/arim/libertybans/core/it/jpmscompat/JpmsBindModule.java index e7f749146..851dbb13a 100644 --- a/bans-core/src/it/jpms-compat/src/main/java/space/arim/libertybans/core/it/jpmscompat/JpmsBindModule.java +++ b/bans-core/src/it/jpms-compat/src/main/java/space/arim/libertybans/core/it/jpmscompat/JpmsBindModule.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -19,6 +19,7 @@ package space.arim.libertybans.core.it.jpmscompat; +import space.arim.libertybans.core.env.EnvMessageChannel; import space.arim.libertybans.core.selector.cache.MuteCache; import space.arim.libertybans.core.selector.cache.OnDemandMuteCache; import space.arim.libertybans.core.importing.PlatformImportSource; @@ -29,6 +30,10 @@ public MuteCache muteCache(OnDemandMuteCache muteCache) { return muteCache; } + public EnvMessageChannel messageChannel(EnvMessageChannel.NoOp messageChannel) { + return messageChannel; + } + public PlatformImportSource platformImportSource() { throw new UnsupportedOperationException("PlatformImportSource not available"); } diff --git a/bans-core/src/it/jpms-compat/src/main/java/space/arim/libertybans/core/it/jpmscompat/JpmsLauncher.java b/bans-core/src/it/jpms-compat/src/main/java/space/arim/libertybans/core/it/jpmscompat/JpmsLauncher.java index aa1930713..39807c43c 100644 --- a/bans-core/src/it/jpms-compat/src/main/java/space/arim/libertybans/core/it/jpmscompat/JpmsLauncher.java +++ b/bans-core/src/it/jpms-compat/src/main/java/space/arim/libertybans/core/it/jpmscompat/JpmsLauncher.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -28,8 +28,10 @@ import space.arim.libertybans.core.PillarOneBindModule; import space.arim.libertybans.core.PillarTwoBindModule; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; +import space.arim.libertybans.core.env.InstanceType; import space.arim.omnibus.DefaultOmnibus; import space.arim.omnibus.Omnibus; @@ -56,11 +58,13 @@ public JpmsLauncher(Path folder, PlatformHandle handle, Environment environment, public BaseFoundation launch() { return new InjectorBuilder() .bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), folder) + .bindInstance(InstanceType.class, InstanceType.STANDALONE) .bindInstance(Omnibus.class, new DefaultOmnibus()) .bindInstance(PlatformHandle.class, handle) .bindInstance(Environment.class, environment) .bindInstance(EnvEnforcer.class, envEnforcer) .bindInstance(EnvUserResolver.class, envUserResolver) + .bindInstance(EnvServerNameDetection.class, (scopeManager) -> {}) .addBindModules( new ApiBindModule(), new PillarOneBindModule(), diff --git a/bans-core/src/main/java/module-info.java b/bans-core/src/main/java/module-info.java index ecd6aed24..f10e6ce29 100644 --- a/bans-core/src/main/java/module-info.java +++ b/bans-core/src/main/java/module-info.java @@ -26,6 +26,7 @@ requires static java.compiler; requires net.kyori.adventure; requires net.kyori.examination.api; + requires net.kyori.adventure.text.serializer.legacy; requires org.flywaydb.core; requires static org.checkerframework.checker.qual; requires static org.jetbrains.annotations; @@ -53,6 +54,7 @@ exports space.arim.libertybans.core.database.execute to space.arim.injector; exports space.arim.libertybans.core.database.flyway to org.flywaydb.core; exports space.arim.libertybans.core.env; + exports space.arim.libertybans.core.env.message; exports space.arim.libertybans.core.event to space.arim.injector, space.arim.libertybans.core.addon.shortcutreasons, space.arim.libertybans.core.addon.layouts; exports space.arim.libertybans.core.importing; exports space.arim.libertybans.core.punish; diff --git a/bans-core/src/main/java/space/arim/libertybans/core/PillarOneBindModuleMinusConfigs.java b/bans-core/src/main/java/space/arim/libertybans/core/PillarOneBindModuleMinusConfigs.java index 7e9f146cb..4bac77b71 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/PillarOneBindModuleMinusConfigs.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/PillarOneBindModuleMinusConfigs.java @@ -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 @@ -33,7 +33,7 @@ import space.arim.libertybans.core.punish.StandardGlobalEnforcement; import space.arim.libertybans.core.punish.StandardLocalEnforcer; import space.arim.libertybans.core.scope.InternalScopeManager; -import space.arim.libertybans.core.scope.Scoper; +import space.arim.libertybans.core.scope.StandardScopeManager; import space.arim.libertybans.core.selector.InternalSelector; import space.arim.libertybans.core.selector.SelectorImpl; @@ -55,7 +55,7 @@ public GlobalEnforcement enforcement(StandardGlobalEnforcement enforcement) { return enforcement; } - public LocalEnforcer enforcer(StandardLocalEnforcer enforcer) { + public LocalEnforcer enforcer(StandardLocalEnforcer enforcer) { return enforcer; } @@ -67,7 +67,7 @@ public InternalSelector selector(SelectorImpl selector) { return selector; } - public InternalScopeManager scopeManager(Scoper scopeManager) { + public InternalScopeManager scopeManager(StandardScopeManager scopeManager) { return scopeManager; } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/ArrayCommandPackage.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/ArrayCommandPackage.java index 272b5f897..b8142893e 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/ArrayCommandPackage.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/ArrayCommandPackage.java @@ -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 @@ -19,6 +19,7 @@ package space.arim.libertybans.core.commands; +import java.util.Locale; import java.util.NoSuchElementException; import java.util.StringJoiner; @@ -46,7 +47,10 @@ public static ArrayCommandPackage create(String...args) { // Maintains the guarantee that position never refers to a hidden argument private void movePastHiddenArguments() { - while (position < args.length && args[position].startsWith(HIDDEN_ARG_PREFIX)) { + String currentArg; + while (position < args.length + && !(currentArg = args[position]).isEmpty() + && currentArg.charAt(0) == HIDDEN_ARG_PREFIX) { position++; } } @@ -73,7 +77,7 @@ public boolean hasNext() { @Override public boolean findHiddenArgument(String argument) { - String searchFor = "-" + argument; + String searchFor = '-' + argument; for (int n = 0; n < position; n++) { if (args[n].equalsIgnoreCase(searchFor)) { return true; @@ -82,6 +86,17 @@ public boolean findHiddenArgument(String argument) { return false; } + @Override + public String findHiddenArgumentSpecifiedValue(String argPrefix) { + String searchFor = '-' + argPrefix + '='; + for (int n = 0; n < position; n++) { + if (args[n].toLowerCase(Locale.ROOT).startsWith(searchFor)) { + return args[n].substring(searchFor.length()); + } + } + return null; + } + @Override public String allRemaining() { StringJoiner joiner = new StringJoiner(" "); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandPackage.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandPackage.java index 0eae9a606..38e2b7445 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandPackage.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandPackage.java @@ -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 @@ -19,6 +19,8 @@ package space.arim.libertybans.core.commands; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.Iterator; /** @@ -43,7 +45,7 @@ public interface CommandPackage extends Iterator { /** * The prefix denoting a hidden argument */ - String HIDDEN_ARG_PREFIX = "-"; + char HIDDEN_ARG_PREFIX = '-'; /** * Gets the current argument and advances to the next argument @@ -79,6 +81,16 @@ public interface CommandPackage extends Iterator { */ boolean findHiddenArgument(String argument); + /** + * Finds a certain hidden argument such as "arg=value" and yields the associated value. + * This is akin to using {@link #findHiddenArgument(String)} except the argument is a wildcard + * wih respect to the specified value. + * + * @param argPrefix the first part of the hidden argument, i.e. "arg" in "arg=value" + * @return the value if it exists + */ + @Nullable String findHiddenArgumentSpecifiedValue(String argPrefix); + /** * Concatenates the current argument and all remaining arguments. This would * be equivalent to joining all calls to {@link #next()}, separating with spaces, diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/ListCommands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/ListCommands.java index 26f5ae749..98cf20b61 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/ListCommands.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/ListCommands.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -29,12 +29,14 @@ import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.api.select.PunishmentSelector; import space.arim.libertybans.api.select.SelectionBase; import space.arim.libertybans.api.select.SelectionBuilderBase; import space.arim.libertybans.api.select.SelectionOrderBuilder; import space.arim.libertybans.api.select.SelectionPredicate; import space.arim.libertybans.core.commands.extra.AsCompositeWildcard; +import space.arim.libertybans.core.commands.extra.ParseScope; import space.arim.libertybans.core.commands.extra.ParseVictim; import space.arim.libertybans.core.commands.extra.TabCompletion; import space.arim.libertybans.core.config.InternalFormatter; @@ -187,8 +189,15 @@ private ReactionStage parsePageThenExecute(SelectionBuilderBase sele sender().sendMessage(section.usage()); return completedFuture(null); } + SelectionPredicate scopeSelection = argumentParser().parseScope( + sender(), command(), ParseScope.selectionPredicate() + ); + if (scopeSelection == null) { + return completedFuture(null); + } int perPage = section.perPage(); SelectionBase selection = selectionBuilder + .scopes(scopeSelection) .skipFirstRetrieved(perPage * (selectedPage - 1)) .limitToRetrieve(perPage) .build(); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java index 28a646e21..69de3e27f 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java @@ -25,7 +25,9 @@ import space.arim.libertybans.api.event.BasePunishEvent; import space.arim.libertybans.api.punish.DraftPunishment; import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.commands.extra.DurationParser; +import space.arim.libertybans.core.commands.extra.ParseScope; import space.arim.libertybans.core.commands.extra.ReasonsConfig; import space.arim.libertybans.core.commands.extra.TabCompletion; import space.arim.libertybans.core.config.AdditionAssistant; @@ -111,6 +113,13 @@ public String exemptionCategory() { @Override public @Nullable DraftPunishment buildDraftSanction(Victim victim, Duration duration, String targetArg) { + + ServerScope serverScope = argumentParser().parseScope( + sender, command, ParseScope.fallbackToDefaultPunishingScope() + ); + if (serverScope == null) { + return null; + } String reason; if (command.hasNext()) { reason = command.allRemaining(); @@ -132,6 +141,7 @@ public String exemptionCategory() { .operator(sender.getOperator()) .reason(reason) .duration(duration) + .scope(serverScope) .build(); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/StringCommandPackage.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/StringCommandPackage.java index a72922e5b..3774bb25a 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/StringCommandPackage.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/StringCommandPackage.java @@ -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 @@ -19,17 +19,19 @@ package space.arim.libertybans.core.commands; -import java.util.HashSet; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.HashMap; import java.util.Locale; +import java.util.Map; import java.util.NoSuchElementException; -import java.util.Set; public final class StringCommandPackage implements CommandPackage { private final String args; /** Position never refers to a hidden command argument */ private transient int position; - private final Set hiddenArguments = new HashSet<>(); + private final Map hiddenArguments = new HashMap<>(); private StringCommandPackage(String args) { this.args = args; @@ -73,14 +75,19 @@ private void consumeHiddenArguments() { if (position == args.length()) { return; } - if (args.charAt(position) != '-') { + if (args.charAt(position) != HIDDEN_ARG_PREFIX) { return; } // Position on the first character of the hidden argument position++; // Collect the hidden argument as if it were a plain argument String hiddenArgument = consumeCurrentArgument(); - hiddenArguments.add(hiddenArgument.toLowerCase(Locale.ROOT)); + // Then parse it and add it to our known collection + String[] hiddenArgPieces = hiddenArgument.split("=", 2); + hiddenArguments.put( + hiddenArgPieces[0].toLowerCase(Locale.ROOT), + hiddenArgPieces.length == 2 ? hiddenArgPieces[1] : null + ); // At this point we will be positioned on the next argument } } @@ -110,7 +117,12 @@ public boolean hasNext() { @Override public boolean findHiddenArgument(String argument) { - return hiddenArguments.contains(argument.toLowerCase(Locale.ROOT)); + return hiddenArguments.containsKey(argument); + } + + @Override + public @Nullable String findHiddenArgumentSpecifiedValue(String argPrefix) { + return hiddenArguments.get(argPrefix); } @Override @@ -124,7 +136,7 @@ public String allRemaining() { public CommandPackage copy() { StringCommandPackage copy = new StringCommandPackage(args); copy.position = position; - copy.hiddenArguments.addAll(hiddenArguments); + copy.hiddenArguments.putAll(hiddenArguments); return copy; } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ArgumentParser.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ArgumentParser.java index 40253fe32..480e639b6 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ArgumentParser.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ArgumentParser.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -19,8 +19,10 @@ package space.arim.libertybans.core.commands.extra; +import org.checkerframework.checker.nullness.qual.Nullable; import space.arim.libertybans.api.Operator; import space.arim.libertybans.api.Victim; +import space.arim.libertybans.core.commands.CommandPackage; import space.arim.libertybans.core.env.CmdSender; import space.arim.libertybans.core.env.UUIDAndAddress; import space.arim.omnibus.util.concurrent.CentralisedFuture; @@ -29,12 +31,14 @@ public interface ArgumentParser { - CentralisedFuture parseOrLookupUUID(CmdSender sender, String targetArg); + CentralisedFuture<@Nullable UUID> parseOrLookupUUID(CmdSender sender, String targetArg); - CentralisedFuture parseVictim(CmdSender sender, String targetArg, ParseVictim how); + CentralisedFuture<@Nullable Victim> parseVictim(CmdSender sender, String targetArg, ParseVictim how); - CentralisedFuture parsePlayer(CmdSender sender, String targetArg); + CentralisedFuture<@Nullable UUIDAndAddress> parsePlayer(CmdSender sender, String targetArg); + + CentralisedFuture<@Nullable Operator> parseOperator(CmdSender sender, String operatorArg); + + @Nullable R parseScope(CmdSender sender, CommandPackage command, ParseScope how); - CentralisedFuture parseOperator(CmdSender sender, String operatorArg); - } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ParseScope.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ParseScope.java new file mode 100644 index 000000000..f758f9075 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ParseScope.java @@ -0,0 +1,60 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.commands.extra; + +import space.arim.libertybans.api.scope.ScopeManager; +import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.api.select.SelectionPredicate; + +public interface ParseScope { + + R explicitScope(ServerScope scope); + + R defaultValue(ScopeManager scopeManager); + + static ParseScope fallbackToDefaultPunishingScope() { + return new ParseScope<>() { + @Override + public ServerScope explicitScope(ServerScope scope) { + return scope; + } + + @Override + public ServerScope defaultValue(ScopeManager scopeManager) { + return scopeManager.defaultPunishingScope(); + } + }; + } + + static ParseScope> selectionPredicate() { + return new ParseScope<>() { + @Override + public SelectionPredicate explicitScope(ServerScope scope) { + return SelectionPredicate.matchingOnly(scope); + } + + @Override + public SelectionPredicate defaultValue(ScopeManager scopeManager) { + return SelectionPredicate.matchingAll(); + } + }; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/StandardArgumentParser.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/StandardArgumentParser.java index 48e86d493..6a3c21e82 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/StandardArgumentParser.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/StandardArgumentParser.java @@ -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 @@ -20,6 +20,7 @@ package space.arim.libertybans.core.commands.extra; import jakarta.inject.Inject; +import org.checkerframework.checker.nullness.qual.Nullable; import space.arim.libertybans.api.AddressVictim; import space.arim.libertybans.api.CompositeVictim; import space.arim.libertybans.api.ConsoleOperator; @@ -28,26 +29,36 @@ import space.arim.libertybans.api.PlayerOperator; import space.arim.libertybans.api.PlayerVictim; import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.formatter.PunishmentFormatter; +import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.commands.CommandPackage; import space.arim.libertybans.core.config.Configs; import space.arim.libertybans.core.config.MessagesConfig; import space.arim.libertybans.core.env.CmdSender; import space.arim.libertybans.core.env.UUIDAndAddress; +import space.arim.libertybans.core.scope.InternalScopeManager; import space.arim.libertybans.core.uuid.UUIDManager; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; +import java.util.Optional; import java.util.UUID; public class StandardArgumentParser implements ArgumentParser { private final FactoryOfTheFuture futuresFactory; private final Configs configs; + private final InternalScopeManager scopeManager; + private final PunishmentFormatter formatter; private final UUIDManager uuidManager; - + @Inject - public StandardArgumentParser(FactoryOfTheFuture futuresFactory, Configs configs, UUIDManager uuidManager) { + public StandardArgumentParser(FactoryOfTheFuture futuresFactory, Configs configs, InternalScopeManager scopeManager, + PunishmentFormatter formatter, UUIDManager uuidManager) { this.futuresFactory = futuresFactory; this.configs = configs; + this.scopeManager = scopeManager; + this.formatter = formatter; this.uuidManager = uuidManager; } @@ -55,12 +66,16 @@ private CentralisedFuture completedFuture(T value) { return futuresFactory.completedFuture(value); } + private MessagesConfig.All all() { + return configs.getMessagesConfig().all(); + } + private MessagesConfig.All.NotFound notFound() { - return configs.getMessagesConfig().all().notFound(); + return all().notFound(); } @Override - public CentralisedFuture parseOrLookupUUID(CmdSender sender, String targetArg) { + public CentralisedFuture<@Nullable UUID> parseOrLookupUUID(CmdSender sender, String targetArg) { return switch (targetArg.length()) { case 36 -> { UUID uuid; @@ -95,7 +110,7 @@ public CentralisedFuture parseOrLookupUUID(CmdSender sender, String target } @Override - public CentralisedFuture parseOperator(CmdSender sender, String operatorArg) { + public CentralisedFuture<@Nullable Operator> parseOperator(CmdSender sender, String operatorArg) { if (ContainsCI.containsIgnoreCase(configs.getMessagesConfig().formatting().consoleArguments(), operatorArg)) { return completedFuture(ConsoleOperator.INSTANCE); } @@ -105,7 +120,7 @@ public CentralisedFuture parseOperator(CmdSender sender, String operat } @Override - public CentralisedFuture parseVictim(CmdSender sender, String targetArg, ParseVictim how) { + public CentralisedFuture<@Nullable Victim> parseVictim(CmdSender sender, String targetArg, ParseVictim how) { NetworkAddress parsedAddress = AddressParser.parseIpv4(targetArg); if (parsedAddress != null) { return completedFuture(AddressVictim.of(parsedAddress)); @@ -132,7 +147,7 @@ public CentralisedFuture parseVictim(CmdSender sender, String targetArg, } @Override - public CentralisedFuture parsePlayer(CmdSender sender, String targetArg) { + public CentralisedFuture<@Nullable UUIDAndAddress> parsePlayer(CmdSender sender, String targetArg) { return uuidManager.lookupPlayer(targetArg).thenApply((optUuidAndAddress) -> { if (optUuidAndAddress.isEmpty()) { sender.sendMessage(notFound().player().replaceText("%TARGET%", targetArg)); @@ -142,4 +157,47 @@ public CentralisedFuture parsePlayer(CmdSender sender, String ta }); } + @Override + public @Nullable R parseScope(CmdSender sender, CommandPackage command, ParseScope how) { + + boolean requirePermissions = configs.getScopeConfig().requirePermissions(); + ServerScope explicitScope; + String specificServer, category, rawScopeInput; + + if ((specificServer = command.findHiddenArgumentSpecifiedValue("server")) != null) { + explicitScope = scopeManager.specificScope(specificServer); + + } else if ((category = command.findHiddenArgumentSpecifiedValue("category")) != null) { + explicitScope = scopeManager.category(category); + + } else if ((rawScopeInput = command.findHiddenArgumentSpecifiedValue("scope")) != null) { + Optional parsed = scopeManager.parseFrom(rawScopeInput); + if (parsed.isEmpty()) { + sender.sendMessage(all().scopes().invalid().replaceText("%SCOPE_ARG%", rawScopeInput)); + return null; + } + explicitScope = parsed.get(); + } else { + if (requirePermissions && !sender.hasPermission("libertybans.scope.default")) { + sender.sendMessage(all().scopes().noPermissionForDefault()); + return null; + } + return how.defaultValue(scopeManager); + } + if (requirePermissions) { + String permissionSuffix = scopeManager.deconstruct(explicitScope, (type, value) -> { + return switch (type) { + case GLOBAL -> "global"; + case SERVER -> "server." + value; + case CATEGORY -> "category." + value; + }; + }); + if (!sender.hasPermission("libertybans.scope." + permissionSuffix)) { + sender.sendMessage(all().scopes().noPermission().replaceText("%SCOPE%", formatter.formatScope(explicitScope))); + return null; + } + } + return how.explicitScope(explicitScope); + } + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.java index 6539cf408..cc64dd727 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.java @@ -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 @@ -38,7 +38,7 @@ import space.arim.dazzleconf.serialiser.Decomposer; import space.arim.dazzleconf.serialiser.FlexibleType; import space.arim.dazzleconf.serialiser.ValueSerialiser; -import space.arim.libertybans.core.scope.ScopeSerializer; +import space.arim.libertybans.core.scope.ConfiguredScope; final class ConfigSerialisers { @@ -51,7 +51,7 @@ static void addTo(ConfigurationOptions.Builder builder) { new ParsedDuration.Serializer(), new DateTimeFormatterSerialiser(), new ZoneIdSerialiser(), - new ScopeSerializer() + new ConfiguredScope.Serializer() ); } @@ -71,7 +71,7 @@ public Class getTargetClass() { @Override public Component deserialise(FlexibleType flexibleType) throws BadValueException { List lines = flexibleType.getList(FlexibleType::getString); - List components = new ArrayList<>(lines.size()); + List components = new ArrayList<>(2 * lines.size()); for (String line : lines) { if (!components.isEmpty()) { components.add(Component.newline()); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java b/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java index a51a78540..a8414b392 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java @@ -1,19 +1,19 @@ -/* - * LibertyBans-core - * Copyright © 2020 Anand Beh - * - * LibertyBans-core 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-core 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-core. If not, see + * along with LibertyBans. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ package space.arim.libertybans.core.config; @@ -32,7 +32,9 @@ public interface Configs extends Part { SqlConfig getSqlConfig(); ImportConfig getImportConfig(); - + + ScopeConfig getScopeConfig(); + CompletableFuture reloadConfigs(); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java b/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java index c62d23653..52f99bde6 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java @@ -465,7 +465,7 @@ public String formatDuration(Duration duration) { public String formatScope(ServerScope scope) { Objects.requireNonNull(scope, "scope"); String globalScopeDisplay = messages().formatting().globalScopeDisplay(); - return scopeManager.getServer(scope, globalScopeDisplay); + return scopeManager.display(scope, globalScopeDisplay); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java index 79271957d..791138837 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java @@ -153,27 +153,10 @@ interface Commands { interface Platforms { - @SubSection - Bukkit bukkit(); - - interface Bukkit { - - @ConfKey("kick-via-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.", - "", - "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." - }) - @DefaultBoolean(false) - boolean kickViaPluginMessaging(); - - } - @SubSection Sponge sponge(); + @ConfHeader("Related to the Sponge platform") interface Sponge { @ConfKey("register-ban-service") @@ -202,6 +185,59 @@ interface Sponge { boolean registerBanService(); } + + @ConfKey("game-servers") + @SubSection + GameServers gameServers(); + + @ConfHeader("Related to game servers such as Spigot, Paper, and Sponge") + interface GameServers { + + @ConfKey("use-plugin-messaging") + @ConfComments({ + "This option is relevant for backend servers running within a network (BungeeCord or Velocity).", + "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", + "", + "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 usePluginMessaging(); + + } + + @SubSection + Proxies proxies(); + + @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 ", + "to servers on which they are banned. The server name is obtained from the proxy API", + "", + "This option is enabled by default for full functionality. However, it increases performance usage,", + "so you may want to disable it if you do not use the scopes feature." + }) + @DefaultBoolean(true) + boolean enforceServerSwitch(); + + } } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java index 14446ea4f..b6ffe3baf 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java @@ -127,7 +127,26 @@ interface NotFound { @DefaultString("&cUnknown sub command. Displaying usage:") Component usage(); - + + @SubSection + Scopes scopes(); + + @ConfHeader("This section is only relevant if using the server scopes feature") + interface Scopes { + + @DefaultString("&cInvalid scope specified: &e%SCOPE_ARG%&c.") + ComponentText invalid(); + + @ConfKey("no-permission") + @DefaultString("&cYou may not use scope &e%SCOPE%&c.") + ComponentText noPermission(); + + @ConfKey("no-permission-for-default") + @DefaultString("&cYou may not use this command without specifying a scope.") + Component noPermissionForDefault(); + + } + } @SubSection diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ScopeConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ScopeConfig.java new file mode 100644 index 000000000..e7119bc53 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/ScopeConfig.java @@ -0,0 +1,127 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.config; + +import space.arim.dazzleconf.annote.ConfComments; +import space.arim.dazzleconf.annote.ConfDefault; +import space.arim.dazzleconf.annote.ConfHeader; +import space.arim.dazzleconf.annote.ConfKey; +import space.arim.dazzleconf.annote.SubSection; +import space.arim.libertybans.core.scope.ConfiguredScope; + +import java.util.List; + +@ConfHeader({ + "This file is for proxies like BungeeCord and Velocity. It is irrelevant for single servers.", + "It controls scope-related settings for this particular server on the network.", + "", + "Unlike other configuration files, one should NOT copy the scope.yml across multiple server instances" +}) +public interface ScopeConfig { + + @ConfKey("default-punishing-scope") + @ConfComments({ + "The default scope used to punish players", + "", + "GLOBAL - uses the global scope", + "THIS_SERVER - applies to this server only via the server name", + "PRIMARY_CATEGORY - uses the first category listed in 'categories-applicable-to-this-server'.", + "", + "If you use PRIMARY_CATEGORY but no categories are configured, a warning is printed and THIS_SERVER is used." + }) + @ConfDefault.DefaultString("GLOBAL") + DefaultPunishingScope defaultPunishingScope(); + + enum DefaultPunishingScope { + GLOBAL, + THIS_SERVER, + PRIMARY_CATEGORY + } + + @ConfKey("categories-applicable-to-this-server") + @ConfComments({ + "The scope categories applicable to this server", + "", + "For example, multiple servers might fall under the 'kitpvp' category,", + "then staff members may use '-category=kitpvp' to create punishment applying to these servers" + }) + @ConfDefault.DefaultStrings({}) + List categoriesApplicableToThisServer(); + + @ConfKey("server-name") + @SubSection + ServerName serverName(); + + @ConfHeader({ + "Controls how the name of this server is detected for use with the server scope.", + "", + "The server name should correspond to the name of the backend server as configured on the proxy.", + "The name of the proxy itself is 'proxy' by default, unless overridden." + }) + interface ServerName { + + @ConfKey("auto-detect") + @ConfComments({ + "By default, we try to detect the name of this backend server using plugin messaging.", + "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.", + "Re-enabling this option may require a restart (/libertybans restart)" + }) + @ConfDefault.DefaultBoolean(true) + boolean autoDetect(); + + @ConfKey("override-value") + @ConfComments({ + "If auto detection is disabled, this option should be set to the name of the server.", + "", + "Server names should be unique, but this is not a strict requirement.", + "If you want a scope applying to multiple servers, you should use categories instead." + }) + @ConfDefault.DefaultString("myserver") + String overrideValue(); + + @ConfKey("fallback-if-auto-detect-fails") + @ConfComments({ + "Auto detection requires a player to have logged in. But you might punish players, e.g. via console, before that.", + "By default, if auto detection has not yet occurred, the global scope will be used as a fallback.", + "The fallback scope may be configured here to something else." + }) + @ConfDefault.DefaultString("*") + ConfiguredScope fallbackIfAutoDetectFails(); + + } + + @ConfKey("require-permissions") + @ConfComments({ + "Whether to require permissions for using scopes:", + "- libertybans.scope.global, libertybans.scope.server., and libertybans.scope.category.", + " become requirements to use the relevant scopes explicitly.", + "- libertybans.scope.default must be granted to use commands without a scope argument" + }) + @ConfDefault.DefaultBoolean(false) + boolean requirePermissions(); + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/StandardConfigs.java b/bans-core/src/main/java/space/arim/libertybans/core/config/StandardConfigs.java index 6745053fb..8279c186d 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/StandardConfigs.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/StandardConfigs.java @@ -1,19 +1,19 @@ -/* - * LibertyBans-core - * Copyright © 2020 Anand Beh - * - * LibertyBans-core 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-core 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-core. If not, see + * along with LibertyBans. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ package space.arim.libertybans.core.config; @@ -47,6 +47,7 @@ public class StandardConfigs implements Configs { private final ConfigHolder messagesHolder = new ConfigHolder<>(MessagesConfig.class); private final ConfigHolder sqlHolder = new ConfigHolder<>(SqlConfig.class); private final ConfigHolder importHolder = new ConfigHolder<>(ImportConfig.class); + private final ConfigHolder scopeHolder = new ConfigHolder<>(ScopeConfig.class); @Inject public StandardConfigs(@Named("folder") Path folder) { @@ -72,7 +73,12 @@ public SqlConfig getSqlConfig() { public ImportConfig getImportConfig() { return importHolder.getConfigData(); } - + + @Override + public ScopeConfig getScopeConfig() { + return scopeHolder.getConfigData(); + } + @Override public CompletableFuture reloadConfigs() { Path langFolder = folder.resolve("lang"); @@ -90,6 +96,8 @@ public CompletableFuture reloadConfigs() { CompletableFuture reloadSql = sqlHolder.reload(folder.resolve("sql.yml")); // Reload import config CompletableFuture reloadImport = importHolder.reload(folder.resolve("import.yml")); + // Reload scope config + CompletableFuture reloadScope = scopeHolder.reload(folder.resolve("scope.yml")); // Reload messages config from specified language file CompletableFuture reloadMessages = CompletableFuture.allOf(futureLangFiles, reloadMain) @@ -100,9 +108,9 @@ public CompletableFuture reloadConfigs() { String langFileOption = mainHolder.getConfigData().langFile(); return messagesHolder.reload(langFolder.resolve("messages_" + langFileOption + ".yml")); }); - return CompletableFuture.allOf(reloadMessages, reloadSql, reloadImport).thenApply((ignore) -> { + return CompletableFuture.allOf(reloadMessages, reloadSql, reloadImport, reloadScope).thenApply((ignore) -> { ConfigResult combinedResult = ConfigResult.combinePessimistically( - reloadMain.join(), reloadMessages.join(), reloadSql.join(), reloadImport.join() + reloadMain.join(), reloadMessages.join(), reloadSql.join(), reloadImport.join(), reloadScope.join() ); return combinedResult != ConfigResult.IO_ERROR; }); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java b/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java index 15278ab99..5a0789097 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java @@ -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 @@ -185,14 +185,13 @@ private void setUsernameAndPassword() { private void setConfiguredDriver() { String jdbcUrl = getBaseUrl() + getUrlProperties(); - JdbcDriver jdbcDriver = vendor.driver(); if (config.useTraditionalJdbcUrl()) { - setDriverClassName(jdbcDriver.driverClassName()); + setDriverClassName(vendor.driver.driverClassName()); hikariConf.setJdbcUrl(jdbcUrl); } else { - hikariConf.setDataSourceClassName(jdbcDriver.dataSourceClassName()); + hikariConf.setDataSourceClassName(vendor.driver.dataSourceClassName()); hikariConf.addDataSourceProperty("url", jdbcUrl); } } @@ -288,7 +287,7 @@ private String getUrlProperties() { } }; logger.trace("Using connection properties {}", properties); - return vendor.driver().formatConnectionProperties(properties); + return vendor.driver.formatConnectionProperties(properties); } /** diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java b/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java index 47179351a..3db93e835 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java @@ -35,7 +35,7 @@ public enum Vendor { ; private final String displayName; - private final JdbcDriver driver; + final JdbcDriver driver; Vendor(String displayName, JdbcDriver driver) { this.displayName = displayName; @@ -47,14 +47,6 @@ public String toString() { return displayName; } - JdbcDriver driver() { - return driver; - } - - public boolean hasDeleteFromJoin() { - return isMySQLLike(); - } - public boolean isRemote() { return isMySQLLike() || isPostgresLike(); } @@ -151,11 +143,28 @@ public String arbitraryBinaryType() { public String alterViewStatement() { return switch (this) { - case HSQLDB, MYSQL, MARIADB -> "ALTER VIEW"; + case HSQLDB, MARIADB, MYSQL -> "ALTER VIEW"; case POSTGRES, COCKROACH -> "CREATE OR REPLACE VIEW"; }; } + public String zeroSmallintLiteral() { + return switch (this) { + case HSQLDB, POSTGRES, COCKROACH -> "CAST(0 AS SMALLINT)"; + case MARIADB, MYSQL -> "0"; + }; + } + + public String[] migrateScope() { + return switch (this) { + case MARIADB, MYSQL -> new String[] {"", ""}; + case HSQLDB, POSTGRES, COCKROACH -> new String[] { + "CAST(", + " AS CHARACTER VARYING(32))" + }; + }; + } + String getConnectionInitSql() { return switch (this) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/MigrateWithFlyway.java b/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/MigrateWithFlyway.java index 2eef9eeb2..8668aa90c 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/MigrateWithFlyway.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/MigrateWithFlyway.java @@ -43,6 +43,9 @@ public MigrateWithFlyway(DataSource dataSource, Vendor vendor) { public void migrate(JooqContext jooqContext) throws MigrationFailedException { Flyway flyway = createFlyway(new MigrationState(jooqContext)); try { + if (Boolean.getBoolean("libertybans.database.flywayrepair")) { + flyway.repair(); + } flyway.migrate(); } catch (FlywayException ex) { throw new MigrationFailedException(ex); @@ -52,7 +55,8 @@ public void migrate(JooqContext jooqContext) throws MigrationFailedException { private Flyway createFlyway(MigrationState migrationState) { var classProvider = migrationState.asClassProvider(List.of( V1__Principle.class, V16__Complete_migration_from_08x.class, - V31__Track_identifier_sequence.class, R__Set_Revision.class + V31__Track_identifier_sequence.class, V34__Scope_identifier_sequence.class, V38__Scope_migration.class, + R__Set_Revision.class )); return Flyway .configure(getClass().getClassLoader()) @@ -66,7 +70,10 @@ private Flyway createFlyway(MigrationState migrationState) { "uuidtype", vendor.uuidType(), "inettype", vendor.inetType(), "arbitrarybinarytype", vendor.arbitraryBinaryType(), - "alterviewstatement", vendor.alterViewStatement() + "alterviewstatement", vendor.alterViewStatement(), + "zerosmallintliteral", vendor.zeroSmallintLiteral(), + "migratescopestart", vendor.migrateScope()[0], + "migratescopeend", vendor.migrateScope()[1] )) .locations("classpath:database-migrations") // Override classpath scanning diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V34__Scope_identifier_sequence.java b/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V34__Scope_identifier_sequence.java new file mode 100644 index 000000000..1bf3dd757 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V34__Scope_identifier_sequence.java @@ -0,0 +1,44 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.database.flyway; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.jooq.DSLContext; +import space.arim.libertybans.core.database.sql.SequenceDefinition; + +import java.sql.Connection; +import java.sql.Statement; + +public final class V34__Scope_identifier_sequence extends BaseJavaMigration { + + @Override + public void migrate(Context flywayContext) throws Exception { + MigrationState migrationState = MigrationState.retrieveState(flywayContext); + Connection connection = flywayContext.getConnection(); + DSLContext context = migrationState.createJooqContext(connection); + + try (Statement statement = connection.createStatement()) { + SequenceDefinition.integer("scope_ids", 1) + .defineUsing(statement, context.family()); + } + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V38__Scope_migration.java b/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V38__Scope_migration.java new file mode 100644 index 000000000..c5f430085 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V38__Scope_migration.java @@ -0,0 +1,81 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.database.flyway; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.jooq.DSLContext; +import org.jooq.Field; +import org.jooq.impl.SQLDataType; +import space.arim.libertybans.core.database.jooq.BatchExecute; +import space.arim.libertybans.core.database.sql.ScopeIdSequenceValue; +import space.arim.libertybans.core.scope.SpecificServerScope; + +import java.sql.Connection; +import java.util.ArrayList; +import java.util.List; + +import static org.jooq.impl.DSL.field; +import static org.jooq.impl.DSL.name; +import static space.arim.libertybans.core.schema.tables.Punishments.PUNISHMENTS; + +public final class V38__Scope_migration extends BaseJavaMigration { + + private static final int BATCH_SIZE = 400; + + @Override + public void migrate(Context flywayContext) throws Exception { + DSLContext context; + { + MigrationState migrationState = MigrationState.retrieveState(flywayContext); + Connection connection = flywayContext.getConnection(); + context = migrationState.createJooqContext(connection); + } + + record RewriteScope(long punishmentId, int scopeId) {} + List scopesToRewrite = new ArrayList<>(); + + Field legacyScopeField = field(name("scope"), SQLDataType.VARCHAR(32)); + try (var cursor = context + .select(PUNISHMENTS.ID, legacyScopeField) + .from(PUNISHMENTS) + .where(legacyScopeField.notEqual("")) + .fetchSize(BATCH_SIZE) + .fetchLazy()) { + + for (var record : cursor) { + Integer scopeId = new ScopeIdSequenceValue(context) + .retrieveScopeIdFieldReified(new SpecificServerScope(record.value2())); + scopesToRewrite.add(new RewriteScope(record.value1(), scopeId)); + } + } + new BatchExecute( + () -> context.batch(context + .update(PUNISHMENTS) + .set(PUNISHMENTS.ID, (Long) null) + .set(PUNISHMENTS.SCOPE_ID, (Integer) null) + ), + (batch, data) -> { + return batch.bind(data.punishmentId, data.scopeId); + } + ).execute(scopesToRewrite, BATCH_SIZE); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchExecute.java b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchExecute.java new file mode 100644 index 000000000..b6c0ef43d --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchExecute.java @@ -0,0 +1,87 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.database.jooq; + +import org.jooq.BatchBindStep; + +import java.util.Objects; + +public class BatchExecute { + + private final BatchProvider batchProvider; + private final BatchAttachment batchAttachment; + + public BatchExecute(BatchProvider batchProvider, BatchAttachment batchAttachment) { + this.batchProvider = Objects.requireNonNull(batchProvider); + this.batchAttachment = Objects.requireNonNull(batchAttachment); + } + + /** + * Provides the batch object for the table into which the data will be inserted + */ + public interface BatchProvider { + + /** + * Creates the batch object + * + * @return a batch object for the target table + */ + BatchBindStep createBatch(); + } + + /** + * Function for attaching the source data to the batch object + * + * @param the source data type + */ + public interface BatchAttachment { + + /** + * Binds data + * + * @param batch the existing batch object + * @param data the data to bind + * @return the new batch object + */ + BatchBindStep attachData(BatchBindStep batch, S data); + + } + + /** + * Performs the batch execution + * + * @param source the source of the data + * @param maxBatchSize how many records to write in a single batch + */ + public void execute(Iterable source, int maxBatchSize) { + BatchBindStep batch = batchProvider.createBatch(); + for (S data : source) { + batch = batchAttachment.attachData(batch, data); + if (batch.size() == maxBatchSize) { + batch.execute(); + batch = batchProvider.createBatch(); + } + } + if (batch.size() > 0) { + batch.execute(); + } + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchTransfer.java b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchTransfer.java index 9723d0ef5..7a2919b68 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchTransfer.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchTransfer.java @@ -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 @@ -19,52 +19,19 @@ package space.arim.libertybans.core.database.jooq; -import org.jooq.BatchBindStep; import org.jooq.Cursor; -public final class BatchTransfer { +import java.util.Objects; + +public final class BatchTransfer extends BatchExecute { private final Cursor cursor; - private final BatchProvider batchProvider; - private final BatchAttachment batchAttachment; public BatchTransfer(Cursor cursor, BatchProvider batchProvider, BatchAttachment batchAttachment) { - this.cursor = cursor; - this.batchProvider = batchProvider; - this.batchAttachment = batchAttachment; - } - - /** - * Provides the batch object for the table into which the data will be inserted - */ - public interface BatchProvider { - - /** - * Creates the batch object - * - * @return a batch object for the target table - */ - BatchBindStep createBatch(); - } - - /** - * Function for attaching the cursor data to the batch object - * - * @param the record type - */ - public interface BatchAttachment { - - /** - * Binds data - * - * @param batch the existing batch object - * @param record the data to bind - * @return the new batch object - */ - BatchBindStep attachData(BatchBindStep batch, R record); - + super(batchProvider, batchAttachment); + this.cursor = Objects.requireNonNull(cursor); } /** @@ -72,19 +39,9 @@ public interface BatchAttachment { * * @param maxBatchSize how many records to transfer in a single batch */ - public void transferData(int maxBatchSize) { - BatchBindStep batch = batchProvider.createBatch(); + public void execute(int maxBatchSize) { try (cursor) { - for (R record : cursor) { - batch = batchAttachment.attachData(batch, record); - if (batch.size() == maxBatchSize) { - batch.execute(); - batch = batchProvider.createBatch(); - } - } - if (batch.size() > 0) { - batch.execute(); - } + super.execute(cursor, maxBatchSize); } } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/JooqClassloading.java b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/JooqClassloading.java index 5dffdab2a..a85ae2061 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/JooqClassloading.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/JooqClassloading.java @@ -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 @@ -26,7 +26,6 @@ import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; -import space.arim.libertybans.core.scope.ScopeImpl; import java.time.Instant; import java.util.UUID; @@ -58,12 +57,11 @@ public void preinitializeClasses() { } catch (RuntimeException ex) { // Purposefully hide the exception stacktrace to avoid unnecessary noise logger.warn("Failed to pre-initialize classes: {}", ex.getMessage()); + return; } long elapsedMillis = (System.nanoTime() - startNanos) / 1_000_000L; - if (elapsedMillis >= 250) { - String elapsedSeconds = String.format("%.2f", ((double) elapsedMillis) / 1000D); - logger.info("Finished pre-initializing classes in {} seconds", elapsedSeconds); - } + String elapsedSeconds = String.format("%.2f", ((double) elapsedMillis) / 1000D); + logger.info("Finished pre-initializing classes in {} seconds", elapsedSeconds); } private void renderDummyQueries() { @@ -78,11 +76,12 @@ private void renderDummyQueries() { .columns( PUNISHMENTS.ID, PUNISHMENTS.TYPE, PUNISHMENTS.OPERATOR, PUNISHMENTS.REASON, - PUNISHMENTS.SCOPE, PUNISHMENTS.START, PUNISHMENTS.END) + PUNISHMENTS.START, PUNISHMENTS.END + ) .values( 0L, PunishmentType.BAN, ConsoleOperator.INSTANCE, "", - ScopeImpl.GLOBAL, Instant.EPOCH, Instant.MAX + Instant.EPOCH, Instant.MAX ) .getSQL(); context diff --git a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/ForcedEmptyScopeValidator.java b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/ScopeTypeConverter.java similarity index 62% rename from bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/ForcedEmptyScopeValidator.java rename to bans-core/src/main/java/space/arim/libertybans/core/database/jooq/ScopeTypeConverter.java index edb9d06ea..f68f45405 100644 --- a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/ForcedEmptyScopeValidator.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/ScopeTypeConverter.java @@ -17,19 +17,15 @@ * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.core.addon.layouts; +package space.arim.libertybans.core.database.jooq; -import space.arim.dazzleconf.error.BadValueException; -import space.arim.dazzleconf.validator.ValueValidator; +import org.jetbrains.annotations.NotNull; +import space.arim.libertybans.core.scope.ScopeType; + +public final class ScopeTypeConverter extends OrdinalEnumConverter { -public class ForcedEmptyScopeValidator implements ValueValidator { @Override - public void validate(String key, Object value) throws BadValueException { - if (!((String) value).isEmpty()) { - throw new BadValueException.Builder() - .key(key) - .message("At the moment, scopes are not supported in punishment layouts") - .build(); - } + public @NotNull Class toType() { + return ScopeType.class; } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ApplicableViewFields.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ApplicableViewFields.java index 3902c6542..2933361c4 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ApplicableViewFields.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ApplicableViewFields.java @@ -21,31 +21,28 @@ import org.jooq.Field; import org.jooq.Record; -import org.jooq.Record13; +import org.jooq.Record14; import org.jooq.Table; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.Operator; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.punish.EscalationTrack; -import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.scope.ScopeType; import java.time.Instant; import java.util.Objects; import java.util.UUID; -public final class ApplicableViewFields> implements PunishmentFields { - - private final Table applicableView; - private final R fieldSupplier; + Operator, String, String, Instant, Instant, + UUID, NetworkAddress, EscalationTrack, ScopeType + >>(Table applicableView, R fieldSupplier) implements PunishmentFields { public ApplicableViewFields(Table applicableView) { - this.applicableView = applicableView; - this.fieldSupplier = applicableView.newRecord(); + this(applicableView, applicableView.newRecord()); } @Override @@ -89,7 +86,7 @@ public Field reason() { } @Override - public Field scope() { + public Field scope() { return fieldSupplier.field8(); } @@ -108,14 +105,13 @@ public Field track() { return fieldSupplier.field13(); } + @Override + public Field scopeType() { + return fieldSupplier.field14(); + } + public Field uuid() { return Objects.requireNonNull(fieldSupplier.field11(), "uuid field does not exist"); } - @Override - public String toString() { - return "ApplicableViewFields{" + - "applicableView=" + applicableView + - '}'; - } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/MultiFieldCriterion.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/MultiFieldCriterion.java new file mode 100644 index 000000000..979836e93 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/MultiFieldCriterion.java @@ -0,0 +1,46 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.database.sql; + +import org.jooq.Condition; +import space.arim.libertybans.api.select.SelectionPredicate; + +import static org.jooq.impl.DSL.noCondition; +import static org.jooq.impl.DSL.not; + +public interface MultiFieldCriterion { + + Condition matchesValue(F value); + + default Condition buildCondition(SelectionPredicate selection) { + // Check field is accepted + Condition fieldAcceptedCondition = noCondition(); + for (F acceptedValue : selection.acceptedValues()) { + fieldAcceptedCondition = fieldAcceptedCondition.or(matchesValue(acceptedValue)); + } + // Check field is not rejected + Condition fieldNotRejectedCondition = noCondition(); + for (F rejectedValue : selection.rejectedValues()) { + fieldNotRejectedCondition = fieldNotRejectedCondition.and(not(matchesValue(rejectedValue))); + } + return fieldAcceptedCondition.and(fieldNotRejectedCondition); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/PunishmentFields.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/PunishmentFields.java index fd2f1e86c..4df993d58 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/PunishmentFields.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/PunishmentFields.java @@ -26,12 +26,12 @@ import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.punish.EscalationTrack; -import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.scope.ScopeType; import java.time.Instant; import java.util.UUID; -public interface PunishmentFields extends VictimFields { +public interface PunishmentFields extends VictimFields, ScopeFields { Field id(); @@ -41,8 +41,6 @@ public interface PunishmentFields extends VictimFields { Field reason(); - Field scope(); - Field start(); Field end(); @@ -52,14 +50,14 @@ public interface PunishmentFields extends VictimFields { default PunishmentFields withNewTable(Table newTable) { record ModifiedTable(Field id, Field type, Field victimType, Field victimUuid, Field victimAddress, - Field operator, Field reason, Field scope, - Field start, Field end, Field track, + Field operator, Field reason, Field scope, + Field start, Field end, Field track, Field scopeType, Table table) implements PunishmentFields { } return new ModifiedTable( id(), type(), victimType(), victimUuid(), victimAddress(), - operator(), reason(), scope(), start(), end(), track(), newTable + operator(), reason(), scope(), start(), end(), track(), scopeType(), newTable ); - }; + } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeCondition.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeCondition.java new file mode 100644 index 000000000..c503fed8e --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeCondition.java @@ -0,0 +1,43 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.database.sql; + +import org.jooq.Condition; +import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.scope.InternalScopeManager; + +import static org.jooq.impl.DSL.noCondition; + +public record ScopeCondition(ScopeFields scopeFields, InternalScopeManager scopeManager) + implements MultiFieldCriterion { + + @Override + public Condition matchesValue(ServerScope scope) { + return scopeManager.deconstruct(scope, (type, value) -> { + Condition typeMatches = scopeFields.scopeType().eq(type); + Condition valueMatches = switch (type) { + case GLOBAL -> noCondition(); + case SERVER, CATEGORY -> scopeFields.scope().eq(value); + }; + return typeMatches.and(valueMatches); + }); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeFields.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeFields.java new file mode 100644 index 000000000..ee5a672f1 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeFields.java @@ -0,0 +1,31 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.database.sql; + +import org.jooq.Field; +import space.arim.libertybans.core.scope.ScopeType; + +public interface ScopeFields extends TableFieldAccessor { + + Field scope(); + + Field scopeType(); + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeIdSequenceValue.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeIdSequenceValue.java new file mode 100644 index 000000000..2eb72f4f7 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeIdSequenceValue.java @@ -0,0 +1,71 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.database.sql; + +import org.jooq.DSLContext; +import org.jooq.Field; +import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.scope.ScopeParsing; +import space.arim.libertybans.core.scope.ScopeType; + +import static org.jooq.impl.DSL.castNull; +import static org.jooq.impl.DSL.val; +import static space.arim.libertybans.core.schema.Sequences.LIBERTYBANS_SCOPE_IDS; +import static space.arim.libertybans.core.schema.tables.Scopes.SCOPES; + +public final class ScopeIdSequenceValue extends SequenceValue { + + public ScopeIdSequenceValue(DSLContext context) { + super(context, LIBERTYBANS_SCOPE_IDS); + } + + private RetrieveOrGenerate retrieveOrGenerate(ScopeType type, String value) { + return new RetrieveOrGenerate( + SCOPES, SCOPES.ID, + SCOPES.TYPE.eq(type).and(SCOPES.VALUE.eq(value)), + (newId) -> { + context + .insertInto(SCOPES) + .columns(SCOPES.ID, SCOPES.TYPE, SCOPES.VALUE) + .values(newId, val(type), val(value)) + .execute(); + } + ); + } + + public Field retrieveScopeId(ServerScope scope) { + return new ScopeParsing().deconstruct(scope, (type, value) -> { + if (type == ScopeType.GLOBAL) { + return castNull(Integer.class); + } + return retrieveOrGenerate(type, value).execute(); + }); + } + + public Integer retrieveScopeIdFieldReified(ServerScope scope) { + return new ScopeParsing().deconstruct(scope, (type, value) -> { + if (type == ScopeType.GLOBAL) { + return null; + } + return retrieveOrGenerate(type, value).executeReified(); + }); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SequenceValue.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SequenceValue.java index 31a1647b3..c53cab875 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SequenceValue.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SequenceValue.java @@ -34,10 +34,12 @@ public class SequenceValue { + final DSLContext context; private final Sequence sequence; private Field lastValueForMySQL; - public SequenceValue(Sequence sequence) { + public SequenceValue(DSLContext context, Sequence sequence) { + this.context = context; this.sequence = Objects.requireNonNull(sequence, "sequence"); } @@ -49,7 +51,7 @@ private Field emulationTableValueField() { return DSL.field("value", sequence.getDataType()); } - public Field nextValue(DSLContext context) { + public Field nextValue() { if (context.family() == SQLDialect.MYSQL) { Table emulationTable = emulationTable(); Field valueField = emulationTableValueField(); @@ -68,7 +70,7 @@ public Field nextValue(DSLContext context) { } } - public Field lastValueInSession(DSLContext context) { + public Field lastValueInSession() { if (context.family() == SQLDialect.MYSQL) { if (lastValueForMySQL == null) { throw new IllegalStateException("For MySQL, a value must be previously generated " + @@ -80,7 +82,7 @@ public Field lastValueInSession(DSLContext context) { } } - public void setValue(DSLContext context, R value) { + public void setValue(R value) { switch (context.family()) { case MYSQL -> context .update(emulationTable()) @@ -100,19 +102,46 @@ public void setValue(DSLContext context, R value) { } } - Field retrieveOrGenerateMatchingRow(DSLContext context, - Table table, Field sequenceValueField, - Condition matchExisting, Consumer> insertNew) { - R existingId = context - .select(sequenceValueField) - .from(table) - .where(matchExisting) - .fetchOne(sequenceValueField); - if (existingId != null) { - return val(existingId); + class RetrieveOrGenerate { + + private final Table table; + private final Field sequenceValueField; + private final Condition matchExisting; + private final Consumer> insertNew; + + RetrieveOrGenerate(Table table, Field sequenceValueField, + Condition matchExisting, Consumer> insertNew) { + this.table = table; + this.sequenceValueField = sequenceValueField; + this.matchExisting = matchExisting; + this.insertNew = insertNew; + } + + Field execute() { + R existingId = context + .select(sequenceValueField) + .from(table) + .where(matchExisting) + .fetchOne(sequenceValueField); + if (existingId != null) { + return val(existingId); + } + insertNew.accept(nextValue()); + return lastValueInSession(); + } + + R executeReified() { + R existingId = context + .select(sequenceValueField) + .from(table) + .where(matchExisting) + .fetchOne(sequenceValueField); + if (existingId != null) { + return existingId; + } + insertNew.accept(nextValue()); + return context.select(lastValueInSession()).fetchSingle(sequenceValueField); } - insertNew.accept(nextValue(context)); - return lastValueInSession(context); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SimpleViewFields.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SimpleViewFields.java index 9a57ac46b..49a1d8e23 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SimpleViewFields.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SimpleViewFields.java @@ -21,30 +21,26 @@ import org.jooq.Field; import org.jooq.Record; -import org.jooq.Record11; +import org.jooq.Record12; import org.jooq.Table; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.Operator; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.punish.EscalationTrack; -import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.scope.ScopeType; import java.time.Instant; import java.util.UUID; -public final class SimpleViewFields> implements PunishmentFields { - - private final Table simpleView; - private final R fieldSupplier; + Operator, String, String, Instant, Instant, EscalationTrack, ScopeType + >>(Table simpleView, R fieldSupplier) implements PunishmentFields { public SimpleViewFields(Table simpleView) { - this.simpleView = simpleView; - this.fieldSupplier = simpleView.newRecord(); + this(simpleView, simpleView.newRecord()); } @Override @@ -88,7 +84,7 @@ public Field reason() { } @Override - public Field scope() { + public Field scope() { return fieldSupplier.field8(); } @@ -108,9 +104,8 @@ public Field track() { } @Override - public String toString() { - return "SimpleViewFields{" + - "simpleView=" + simpleView + - '}'; + public Field scopeType() { + return fieldSupplier.field12(); } + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TableForType.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TableForType.java index b05a7c94c..7102afd21 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TableForType.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TableForType.java @@ -19,8 +19,8 @@ package space.arim.libertybans.core.database.sql; -import org.jooq.Record11; -import org.jooq.Record13; +import org.jooq.Record12; +import org.jooq.Record14; import org.jooq.Record2; import org.jooq.Table; import space.arim.libertybans.api.NetworkAddress; @@ -28,7 +28,6 @@ import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.punish.EscalationTrack; -import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.schema.tables.ApplicableBans; import space.arim.libertybans.core.schema.tables.ApplicableMutes; import space.arim.libertybans.core.schema.tables.ApplicableWarns; @@ -38,6 +37,7 @@ import space.arim.libertybans.core.schema.tables.SimpleMutes; import space.arim.libertybans.core.schema.tables.SimpleWarns; import space.arim.libertybans.core.schema.tables.Warns; +import space.arim.libertybans.core.scope.ScopeType; import java.time.Instant; import java.util.Objects; @@ -64,9 +64,9 @@ private Table> dataTable0() { }; } - public SimpleViewFields> simpleView() { + public SimpleViewFields> simpleView() { var view = switch (type) { case BAN -> SimpleBans.SIMPLE_BANS; case MUTE -> SimpleMutes.SIMPLE_MUTES; @@ -76,11 +76,11 @@ Operator, String, ServerScope, Instant, Instant, EscalationTrack>> simpleView() return new SimpleViewFields<>(view); } - public ApplicableViewFields> applicableView() { + public ApplicableViewFields> applicableView() { var view = switch (type) { case BAN -> ApplicableBans.APPLICABLE_BANS; case MUTE -> ApplicableMutes.APPLICABLE_MUTES; diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TrackIdSequenceValue.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TrackIdSequenceValue.java index c5711845c..bdcbdf415 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TrackIdSequenceValue.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TrackIdSequenceValue.java @@ -30,30 +30,26 @@ public final class TrackIdSequenceValue extends SequenceValue { - public TrackIdSequenceValue() { - super(LIBERTYBANS_TRACK_IDS); + public TrackIdSequenceValue(DSLContext context) { + super(context, LIBERTYBANS_TRACK_IDS); } - public Field retrieveTrackIdField(DSLContext context, EscalationTrack escalationTrack) { + public Field retrieveTrackId(EscalationTrack escalationTrack) { if (escalationTrack == null) { return castNull(Integer.class); } - return retrieveOrGenerateMatchingRow( - context, TRACKS, TRACKS.ID, + return new RetrieveOrGenerate( + TRACKS, TRACKS.ID, TRACKS.NAMESPACE.eq(escalationTrack.getNamespace()) .and(TRACKS.VALUE.eq(escalationTrack.getValue())), (newId) -> { context .insertInto(TRACKS) .columns(TRACKS.ID, TRACKS.NAMESPACE, TRACKS.VALUE) - .values( - newId, - val(escalationTrack.getNamespace()), - val(escalationTrack.getValue()) - ) + .values(newId, val(escalationTrack.getNamespace()), val(escalationTrack.getValue())) .execute(); } - ); + ).execute(); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimCondition.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimCondition.java index 6245b7516..6e12141d3 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimCondition.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimCondition.java @@ -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 @@ -32,7 +32,7 @@ import static space.arim.libertybans.api.CompositeVictim.WILDCARD_ADDRESS; import static space.arim.libertybans.api.CompositeVictim.WILDCARD_UUID; -public final class VictimCondition { +public final class VictimCondition implements MultiFieldCriterion { private final VictimFields fields; @@ -79,7 +79,8 @@ public Condition matchesVictim(VictimData victim) { return fields.victimType().eq(inline(victim.type())).and(matchesData); } - public Condition matchesVictim(Victim victim) { + @Override + public Condition matchesValue(Victim victim) { return matchesVictim(new SerializedVictim(victim)); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimIdSequenceValue.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimIdSequenceValue.java index 8b029beff..9aeb8e54c 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimIdSequenceValue.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimIdSequenceValue.java @@ -29,14 +29,14 @@ public final class VictimIdSequenceValue extends SequenceValue { - public VictimIdSequenceValue() { - super(LIBERTYBANS_VICTIM_IDS); + public VictimIdSequenceValue(DSLContext context) { + super(context, LIBERTYBANS_VICTIM_IDS); } - public Field retrieveVictimIdField(DSLContext context, Victim victim) { + public Field retrieveVictimId(Victim victim) { VictimData victimData = FixedVictimData.from(new SerializedVictim(victim)); - return retrieveOrGenerateMatchingRow( - context, VICTIMS, VICTIMS.ID, + return new RetrieveOrGenerate( + VICTIMS, VICTIMS.ID, new VictimCondition(new VictimTableFields()).matchesVictim(victimData), (newId) -> { context @@ -50,7 +50,7 @@ public Field retrieveVictimIdField(DSLContext context, Victim victim) { ) .execute(); } - ); + ).execute(); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java b/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java index 1e06e6b68..4b08306a6 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java @@ -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; @@ -36,6 +42,8 @@ public abstract class AbstractEnvEnforcer

implements EnvEnforcer

{ private final Interlocutor interlocutor; private final AudienceRepresenter audienceRepresenter; + private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); + protected AbstractEnvEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter formatter, Interlocutor interlocutor, AudienceRepresenter audienceRepresenter) { this.futuresFactory = Objects.requireNonNull(futuresFactory, "futuresFactory"); @@ -83,10 +91,30 @@ private CentralisedFuture sendToThoseWithPermissionNoPrefix(String permiss } }; } - return doForAllPlayers(callback); + return doForAllPlayers((players) -> players.forEach(callback)); + } + + @Override + public final void sendPluginMessage(P player, PluginMessage 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 doForAllPlayers(Consumer

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 boolean sendPluginMessageIfListening(P player, PluginMessage pluginMessage, D data); @Override public final void sendMessageNoPrefix(P player, ComponentLike message) { @@ -95,10 +123,16 @@ public final void sendMessageNoPrefix(P player, ComponentLike message) { @Override public final CentralisedFuture enforceMatcher(TargetMatcher

matcher) { - return doForAllPlayers((player) -> { - if (matcher.matches(getUniqueIdFor(player), getAddressFor(player))) { - matcher.callback().accept(player); + return doForAllPlayers((players) -> { + List

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()); }); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java index c899b1bba..eabae3a9f 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java @@ -20,6 +20,7 @@ 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; @@ -27,6 +28,7 @@ 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; /** @@ -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 sendToThoseWithPermission(String permission, ComponentLike message); @@ -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 doForPlayerIfOnline(UUID uuid, Consumer

callback); + /** + * Completes an action for all players online + * + * @param action the action + * @return a future completed when the operation is done + */ + CentralisedFuture doForAllPlayers(Consumer> action); + /** * Kicks the given player.
*
@@ -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 + */ + void sendPluginMessage(P player, PluginMessage pluginMessage, D data); + /** * Sends a message to the given player. Does not include a prefix.
*
@@ -103,6 +123,16 @@ public interface EnvEnforcer<@PlatformPlayer P> { */ InetAddress getAddressFor(P player); + /** + * Gets the name of a player.
+ *
+ * Must be used within a callback. + * + * @param player the player + * @return the name + */ + String getNameFor(P player); + /** * Determines whether the player has a permission * diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvMessageChannel.java b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvMessageChannel.java new file mode 100644 index 000000000..b0375dada --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvMessageChannel.java @@ -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 + * 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 { + + /** + * 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 the type of the handler + */ + H createHandler(Consumer acceptor, PluginMessage pluginMessage); + + static void parameterize(EnvMessageChannel messageChannel, Consumer> action) { + action.accept(messageChannel); + } + + final class NoOp implements EnvMessageChannel { + + @Override + public void installHandler(Void handler) { + + } + + @Override + public void uninstallHandler(Void handler) { + + } + + @Override + public Void createHandler(Consumer acceptor, PluginMessage pluginMessage) { + return null; + } + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvServerNameDetection.java b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvServerNameDetection.java new file mode 100644 index 000000000..13efe9ada --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvServerNameDetection.java @@ -0,0 +1,31 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.env; + +import space.arim.libertybans.core.scope.InternalScopeManager; + +public interface EnvServerNameDetection { + + /** + * Detects the server name, if immediately known, for use with the scopes feature + */ + void detectName(InternalScopeManager scopeManager); + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java index ce0530186..7a5f85d0c 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java @@ -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 @@ -19,29 +19,34 @@ package space.arim.libertybans.core.env; -import java.util.List; -import java.util.Set; - import jakarta.inject.Inject; import jakarta.inject.Singleton; - import space.arim.libertybans.bootstrap.StartupException; import space.arim.libertybans.core.Part; import space.arim.libertybans.core.config.Configs; +import space.arim.libertybans.core.scope.InternalScopeManager; + +import java.util.List; +import java.util.Set; @Singleton public final class EnvironmentManager implements Part { private final Environment environment; private final Configs configs; + private final EnvServerNameDetection serverNameDetection; + private final InternalScopeManager scopeManager; private Set listeners; private PlatformListener[] commandAliases; @Inject - public EnvironmentManager(Environment environment, Configs configs) { + public EnvironmentManager(Environment environment, Configs configs, + EnvServerNameDetection serverNameDetection, InternalScopeManager scopeManager) { this.environment = environment; this.configs = configs; + this.serverNameDetection = serverNameDetection; + this.scopeManager = scopeManager; } public Object platformAccess() { @@ -68,6 +73,7 @@ private void registerAliases() { public void startup() { registerListeners(); registerAliases(); + serverNameDetection.detectName(scopeManager); } @Override @@ -82,6 +88,7 @@ public void shutdown() { for (PlatformListener commandAlias : commandAliases) { commandAlias.unregister(); } + scopeManager.clearDetectedServerName(); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/InstanceType.java b/bans-core/src/main/java/space/arim/libertybans/core/env/InstanceType.java new file mode 100644 index 000000000..3f541d830 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/InstanceType.java @@ -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 + * 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 +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/ParallelisedListener.java b/bans-core/src/main/java/space/arim/libertybans/core/env/ParallelisedListener.java index a7586fa01..55748dd5b 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/ParallelisedListener.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/ParallelisedListener.java @@ -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 @@ -63,7 +63,7 @@ protected final void begin(E event, CentralisedFuture computation) { } protected final void debugPrematurelyDenied(E event) { - logger.debug("Event {} is already blocked", event); + logger.trace("Event {} is already blocked", event); } protected final void absentFutureHandler(E event) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/PluginMessageAsBytes.java b/bans-core/src/main/java/space/arim/libertybans/core/env/PluginMessageAsBytes.java new file mode 100644 index 000000000..390ad6e82 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/PluginMessageAsBytes.java @@ -0,0 +1,73 @@ +/* + * 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 + * 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 space.arim.libertybans.core.env.message.PluginMessageInput; +import space.arim.libertybans.core.env.message.PluginMessageOutput; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Optional; + +public record PluginMessageAsBytes(PluginMessage pluginMessage) { + + public byte[] generateBytes(D data) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutput = new DataOutputStream(outputStream)) { + + pluginMessage.writeTo(data, new DataOutputAsOutput(dataOutput)); + return outputStream.toByteArray(); + + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + public Optional readBytes(byte[] data) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data); + DataInputStream dataInput = new DataInputStream(inputStream)) { + + return pluginMessage.readFrom(new DataInputAsInput(dataInput)); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + record DataOutputAsOutput(DataOutput dataOutput) implements PluginMessageOutput { + @Override + public void writeUTF(String utf) throws IOException { + dataOutput.writeUTF(utf); + } + } + + record DataInputAsInput(DataInput dataInput) implements PluginMessageInput { + @Override + public String readUTF() throws IOException { + return dataInput.readUTF(); + } + } +} diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ChannelRegistration.java b/bans-core/src/main/java/space/arim/libertybans/core/env/message/GetServer.java similarity index 56% rename from bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ChannelRegistration.java rename to bans-core/src/main/java/space/arim/libertybans/core/env/message/GetServer.java index 4182daf6e..aa287e26d 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ChannelRegistration.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/message/GetServer.java @@ -17,31 +17,26 @@ * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.env.spigot; +package space.arim.libertybans.core.env.message; -import jakarta.inject.Inject; -import org.bukkit.plugin.Plugin; -import space.arim.libertybans.core.env.PlatformListener; +import java.io.IOException; -public final class ChannelRegistration implements PlatformListener { +public final class GetServer implements PluginMessage { - private final Plugin plugin; - - static final String BUNGEE_CHANNEL = "BungeeCord"; - - @Inject - public ChannelRegistration(Plugin plugin) { - this.plugin = plugin; + @Override + public String subchannelName() { + return "GetServer"; } @Override - public void register() { - plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, BUNGEE_CHANNEL); + public void writeData(Void data, PluginMessageOutput output) throws IOException { } @Override - public void unregister() { - plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, BUNGEE_CHANNEL); + public Response readResponse(PluginMessageInput input) throws IOException { + return new Response(input.readUTF()); } + public record Response(String server) { } + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/message/KickPlayer.java b/bans-core/src/main/java/space/arim/libertybans/core/env/message/KickPlayer.java new file mode 100644 index 000000000..18e2b04a8 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/message/KickPlayer.java @@ -0,0 +1,47 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.env.message; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.io.IOException; + +public final class KickPlayer implements PluginMessage { + + @Override + public String subchannelName() { + return "KickPlayer"; + } + + @Override + public void writeData(Data data, PluginMessageOutput output) throws IOException { + output.writeUTF(data.playerName); + output.writeUTF(LegacyComponentSerializer.legacySection().serialize(data.message)); + } + + @Override + public Void readResponse(PluginMessageInput input) throws IOException { + return null; + } + + public record Data(String playerName, Component message) {} + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessage.java b/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessage.java new file mode 100644 index 000000000..c1a2dd949 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessage.java @@ -0,0 +1,56 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.env.message; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Optional; + +public interface PluginMessage { + + String subchannelName(); + + void writeData(D data, PluginMessageOutput output) throws IOException; + + R readResponse(PluginMessageInput input) throws IOException; + + default void writeTo(D data, PluginMessageOutput output) { + try { + output.writeUTF(subchannelName()); + writeData(data, output); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + default Optional readFrom(PluginMessageInput input) { + try { + String subchannel = input.readUTF(); + if (!subchannel.equals(subchannelName())) { + return Optional.empty(); + } + return Optional.of(readResponse(input)); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageInput.java b/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageInput.java new file mode 100644 index 000000000..79872b547 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageInput.java @@ -0,0 +1,28 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.env.message; + +import java.io.IOException; + +public interface PluginMessageInput { + + String readUTF() throws IOException; + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageOutput.java b/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageOutput.java new file mode 100644 index 000000000..269b98877 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageOutput.java @@ -0,0 +1,28 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.env.message; + +import java.io.IOException; + +public interface PluginMessageOutput { + + void writeUTF(String utf) throws IOException; + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/importing/SelfImportProcess.java b/bans-core/src/main/java/space/arim/libertybans/core/importing/SelfImportProcess.java index 3abb024c8..bd0daa01b 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/importing/SelfImportProcess.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/importing/SelfImportProcess.java @@ -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 @@ -156,7 +156,7 @@ private void transferTable(Table table) { } return batch.bind(bindValues); } - ).transferData(maxBatchSize); + ).execute(maxBatchSize); } private void updateSequences() { @@ -165,13 +165,13 @@ private void updateSequences() { .select(DSL.max(PUNISHMENTS.ID).plus(1)) .from(PUNISHMENTS) .fetchSingle().value1(); - new SequenceValue<>(LIBERTYBANS_PUNISHMENT_IDS).setValue(target, nextPunishmentId); + new SequenceValue<>(target, LIBERTYBANS_PUNISHMENT_IDS).setValue(nextPunishmentId); int nextVictimId = target .select(DSL.max(VICTIMS.ID).plus(1)) .from(VICTIMS) .fetchSingle().value1(); - new SequenceValue<>(LIBERTYBANS_VICTIM_IDS).setValue(target, nextVictimId); + new SequenceValue<>(target, LIBERTYBANS_VICTIM_IDS).setValue(nextVictimId); } } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java index 8d869d259..d7105e7c4 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java @@ -63,8 +63,7 @@ public DraftPunishmentBuilder reason(String reason) { @Override public DraftPunishmentBuilder scope(ServerScope scope) { - enactor.scopeManager().checkScope(scope); - this.scope = scope; + this.scope = enactor.scopeManager().checkScope(scope); return this; } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/Enaction.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enaction.java index 5307a3d79..80c5b5141 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/Enaction.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enaction.java @@ -28,6 +28,7 @@ import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.database.execute.Transaction; +import space.arim.libertybans.core.database.sql.ScopeIdSequenceValue; import space.arim.libertybans.core.database.sql.SequenceValue; import space.arim.libertybans.core.database.sql.TableForType; import space.arim.libertybans.core.database.sql.TrackIdSequenceValue; @@ -84,25 +85,27 @@ private Punishment enact(PunishmentCreator creator, DSLContext context, Transaction transaction, boolean active) { MiscUtil.checkNoCompositeVictimWildcards(victim); - Field escalationTrackId = new TrackIdSequenceValue().retrieveTrackIdField(context, escalationTrack); + Field escalationTrackId = new TrackIdSequenceValue(context).retrieveTrackId(escalationTrack); + Field scopeId = new ScopeIdSequenceValue(context).retrieveScopeId(scope); - SequenceValue punishmentIdSequence = new SequenceValue<>(LIBERTYBANS_PUNISHMENT_IDS); + SequenceValue punishmentIdSequence = new SequenceValue<>(context, LIBERTYBANS_PUNISHMENT_IDS); context .insertInto(PUNISHMENTS) .columns( PUNISHMENTS.ID, PUNISHMENTS.TYPE, PUNISHMENTS.OPERATOR, PUNISHMENTS.REASON, - PUNISHMENTS.SCOPE, PUNISHMENTS.START, PUNISHMENTS.END, PUNISHMENTS.TRACK + PUNISHMENTS.SCOPE, PUNISHMENTS.START, PUNISHMENTS.END, + PUNISHMENTS.TRACK, PUNISHMENTS.SCOPE_ID ) .values( - punishmentIdSequence.nextValue(context), val(type, PUNISHMENTS.TYPE), + punishmentIdSequence.nextValue(), val(type, PUNISHMENTS.TYPE), val(operator, PUNISHMENTS.OPERATOR), val(reason, PUNISHMENTS.REASON), - val(scope, PUNISHMENTS.SCOPE), val(start, PUNISHMENTS.START), val(end, PUNISHMENTS.END), - escalationTrackId + val("", PUNISHMENTS.SCOPE), val(start, PUNISHMENTS.START), val(end, PUNISHMENTS.END), + escalationTrackId, scopeId ) .execute(); - Field punishmentIdField = punishmentIdSequence.lastValueInSession(context); - Field victimIdField = new VictimIdSequenceValue().retrieveVictimIdField(context, victim); + Field punishmentIdField = punishmentIdSequence.lastValueInSession(); + Field victimIdField = new VictimIdSequenceValue(context).retrieveVictimId(victim); if (active && type != PunishmentType.KICK) { var dataTable = new TableForType(type).dataTable(); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java index 9daf81904..650be8c5a 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java @@ -33,6 +33,7 @@ import space.arim.libertybans.api.punish.EscalationTrack; import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.database.InternalDatabase; import space.arim.libertybans.core.database.execute.QueryExecutor; import space.arim.libertybans.core.database.execute.SQLFunction; @@ -133,13 +134,14 @@ CentralisedFuture calculatePunishment(CalculablePunishment calculabl database.clearExpiredPunishments(context, type, start); } Duration duration = calculationResult.duration(); + ServerScope scope = scopeManager.checkScope(calculationResult.scope()); Instant end = duration.isZero() ? Punishment.PERMANENT_END_DATE : start.plusSeconds(duration.toSeconds()); Enaction enaction = new Enaction( new Enaction.OrderDetails( type, victim, operator, - calculationResult.reason(), calculationResult.scope(), + calculationResult.reason(), scope, start, end, escalationTrack ), creator); @@ -200,6 +202,7 @@ public CentralisedFuture queryWithRetry(int retryCount, SQLTransactionalF return new SelectionResources( futuresFactory, () -> contextualExecutor, + scopeManager, creator, time ); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/Modifier.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Modifier.java index d958c2702..61f1ab0aa 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/Modifier.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Modifier.java @@ -21,12 +21,14 @@ import jakarta.inject.Inject; import jakarta.inject.Provider; +import org.checkerframework.checker.nullness.qual.Nullable; import org.jooq.Field; import space.arim.libertybans.api.punish.EscalationTrack; import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.punish.PunishmentEditor; import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.database.InternalDatabase; +import space.arim.libertybans.core.database.sql.ScopeIdSequenceValue; import space.arim.libertybans.core.database.sql.TrackIdSequenceValue; import space.arim.libertybans.core.scope.InternalScopeManager; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; @@ -43,6 +45,7 @@ import static org.jooq.impl.DSL.inline; import static org.jooq.impl.DSL.when; import static space.arim.libertybans.core.schema.tables.Punishments.PUNISHMENTS; +import static space.arim.libertybans.core.schema.tables.Scopes.SCOPES; import static space.arim.libertybans.core.schema.tables.Tracks.TRACKS; public final class Modifier { @@ -64,16 +67,17 @@ public Modifier(FactoryOfTheFuture futuresFactory, Provider db } /** Holder for nullable escalation track */ - record EscalationTrackBox(EscalationTrack track) {} + record EscalationTrackBox(@Nullable EscalationTrack track) {} class Editor implements PunishmentEditor { private final Punishment oldInstance; - private String reason; - private ServerScope scope; - private Instant endDate; - private Duration endDateDelta; - private EscalationTrackBox escalationTrackBox; + // Null indicates unchanged; nonnull requires an update + private @Nullable String reason; + private @Nullable ServerScope scope; + private @Nullable Instant endDate; + private @Nullable Duration endDateDelta; + private @Nullable EscalationTrackBox escalationTrackBox; Editor(Punishment oldInstance) { this.oldInstance = oldInstance; @@ -86,8 +90,7 @@ public void setReason(String reason) { @Override public void setScope(ServerScope scope) { - scopeManager.checkScope(scope); - this.scope = scope; + this.scope = scopeManager.checkScope(scope); } @Override @@ -121,18 +124,18 @@ var record = context.newRecord(PUNISHMENTS); if (reason != null) { record.setReason(reason); } - if (scope != null) { - record.setScope(scope); - } if (endDate != null) { record.setEnd(endDate); } - Map, Field> furtherModifications = new HashMap<>(3, 0.99f); + Map, Field> furtherModifications = new HashMap<>(4, 0.99f); if (escalationTrackBox != null) { - Field newTrack = new TrackIdSequenceValue() - .retrieveTrackIdField(context, escalationTrackBox.track); + Field newTrack = new TrackIdSequenceValue(context).retrieveTrackId(escalationTrackBox.track); furtherModifications.put(PUNISHMENTS.TRACK, newTrack); } + if (scope != null) { + Field newScope = new ScopeIdSequenceValue(context).retrieveScopeId(scope); + furtherModifications.put(PUNISHMENTS.SCOPE, newScope); + } if (endDateDelta != null) { Field newEndDate = when( // Pass-through permanent punishments @@ -152,12 +155,15 @@ var record = context.newRecord(PUNISHMENTS); .execute(); return context .select( - PUNISHMENTS.REASON, PUNISHMENTS.SCOPE, PUNISHMENTS.END, - TRACKS.NAMESPACE, TRACKS.VALUE + PUNISHMENTS.REASON, PUNISHMENTS.END, + TRACKS.NAMESPACE, TRACKS.VALUE, + SCOPES.TYPE, SCOPES.VALUE ) .from(PUNISHMENTS) .leftJoin(TRACKS) .on(PUNISHMENTS.TRACK.eq(TRACKS.ID)) + .leftJoin(SCOPES) + .on(PUNISHMENTS.SCOPE_ID.eq(SCOPES.ID)) .where(PUNISHMENTS.ID.eq(id)) // Can return null if punishment was expunged .fetchOne(creator.punishmentMapperForModifications(oldInstance)); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/PunishmentCreator.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/PunishmentCreator.java index cd4a44d88..d43550d94 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/PunishmentCreator.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/PunishmentCreator.java @@ -21,8 +21,8 @@ import org.jooq.Record10; import org.jooq.Record11; -import org.jooq.Record5; -import org.jooq.Record9; +import org.jooq.Record12; +import org.jooq.Record6; import org.jooq.RecordMapper; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.Operator; @@ -31,6 +31,7 @@ import space.arim.libertybans.api.punish.EscalationTrack; import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.scope.ScopeType; import java.time.Instant; import java.util.UUID; @@ -41,20 +42,20 @@ Punishment createPunishment(long id, PunishmentType type, Victim victim, Operator operator, String reason, ServerScope scope, Instant start, Instant end, EscalationTrack escalationTrack); - RecordMapper, + RecordMapper, Punishment> punishmentMapper(); - RecordMapper, + RecordMapper, Punishment> punishmentMapper(long id); - RecordMapper, + RecordMapper, Punishment> punishmentMapper(long id, PunishmentType type); - RecordMapper, + RecordMapper, Punishment> punishmentMapperForModifications(Punishment oldPunishment); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/Revoker.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Revoker.java index 3c3c6d4ba..ca91dd1dc 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/Revoker.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Revoker.java @@ -24,7 +24,6 @@ import jakarta.inject.Singleton; import org.jooq.Condition; import org.jooq.DSLContext; -import org.jooq.impl.DSL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import space.arim.libertybans.api.PunishmentType; @@ -32,6 +31,7 @@ import space.arim.libertybans.api.punish.ExpunctionOrder; import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.punish.RevocationOrder; +import space.arim.libertybans.api.select.SelectionPredicate; import space.arim.libertybans.core.database.InternalDatabase; import space.arim.libertybans.core.database.sql.EndTimeCondition; import space.arim.libertybans.core.database.sql.TableForType; @@ -43,7 +43,9 @@ import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.time.Instant; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import static space.arim.libertybans.core.schema.tables.Punishments.PUNISHMENTS; import static space.arim.libertybans.core.schema.tables.SimpleActive.SIMPLE_ACTIVE; @@ -151,8 +153,8 @@ private Punishment deleteAndGetActivePunishmentByIdAndType(DSLContext context, Punishment result = context .select( SIMPLE_HISTORY.VICTIM_TYPE, SIMPLE_HISTORY.VICTIM_UUID, SIMPLE_HISTORY.VICTIM_ADDRESS, - SIMPLE_HISTORY.OPERATOR, SIMPLE_HISTORY.REASON, - SIMPLE_HISTORY.SCOPE, SIMPLE_HISTORY.START, SIMPLE_HISTORY.END, SIMPLE_HISTORY.TRACK + SIMPLE_HISTORY.OPERATOR, SIMPLE_HISTORY.REASON, SIMPLE_HISTORY.SCOPE, + SIMPLE_HISTORY.START, SIMPLE_HISTORY.END, SIMPLE_HISTORY.TRACK, SIMPLE_HISTORY.SCOPE_TYPE ) .from(SIMPLE_HISTORY) .where(SIMPLE_HISTORY.ID.eq(id)) @@ -214,14 +216,19 @@ CentralisedFuture undoAndGetPunishmentById(final long id) { } private static Condition matchesAnyVictim(VictimFields victimFields, List victims) { - Condition matchesAnyVictim = DSL.noCondition(); - VictimCondition victimCondition = new VictimCondition(victimFields); - for (Victim victim : victims) { - matchesAnyVictim = matchesAnyVictim.or( - victimCondition.matchesVictim(victim) - ); - } - return matchesAnyVictim; + LinkedHashSet victimSet = new LinkedHashSet<>(victims); + // Implement it ourself because preserving order is important + return new VictimCondition(victimFields).buildCondition(new SelectionPredicate<>() { + @Override + public Set acceptedValues() { + return victimSet; + } + + @Override + public Set rejectedValues() { + return Set.of(); + } + }); } CentralisedFuture undoPunishmentByTypeAndPossibleVictims(final PunishmentType type, diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/SecurePunishmentCreator.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/SecurePunishmentCreator.java index 83c6e65e8..74af6a728 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/SecurePunishmentCreator.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/SecurePunishmentCreator.java @@ -24,8 +24,8 @@ import jakarta.inject.Singleton; import org.jooq.Record10; import org.jooq.Record11; -import org.jooq.Record5; -import org.jooq.Record9; +import org.jooq.Record12; +import org.jooq.Record6; import org.jooq.RecordMapper; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.Operator; @@ -35,20 +35,25 @@ import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.database.sql.DeserializedVictim; +import space.arim.libertybans.core.scope.InternalScopeManager; +import space.arim.libertybans.core.scope.ScopeType; import java.time.Instant; +import java.util.Objects; import java.util.UUID; @Singleton public class SecurePunishmentCreator implements PunishmentCreator { + private final InternalScopeManager scopeManager; private final Provider revoker; private final Provider enforcement; private final Provider modifier; @Inject - public SecurePunishmentCreator(Provider revoker, Provider enforcement, - Provider modifier) { + public SecurePunishmentCreator(InternalScopeManager scopeManager, Provider revoker, + Provider enforcement, Provider modifier) { + this.scopeManager = scopeManager; this.revoker = revoker; this.enforcement = enforcement; this.modifier = modifier; @@ -73,76 +78,89 @@ public Punishment createPunishment(long id, PunishmentType type, Victim victim, } @Override - public RecordMapper, + public RecordMapper, Punishment> punishmentMapper() { return (record) -> { Victim victim = new DeserializedVictim( record.value4(), record.value5() ).victim(record.value3()); + ServerScope scope = scopeManager.deserialize( + record.value12(), record.value8() + ); return new SecurePunishment( SecurePunishmentCreator.this, record.value1(), record.value2(), // id, type victim, record.value6(), record.value7(), // victim, operator, reason - record.value8(), record.value9(), record.value10(), record.value11() // scope, start, end, track + scope, record.value9(), record.value10(), record.value11() // scope, start, end, track ); }; } @Override - public RecordMapper, + public RecordMapper, Punishment> punishmentMapper(long id) { return (record) -> { Victim victim = new DeserializedVictim( record.value3(), record.value4() ).victim(record.value2()); + ServerScope scope = scopeManager.deserialize( + record.value11(), record.value7() + ); return new SecurePunishment( SecurePunishmentCreator.this, id, /* type */ record.value1(), victim, record.value5(), record.value6(), // operator, reason - record.value7(), record.value8(), record.value9(), record.value10() // scope, start, end, track + scope, record.value8(), record.value9(), record.value10() // scope, start, end, track ); }; } @Override - public RecordMapper, + public RecordMapper, Punishment> punishmentMapper(long id, PunishmentType type) { return (record) -> { Victim victim = new DeserializedVictim( record.value2(), record.value3() ).victim(record.value1()); + ServerScope scope = scopeManager.deserialize( + record.value10(), record.value6() + ); return new SecurePunishment( SecurePunishmentCreator.this, id, type, victim, record.value4(), record.value5(), // operator, reason - record.value6(), record.value7(), record.value8(), record.value9() // scope, start, end, track + scope, record.value7(), record.value8(), record.value9() // scope, start, end, track ); }; } @Override - public RecordMapper, + public RecordMapper, Punishment> punishmentMapperForModifications(Punishment oldPunishment) { return (record) -> { EscalationTrack escalationTrack; { - String trackNamespace = record.value4(); - String trackValue = record.value5(); + String trackNamespace = record.value3(); + String trackValue = record.value4(); if (trackNamespace == null && trackValue == null) { escalationTrack = null; } else { escalationTrack = EscalationTrack.create(trackNamespace, trackValue); } } + ServerScope scope = scopeManager.deserialize( + Objects.requireNonNullElse(record.value5(), ScopeType.GLOBAL), + record.value6() + ); return new SecurePunishment( SecurePunishmentCreator.this, oldPunishment.getIdentifier(), oldPunishment.getType(), oldPunishment.getVictim(), oldPunishment.getOperator(), record.value1(), - record.value2(), oldPunishment.getStartDate(), record.value3(), + scope, oldPunishment.getStartDate(), record.value2(), escalationTrack ); }; diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/StandardLocalEnforcer.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/StandardLocalEnforcer.java index 1f81e6a56..a8413fc01 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/StandardLocalEnforcer.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/StandardLocalEnforcer.java @@ -25,6 +25,7 @@ import net.kyori.adventure.text.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import space.arim.api.env.annote.PlatformPlayer; import space.arim.api.jsonchat.adventure.util.ComponentText; import space.arim.libertybans.api.AddressVictim; import space.arim.libertybans.api.CompositeVictim; @@ -47,8 +48,10 @@ import space.arim.libertybans.core.env.AdditionalUUIDTargetMatcher; import space.arim.libertybans.core.env.EnvEnforcer; import space.arim.libertybans.core.env.ExactTargetMatcher; +import space.arim.libertybans.core.env.InstanceType; import space.arim.libertybans.core.env.TargetMatcher; import space.arim.libertybans.core.env.UUIDTargetMatcher; +import space.arim.libertybans.core.env.message.KickPlayer; import space.arim.libertybans.core.punish.permission.PunishmentPermission; import space.arim.libertybans.core.selector.cache.MuteCache; import space.arim.omnibus.util.ThisClass; @@ -63,22 +66,24 @@ import static space.arim.libertybans.core.schema.tables.StrictLinks.STRICT_LINKS; @Singleton -public final class StandardLocalEnforcer implements LocalEnforcer { +public final class StandardLocalEnforcer<@PlatformPlayer P> implements LocalEnforcer { + private final InstanceType instanceType; private final Configs configs; private final FactoryOfTheFuture futuresFactory; private final Provider queryExecutor; private final PunishmentSelector selector; private final InternalFormatter formatter; + private final EnvEnforcer

envEnforcer; private final MuteCache muteCache; - private final EnvEnforcer envEnforcer; private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); @Inject - public StandardLocalEnforcer(Configs configs, FactoryOfTheFuture futuresFactory, + public StandardLocalEnforcer(InstanceType instanceType, Configs configs, FactoryOfTheFuture futuresFactory, Provider queryExecutor, PunishmentSelector selector, - InternalFormatter formatter, EnvEnforcer envEnforcer, MuteCache muteCache) { + InternalFormatter formatter, EnvEnforcer

envEnforcer, MuteCache muteCache) { + this.instanceType = instanceType; this.configs = configs; this.futuresFactory = futuresFactory; this.queryExecutor = queryExecutor; @@ -95,7 +100,7 @@ public CentralisedFuture enforceWithoutSynchronization(Punishment punishme PunishmentAdditionSection section = configs.getMessagesConfig().additions().forType(punishment.getType()); - var arrestsAndNotices = new Parameterized<>(envEnforcer).enforceArrestsAndNotices(punishment); + var arrestsAndNotices = enforceArrestsAndNotices(punishment); if (enforcementOptions.broadcasting() == Broadcasting.NONE) { return arrestsAndNotices; } @@ -196,123 +201,120 @@ public CentralisedFuture updateDetailsWithoutSynchronization(long id) { if (optPunishment.isEmpty()) { // Possible race condition if punishment is expunged logger.debug("Tried to update details of non-existent punishment with id {}", id); - return futuresFactory.completedFuture(null); + return completedFuture(null); } return updateDetailsWithoutSynchronization(optPunishment.get()); }).toCompletableFuture(); } - // Enforcement of enacted punishments + private CentralisedFuture enforceArrestsAndNotices(Punishment punishment) { - private class Parameterized

{ + return formatter.getPunishmentMessage(punishment).thenCompose((message) -> { - private final EnvEnforcer

envEnforcer; + Victim victim = punishment.getVictim(); + Consumer

enforcementCallback = enforcementCallback(punishment, message); + AddressStrictness strictness = configs.getMainConfig().enforcement().addressStrictness(); - Parameterized(EnvEnforcer

envEnforcer) { - this.envEnforcer = envEnforcer; - } - - CentralisedFuture enforceArrestsAndNotices(Punishment punishment) { - - return formatter.getPunishmentMessage(punishment).thenCompose((message) -> { - - Victim victim = punishment.getVictim(); - Consumer

enforcementCallback = enforcementCallback(punishment, message); - AddressStrictness strictness = configs.getMainConfig().enforcement().addressStrictness(); + if (victim instanceof PlayerVictim playerVictim) { + UUID uuid = playerVictim.getUUID(); + if (strictness == AddressStrictness.STRICT) { + return matchUserPunishmentStrict(uuid, enforcementCallback) + .thenCompose(envEnforcer::enforceMatcher); + } + return envEnforcer.doForPlayerIfOnline(uuid, enforcementCallback); - if (victim instanceof PlayerVictim playerVictim) { - UUID uuid = playerVictim.getUUID(); - if (strictness == AddressStrictness.STRICT) { - return matchUserPunishmentStrict(uuid, enforcementCallback) - .thenCompose(envEnforcer::enforceMatcher); - } - return envEnforcer.doForPlayerIfOnline(uuid, enforcementCallback); + } else if (victim instanceof AddressVictim addressVictim) { + NetworkAddress address = addressVictim.getAddress(); + return matchAddressPunishment(strictness, enforcementCallback, address) + .thenCompose(envEnforcer::enforceMatcher); - } else if (victim instanceof AddressVictim addressVictim) { - NetworkAddress address = addressVictim.getAddress(); - return matchAddressPunishment(strictness, enforcementCallback, address) - .thenCompose(envEnforcer::enforceMatcher); + } else if (victim instanceof CompositeVictim compositeVictim) { + UUID uuid = compositeVictim.getUUID(); + NetworkAddress address = compositeVictim.getAddress(); + return matchAddressPunishment(strictness, enforcementCallback, address) + .thenApply((addressMatcher) -> new AdditionalUUIDTargetMatcher<>(uuid, addressMatcher)) + .thenCompose(envEnforcer::enforceMatcher); - } else if (victim instanceof CompositeVictim compositeVictim) { - UUID uuid = compositeVictim.getUUID(); - NetworkAddress address = compositeVictim.getAddress(); - return matchAddressPunishment(strictness, enforcementCallback, address) - .thenApply((addressMatcher) -> new AdditionalUUIDTargetMatcher<>(uuid, addressMatcher)) - .thenCompose(envEnforcer::enforceMatcher); + } else { + throw MiscUtil.unknownVictimType(victim.getType()); + } + }); + } + private Consumer

enforcementCallback(Punishment punishment, Component message) { + return switch (punishment.getType()) { + case BAN, KICK -> (player) -> { + if (instanceType == InstanceType.GAME_SERVER + && configs.getMainConfig().platforms().gameServers().usePluginMessaging()) { + envEnforcer.sendPluginMessage( + player, new KickPlayer(), new KickPlayer.Data(envEnforcer.getNameFor(player), message) + ); } else { - throw MiscUtil.unknownVictimType(victim.getType()); + envEnforcer.kickPlayer(player, message); } - }); - } - - private Consumer

enforcementCallback(Punishment punishment, Component message) { - return switch (punishment.getType()) { - case BAN, KICK -> (player) -> envEnforcer.kickPlayer(player, message); - case MUTE -> (player) -> { - /* - * Mute enforcement must additionally take into account the mute cache - */ - UUID uuid = envEnforcer.getUniqueIdFor(player); - NetworkAddress address = NetworkAddress.of(envEnforcer.getAddressFor(player)); - muteCache.setCachedMute(uuid, address, punishment); - - envEnforcer.sendMessageNoPrefix(player, message); - }; - case WARN -> (player) -> envEnforcer.sendMessageNoPrefix(player, message); }; - } - - private CentralisedFuture> matchAddressPunishment( - AddressStrictness strictness, Consumer

enforcementCallback, NetworkAddress address) { - return switch (strictness) { - case LENIENT -> completedFuture(new ExactTargetMatcher<>(address, enforcementCallback)); - case NORMAL -> matchAddressPunishmentNormal(address, enforcementCallback); - case STERN, STRICT -> matchAddressPunishmentSternOrStrict(address, enforcementCallback); + case MUTE -> (player) -> { + /* + * Mute enforcement must additionally take into account the mute cache + */ + UUID uuid = envEnforcer.getUniqueIdFor(player); + NetworkAddress address = NetworkAddress.of(envEnforcer.getAddressFor(player)); + muteCache.setCachedMute(uuid, address, punishment); + + envEnforcer.sendMessageNoPrefix(player, message); }; - } + case WARN -> (player) -> envEnforcer.sendMessageNoPrefix(player, message); + }; + } - private CentralisedFuture> matchAddressPunishmentNormal( - NetworkAddress address, Consumer

enforcementCallback) { - return queryExecutor.get().query(SQLFunction.readOnly((context) -> { - return context - .select(ADDRESSES.UUID) - .from(ADDRESSES) - .where(ADDRESSES.ADDRESS.eq(address)) - .fetchSet(ADDRESSES.UUID); - })).thenApply((uuids) -> { - return new UUIDTargetMatcher<>(uuids, enforcementCallback); - }); - } + private CentralisedFuture> matchAddressPunishment( + AddressStrictness strictness, Consumer

enforcementCallback, NetworkAddress address) { + return switch (strictness) { + case LENIENT -> completedFuture(new ExactTargetMatcher<>(address, enforcementCallback)); + case NORMAL -> matchAddressPunishmentNormal(address, enforcementCallback); + case STERN, STRICT -> matchAddressPunishmentSternOrStrict(address, enforcementCallback); + }; + } - private CentralisedFuture> matchAddressPunishmentSternOrStrict( - NetworkAddress address, Consumer

enforcementCallback) { - return queryExecutor.get().query(SQLFunction.readOnly((context) -> { - return context - .select(STRICT_LINKS.UUID2) - .from(STRICT_LINKS) - .innerJoin(ADDRESSES) - .on(STRICT_LINKS.UUID1.eq(ADDRESSES.UUID)) - .where(ADDRESSES.ADDRESS.eq(address)) - .fetchSet(STRICT_LINKS.UUID2); - })).thenApply((uuids) -> { - return new UUIDTargetMatcher<>(uuids, enforcementCallback); - }); - } + private CentralisedFuture> matchAddressPunishmentNormal( + NetworkAddress address, Consumer

enforcementCallback) { + return queryExecutor.get().query(SQLFunction.readOnly((context) -> { + return context + .select(ADDRESSES.UUID) + .from(ADDRESSES) + .where(ADDRESSES.ADDRESS.eq(address)) + .fetchSet(ADDRESSES.UUID); + })).thenApply((uuids) -> { + return new UUIDTargetMatcher<>(uuids, enforcementCallback); + }); + } - private CentralisedFuture> matchUserPunishmentStrict( - UUID uuid, Consumer

enforcementCallback) { - return queryExecutor.get().query(SQLFunction.readOnly((context) -> { - return context - .select(STRICT_LINKS.UUID2) - .from(STRICT_LINKS) - .where(STRICT_LINKS.UUID1.eq(uuid)) - .fetchSet(STRICT_LINKS.UUID2); - })).thenApply((uuids) -> { - return new UUIDTargetMatcher<>(uuids, enforcementCallback); - }); - } + private CentralisedFuture> matchAddressPunishmentSternOrStrict( + NetworkAddress address, Consumer

enforcementCallback) { + return queryExecutor.get().query(SQLFunction.readOnly((context) -> { + return context + .select(STRICT_LINKS.UUID2) + .from(STRICT_LINKS) + .innerJoin(ADDRESSES) + .on(STRICT_LINKS.UUID1.eq(ADDRESSES.UUID)) + .where(ADDRESSES.ADDRESS.eq(address)) + .fetchSet(STRICT_LINKS.UUID2); + })).thenApply((uuids) -> { + return new UUIDTargetMatcher<>(uuids, enforcementCallback); + }); + } + private CentralisedFuture> matchUserPunishmentStrict( + UUID uuid, Consumer

enforcementCallback) { + return queryExecutor.get().query(SQLFunction.readOnly((context) -> { + return context + .select(STRICT_LINKS.UUID2) + .from(STRICT_LINKS) + .where(STRICT_LINKS.UUID1.eq(uuid)) + .fetchSet(STRICT_LINKS.UUID2); + })).thenApply((uuids) -> { + return new UUIDTargetMatcher<>(uuids, enforcementCallback); + }); } private CentralisedFuture completedFuture(T value) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeSerializer.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/CategoryScope.java similarity index 58% rename from bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeSerializer.java rename to bans-core/src/main/java/space/arim/libertybans/core/scope/CategoryScope.java index 31a30300a..77700ec36 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeSerializer.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/CategoryScope.java @@ -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 @@ -19,25 +19,20 @@ package space.arim.libertybans.core.scope; -import space.arim.dazzleconf.error.BadValueException; -import space.arim.dazzleconf.serialiser.Decomposer; -import space.arim.dazzleconf.serialiser.FlexibleType; -import space.arim.dazzleconf.serialiser.ValueSerialiser; import space.arim.libertybans.api.scope.ServerScope; -public final class ScopeSerializer implements ValueSerialiser { - @Override - public Class getTargetClass() { - return ServerScope.class; - } +import java.util.Objects; - @Override - public ServerScope deserialise(FlexibleType flexibleType) throws BadValueException { - return ScopeImpl.GLOBAL; +record CategoryScope(String category) implements ServerScope { + + CategoryScope { + ScopeParsing.checkScopeValue(category); } @Override - public Object serialise(ServerScope value, Decomposer decomposer) { - return "*"; + public boolean appliesTo(String server) { + Objects.requireNonNull(server); + return false; } + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/ConfiguredScope.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/ConfiguredScope.java new file mode 100644 index 000000000..5ce17ae78 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/ConfiguredScope.java @@ -0,0 +1,94 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.scope; + +import org.checkerframework.checker.nullness.qual.Nullable; +import space.arim.dazzleconf.error.BadValueException; +import space.arim.dazzleconf.serialiser.Decomposer; +import space.arim.dazzleconf.serialiser.FlexibleType; +import space.arim.dazzleconf.serialiser.ValueSerialiser; +import space.arim.libertybans.api.scope.ScopeManager; +import space.arim.libertybans.api.scope.ServerScope; + +import java.util.Objects; +import java.util.Optional; + +public final class ConfiguredScope { + + private final @Nullable ServerScope scope; + + private ConfiguredScope(@Nullable ServerScope scope) { + this.scope = scope; + } + + Optional rawOptional() { + return Optional.ofNullable(scope); + } + + public static ConfiguredScope create(ServerScope scope) { + Objects.requireNonNull(scope); + return new ConfiguredScope(scope); + } + + public static ConfiguredScope defaultPunishingScope() { + return new ConfiguredScope(null); + } + + public ServerScope actualize(ScopeManager scopeManager) { + return scope == null ? scopeManager.defaultPunishingScope() : scope; + } + + public static final class Serializer implements ValueSerialiser { + + private final ScopeParsing scopeParsing = new ScopeParsing(); + + @Override + public Class getTargetClass() { + return ConfiguredScope.class; + } + + @Override + public ConfiguredScope deserialise(FlexibleType flexibleType) throws BadValueException { + String input = flexibleType.getString(); + if (input.isEmpty()) { + return ConfiguredScope.defaultPunishingScope(); + } + ServerScope scope; + try { + scope = scopeParsing.parseFrom(input); + } catch (IllegalArgumentException ex) { + throw flexibleType.badValueExceptionBuilder() + .message("Invalid scope details") + .cause(ex) + .build(); + } + return new ConfiguredScope(scope); + } + + @Override + public Object serialise(ConfiguredScope value, Decomposer decomposer) { + ServerScope scope = value.scope; + if (scope == null) { + return ""; + } + return scopeParsing.display(scope, ScopeParsing.GLOBAL_SCOPE_USER_INPUT); + } + } +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/GlobalScope.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/GlobalScope.java new file mode 100644 index 000000000..9e1be8f24 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/GlobalScope.java @@ -0,0 +1,38 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.scope; + +import space.arim.libertybans.api.scope.ServerScope; + +import java.util.Objects; + +public final class GlobalScope implements ServerScope { + + public static final ServerScope INSTANCE = new GlobalScope(); + + private GlobalScope() {} + + @Override + public boolean appliesTo(String server) { + Objects.requireNonNull(server); + return true; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/InternalScopeManager.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/InternalScopeManager.java index 0e873b7ba..b54f76eaf 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/scope/InternalScopeManager.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/InternalScopeManager.java @@ -22,17 +22,47 @@ import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.api.scope.ServerScope; +import java.util.Optional; +import java.util.function.BiFunction; + public interface InternalScopeManager extends ScopeManager { - String getServer(ServerScope scope, String defaultIfGlobal); - + ServerScope deserialize(ScopeType scopeType, String value); + + R deconstruct(ServerScope scope, BiFunction computeResult); + + String display(ServerScope scope, String defaultIfGlobal); + + Optional parseFrom(String userInput); + /** * Checks that a server scope is nonnull and of the right implementation class * * @param scope the server scope * @throws NullPointerException if {@code scope} is null * @throws IllegalArgumentException if {@code scope} is a foreign implementation + * @return the same scope + */ + ServerScope checkScope(ServerScope scope); + + /** + * Whether to automatically detect the name of this server instance + * + * @return whether to detect the server name */ - void checkScope(ServerScope scope); - + boolean serverNameUndetected(); + + /** + * Sets the automatically detected name of this server instance + * + * @param serverName the server name + */ + void detectServerName(String serverName); + + /** + * Clears the automatically detected server name + * + */ + void clearDetectedServerName(); + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeImpl.java deleted file mode 100644 index c9bac084a..000000000 --- a/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeImpl.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * LibertyBans-core - * Copyright © 2020 Anand Beh - * - * LibertyBans-core 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-core 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-core. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.core.scope; - -import space.arim.libertybans.api.scope.ServerScope; - -public final class ScopeImpl implements ServerScope { - - private final String server; - - public static final ServerScope GLOBAL = new ScopeImpl(""); - - private ScopeImpl(String server) { - if (server.length() > 32) { - throw new IllegalArgumentException("Server must be 32 or fewer characters"); - } - this.server = server; - } - - /** - * Creates a scope applying to a specific server - * - * @param server the server name. cannot be empty - * @return the scope - */ - public static ScopeImpl specificServer(String server) { - if (server.isEmpty()) { - throw new IllegalArgumentException("Server cannot be empty"); - } - return new ScopeImpl(server); - } - - /** - * Gets the server for this scope - * - * @param scope the scope - * @param defaultIfGlobal if the scope turns out to be global, this will be returned instead - * @return the server for this scope, or {@code defaultIfGlobal} if this is the global scope - */ - public static String getServer(ServerScope scope, String defaultIfGlobal) { - if (!(scope instanceof ScopeImpl)) { - throw new IllegalArgumentException("Foreign implementation of Scope: " + scope.getClass()); - } - String server = ((ScopeImpl) scope).server(); - return server.isEmpty() ? defaultIfGlobal : server; - } - - String server() { - return server; - } - - @Override - public boolean appliesTo(String server) { - return this.server.isEmpty() || this.server.equals(server); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + server.hashCode(); - return result; - } - - @Override - public boolean equals(Object object) { - if (this == object) { - return true; - } - if (!(object instanceof ScopeImpl)) { - return false; - } - ScopeImpl other = (ScopeImpl) object; - return server.equals(other.server); - } - - @Override - public String toString() { - return server.isEmpty() ? "global" : "server:" + server; - } - -} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeParsing.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeParsing.java new file mode 100644 index 000000000..eda318e7a --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeParsing.java @@ -0,0 +1,128 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.scope; + +import space.arim.libertybans.api.scope.ServerScope; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiFunction; + +public class ScopeParsing { + + public static final String GLOBAL_SCOPE_USER_INPUT = "*"; + + private static final int SCOPE_VALUE_LENGTH_LIMIT = 32; + + static void checkScopeValue(String value) { + if (value.isEmpty()) { + throw new IllegalArgumentException("Scope is empty"); + } + if (value.length() > SCOPE_VALUE_LENGTH_LIMIT) { + throw new IllegalArgumentException("Scope length must be less than " + SCOPE_VALUE_LENGTH_LIMIT); + } + } + + public ServerScope specificScope(String server) { + return new SpecificServerScope(server); + } + + public ServerScope category(String category) { + return new CategoryScope(category); + } + + public ServerScope globalScope() { + return GlobalScope.INSTANCE; + } + + /** + * Same as {@link #parseFrom(String)} except yields optional. + * + * @param userInput the user input + * @return the scope if the input matched an appropriate pattern and was valid + */ + public Optional parseInputOptionally(String userInput) { + ServerScope scope; + try { + scope = parseFrom(userInput); + } catch (IllegalArgumentException ex) { + return Optional.empty(); + } + return Optional.of(scope); + } + + /** + * Inverse of display(scope, GLOBAL_SCOPE_USER_INPUT) + * + * @param userInput the user input + * @return the scope if the input matched an appropriate pattern and was valid + * @throws IllegalArgumentException if not a valid scope + */ + public ServerScope parseFrom(String userInput) { + + if (userInput.equals(GLOBAL_SCOPE_USER_INPUT)) { + return globalScope(); + + } else if (userInput.startsWith("server:")) { + String server = userInput.substring("server:".length()); + return specificScope(server); + + } else if (userInput.startsWith("category:")) { + String category = userInput.substring("category:".length()); + return category(category); + } + throw new IllegalArgumentException( + "Scopes must be of the form '" + GLOBAL_SCOPE_USER_INPUT + "', 'server:' plus a server, " + + "or 'category:' plus a category" + ); + } + + String display(ServerScope scope, String defaultIfGlobal) { + return deconstruct(scope, (type, value) -> { + String prefix = switch (type) { + case GLOBAL -> defaultIfGlobal; + case SERVER -> "server:"; + case CATEGORY -> "category:"; + }; + return prefix + value; + }); + } + + public R deconstruct(ServerScope scope, BiFunction computeResult) { + ScopeType type; + String value; + if (scope instanceof GlobalScope) { + type = ScopeType.GLOBAL; + value = ""; + } else if (scope instanceof SpecificServerScope specificServer) { + type = ScopeType.SERVER; + value = specificServer.server(); + } else if (scope instanceof CategoryScope category) { + type = ScopeType.CATEGORY; + value = category.category(); + } else { + Objects.requireNonNull(scope, "scope"); + throw new IllegalArgumentException( + "Unknown server scope implementation: " + scope + " (" + scope.getClass() + ')' + ); + } + return computeResult.apply(type, value); + } +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeType.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeType.java new file mode 100644 index 000000000..f6f2d57f6 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeType.java @@ -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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.scope; + +public enum ScopeType { + GLOBAL, + SERVER, + CATEGORY +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/Scoper.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/Scoper.java deleted file mode 100644 index 3aac66070..000000000 --- a/bans-core/src/main/java/space/arim/libertybans/core/scope/Scoper.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * LibertyBans-core - * Copyright © 2020 Anand Beh - * - * LibertyBans-core 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-core 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-core. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.core.scope; - -import java.util.Objects; - -import jakarta.inject.Singleton; - -import space.arim.libertybans.api.scope.ServerScope; - -@Singleton -public class Scoper implements InternalScopeManager { - - public Scoper() { - - } - - @Override - public ServerScope specificScope(String server) { - Objects.requireNonNull(server, "server"); - return ScopeImpl.specificServer(server); - } - - @Override - public ServerScope globalScope() { - return ScopeImpl.GLOBAL; - } - - @Override - public ServerScope currentServerScope() { - return ScopeImpl.GLOBAL; // TODO implement scopes - } - - @Override - public String getServer(ServerScope scope, String defaultIfGlobal) { - return ScopeImpl.getServer(scope, defaultIfGlobal); - } - - @Override - public void checkScope(ServerScope scope) { - if (scope == null) { - throw new NullPointerException("scope"); - } - if (!(scope instanceof ScopeImpl)) { - throw new IllegalArgumentException("Foreign implementation of scope " + scope.getClass()); - } - } - -} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/ServerNameListenerBase.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/ServerNameListenerBase.java new file mode 100644 index 000000000..f7ae3bb6a --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/ServerNameListenerBase.java @@ -0,0 +1,86 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.scope; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import space.arim.api.env.annote.PlatformPlayer; +import space.arim.libertybans.core.config.Configs; +import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.InstanceType; +import space.arim.libertybans.core.env.PlatformListener; +import space.arim.libertybans.core.env.message.GetServer; + +import java.util.function.Consumer; + +@Singleton +public final class ServerNameListenerBase<@PlatformPlayer P, H> implements PlatformListener { + + private final Configs configs; + private final InternalScopeManager scopeManager; + private final EnvEnforcer

envEnforcer; + private final EnvMessageChannel envMessageChannel; + + private final H handler; + + @Inject + public ServerNameListenerBase(InstanceType instanceType, Configs configs, InternalScopeManager scopeManager, + EnvEnforcer

envEnforcer, EnvMessageChannel envMessageChannel) { + if (instanceType != InstanceType.GAME_SERVER) { + throw new IllegalStateException("Cannot use server name listener except for backend game servers"); + } + this.configs = configs; + this.scopeManager = scopeManager; + this.envEnforcer = envEnforcer; + this.envMessageChannel = envMessageChannel; + handler = envMessageChannel.createHandler(new ResponseHandler(), new GetServer()); + } + + @Override + public void register() { + if (configs.getMainConfig().platforms().gameServers().usePluginMessaging()) { + envMessageChannel.installHandler(handler); + } + } + + @Override + public void unregister() { + // This should not throw even if the handler is not registered + envMessageChannel.uninstallHandler(handler); + } + + public void onJoin(P player) { + if (configs.getMainConfig().platforms().gameServers().usePluginMessaging() + && configs.getScopeConfig().serverName().autoDetect() + && scopeManager.serverNameUndetected()) { + envEnforcer.sendPluginMessage(player, new GetServer(), null); + } + } + + final class ResponseHandler implements Consumer { + + @Override + public void accept(GetServer.Response response) { + scopeManager.detectServerName(response.server()); + } + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/ServerScopeConverter.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/SpecificServerScope.java similarity index 51% rename from bans-core/src/main/java/space/arim/libertybans/core/database/jooq/ServerScopeConverter.java rename to bans-core/src/main/java/space/arim/libertybans/core/scope/SpecificServerScope.java index 884ad098a..e163710ba 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/ServerScopeConverter.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/SpecificServerScope.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -17,41 +17,22 @@ * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.core.database.jooq; +package space.arim.libertybans.core.scope; -import org.jetbrains.annotations.NotNull; -import org.jooq.Converter; import space.arim.libertybans.api.scope.ServerScope; -import space.arim.libertybans.core.scope.ScopeImpl; -public final class ServerScopeConverter implements Converter { +import java.util.Objects; - @Override - public ServerScope from(String server) { - if (server == null) { - return null; - } - if (server.isEmpty()) { - return ScopeImpl.GLOBAL; - } - return ScopeImpl.specificServer(server); - } +public record SpecificServerScope(String server) implements ServerScope { - @Override - public String to(ServerScope scope) { - if (scope == null) { - return null; - } - return ScopeImpl.getServer(scope, ""); + public SpecificServerScope { + ScopeParsing.checkScopeValue(server); } @Override - public @NotNull Class fromType() { - return String.class; + public boolean appliesTo(String server) { + Objects.requireNonNull(server); + return this.server.equals(server); } - @Override - public @NotNull Class toType() { - return ServerScope.class; - } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/StandardScopeManager.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/StandardScopeManager.java new file mode 100644 index 000000000..42e1957d8 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/StandardScopeManager.java @@ -0,0 +1,167 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.scope; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.slf4j.LoggerFactory; +import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.config.Configs; +import space.arim.libertybans.core.config.ScopeConfig; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiFunction; + +@Singleton +public class StandardScopeManager implements InternalScopeManager { + + private final Configs configs; + private final ScopeParsing scopeParsing; + + private volatile String serverName; + + @Inject + public StandardScopeManager(Configs configs, ScopeParsing scopeParsing) { + this.configs = configs; + this.scopeParsing = scopeParsing; + } + + private ScopeConfig config() { + return configs.getScopeConfig(); + } + + @Override + public ServerScope specificScope(String server) { + return new SpecificServerScope(server); + } + + @Override + public ServerScope category(String category) { + return new CategoryScope(category); + } + + @Override + public ServerScope globalScope() { + return GlobalScope.INSTANCE; + } + + @Override + public Optional currentServerScope() { + var nameConfig = config().serverName(); + if (nameConfig.autoDetect()) { + return Optional.ofNullable(this.serverName).map(this::specificScope); + } else { + return Optional.of(specificScope(nameConfig.overrideValue())); + } + } + + @Override + public ServerScope currentServerScopeOrFallback() { + return currentServerScope().orElseGet(() -> { + ConfiguredScope fallback = config().serverName().fallbackIfAutoDetectFails(); + // Don't call fallback.actualize(this) to prevent infinite recursion + return fallback.rawOptional().orElse(globalScope()); + }); + } + + @Override + public Set scopesApplicableToCurrentServer() { + Set applicable = new HashSet<>(); + applicable.add(globalScope()); + applicable.add(currentServerScopeOrFallback()); + for (String category : config().categoriesApplicableToThisServer()) { + applicable.add(category(category)); + } + return applicable; + } + + @Override + public ServerScope defaultPunishingScope() { + return switch (config().defaultPunishingScope()) { + case GLOBAL -> globalScope(); + case THIS_SERVER -> currentServerScopeOrFallback(); + case PRIMARY_CATEGORY -> { + var categories = config().categoriesApplicableToThisServer(); + if (categories.isEmpty()) { + LoggerFactory.getLogger(getClass()).warn( + "In the scope.yml configuration, the option default-punishing-scope is set to " + + "'PRIMARY_CATEGORY', but no categories were specified. Either configure a category " + + "or use a different default-punishing-scope value. The value 'THIS_SERVER' will be " + + "temporarily used while you fix the configuration." + ); + yield currentServerScopeOrFallback(); + } else { + yield category(categories.get(0)); + } + } + }; + } + + @Override + public ServerScope deserialize(ScopeType scopeType, String value) { + return switch (scopeType) { + case GLOBAL -> GlobalScope.INSTANCE; + case SERVER -> new SpecificServerScope(value); + case CATEGORY -> new CategoryScope(value); + }; + } + + @Override + public R deconstruct(ServerScope scope, BiFunction computeResult) { + return scopeParsing.deconstruct(scope, computeResult); + } + + @Override + public String display(ServerScope scope, String defaultIfGlobal) { + return scopeParsing.display(scope, defaultIfGlobal); + } + + @Override + public Optional parseFrom(String userInput) { + return scopeParsing.parseInputOptionally(userInput); + } + + @Override + public ServerScope checkScope(ServerScope scope) { + // If deconstruct succeeds, scope is okay + deconstruct(scope, (_type, _value) -> null); + return scope; + } + + @Override + public boolean serverNameUndetected() { + return serverName == null; + } + + @Override + public void detectServerName(String serverName) { + Objects.requireNonNull(serverName); + this.serverName = serverName; + } + + @Override + public void clearDetectedServerName() { + serverName = null; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/Gatekeeper.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/Gatekeeper.java index 8ed3ea08c..a47fadb97 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/Gatekeeper.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/Gatekeeper.java @@ -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 @@ -25,6 +25,8 @@ import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.api.select.SelectionPredicate; import space.arim.libertybans.api.select.SortPunishments; import space.arim.libertybans.core.alts.AltDetection; import space.arim.libertybans.core.alts.AltNotification; @@ -40,6 +42,7 @@ import java.time.Instant; import java.util.List; +import java.util.Set; import java.util.UUID; public final class Gatekeeper { @@ -68,7 +71,7 @@ public Gatekeeper(Configs configs, FactoryOfTheFuture futuresFactory, Provider executeAndCheckConnection(UUID uuid, String name, NetworkAddress address, - SelectorImpl selector) { + Set scopes, SelectorImpl selector) { return queryExecutor.get().queryWithRetry((context, transaction) -> { Instant currentTime = time.currentTimestamp(); @@ -78,6 +81,7 @@ CentralisedFuture executeAndCheckConnection(UUID uuid, String name, N Punishment ban = selector.selectionByApplicabilityBuilder(uuid, address) .type(PunishmentType.BAN) + .scopes(SelectionPredicate.matchingAnyOf(scopes)) .build() .findFirstSpecificPunishment(context, () -> currentTime, SortPunishments.LATEST_END_DATE_FIRST); if (ban != null) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/Guardian.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/Guardian.java index c465416a7..488bcd127 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/Guardian.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/Guardian.java @@ -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 @@ -20,6 +20,7 @@ package space.arim.libertybans.core.selector; import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.Nullable; import space.arim.libertybans.api.NetworkAddress; import space.arim.omnibus.util.concurrent.CentralisedFuture; @@ -39,7 +40,7 @@ public interface Guardian { * @param address the player's network address * @return a future which yields the punishment message if denied, else null if allowed */ - CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address); + CentralisedFuture<@Nullable Component> executeAndCheckConnection(UUID uuid, String name, NetworkAddress address); /** * Enforces an incoming connection, returning a punishment message if denied, null if allowed.
@@ -52,10 +53,22 @@ public interface Guardian { * @param address the player's network address * @return a future which yields the punishment message if denied, else null if allowed */ - default CentralisedFuture executeAndCheckConnection(UUID uuid, String name, InetAddress address) { + default CentralisedFuture<@Nullable Component> executeAndCheckConnection(UUID uuid, String name, InetAddress address) { return executeAndCheckConnection(uuid, name, NetworkAddress.of(address)); } + /** + * Enforces a server switch, returning a punishment message if denied, null if allowed.
+ *
+ * Queries for an applicable ban, and formats the ban reason as the punishment message. + * + * @param uuid the player's uuid + * @param address the player's network address + * @param destinationServer the player's destination server + * @return a future which yields the punishment message if denied, else null if allowed + */ + CentralisedFuture<@Nullable Component> checkServerSwitch(UUID uuid, InetAddress address, String destinationServer); + /** * Enforces a chat message or executed command, returning a punishment message if denied, null if allowed.
*
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/IDImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/IDImpl.java index ed5814bc9..a258f4557 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/IDImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/IDImpl.java @@ -60,8 +60,8 @@ CentralisedFuture getActivePunishmentById(long id) { .select( SIMPLE_ACTIVE.TYPE, SIMPLE_ACTIVE.VICTIM_TYPE, SIMPLE_ACTIVE.VICTIM_UUID, SIMPLE_ACTIVE.VICTIM_ADDRESS, - SIMPLE_ACTIVE.OPERATOR, SIMPLE_ACTIVE.REASON, - SIMPLE_ACTIVE.SCOPE, SIMPLE_ACTIVE.START, SIMPLE_ACTIVE.END, SIMPLE_ACTIVE.TRACK + SIMPLE_ACTIVE.OPERATOR, SIMPLE_ACTIVE.REASON, SIMPLE_ACTIVE.SCOPE, + SIMPLE_ACTIVE.START, SIMPLE_ACTIVE.END, SIMPLE_ACTIVE.TRACK, SIMPLE_ACTIVE.SCOPE_TYPE ) .from(SIMPLE_ACTIVE) .where(SIMPLE_ACTIVE.ID.eq(id)) @@ -81,8 +81,8 @@ CentralisedFuture getActivePunishmentByIdAndType(long id, Punishment return context .select( simpleView.victimType(), simpleView.victimUuid(), simpleView.victimAddress(), - simpleView.operator(), simpleView.reason(), - simpleView.scope(), simpleView.start(), simpleView.end(), simpleView.track() + simpleView.operator(), simpleView.reason(), simpleView.scope(), + simpleView.start(), simpleView.end(), simpleView.track(), simpleView.scopeType() ) .from(simpleView.table()) .where(simpleView.id().eq(id)) @@ -98,8 +98,8 @@ CentralisedFuture getHistoricalPunishmentById(long id) { .select( SIMPLE_HISTORY.TYPE, SIMPLE_HISTORY.VICTIM_TYPE, SIMPLE_HISTORY.VICTIM_UUID, SIMPLE_HISTORY.VICTIM_ADDRESS, - SIMPLE_HISTORY.OPERATOR, SIMPLE_HISTORY.REASON, - SIMPLE_HISTORY.SCOPE, SIMPLE_HISTORY.START, SIMPLE_HISTORY.END, SIMPLE_HISTORY.TRACK + SIMPLE_HISTORY.OPERATOR, SIMPLE_HISTORY.REASON, SIMPLE_HISTORY.SCOPE, + SIMPLE_HISTORY.START, SIMPLE_HISTORY.END, SIMPLE_HISTORY.TRACK, SIMPLE_HISTORY.SCOPE_TYPE ) .from(SIMPLE_HISTORY) .where(SIMPLE_HISTORY.ID.eq(id)) @@ -113,8 +113,8 @@ CentralisedFuture getHistoricalPunishmentByIdAndType(long id, Punish return context .select( SIMPLE_HISTORY.VICTIM_TYPE, SIMPLE_HISTORY.VICTIM_UUID, SIMPLE_HISTORY.VICTIM_ADDRESS, - SIMPLE_HISTORY.OPERATOR, SIMPLE_HISTORY.REASON, - SIMPLE_HISTORY.SCOPE, SIMPLE_HISTORY.START, SIMPLE_HISTORY.END, SIMPLE_HISTORY.TRACK + SIMPLE_HISTORY.OPERATOR, SIMPLE_HISTORY.REASON, SIMPLE_HISTORY.SCOPE, + SIMPLE_HISTORY.START, SIMPLE_HISTORY.END, SIMPLE_HISTORY.TRACK, SIMPLE_HISTORY.SCOPE_TYPE ) .from(SIMPLE_HISTORY) .where(SIMPLE_HISTORY.ID.eq(id)) diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/IntelligentGuardian.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/IntelligentGuardian.java index 7f663d171..632dfc7c7 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/IntelligentGuardian.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/IntelligentGuardian.java @@ -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 @@ -22,41 +22,70 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.Nullable; import space.arim.libertybans.api.NetworkAddress; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.scope.ScopeManager; +import space.arim.libertybans.api.select.SortPunishments; import space.arim.libertybans.core.config.Configs; +import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.selector.cache.MuteCache; import space.arim.libertybans.core.uuid.UUIDManager; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; +import java.net.InetAddress; import java.util.UUID; import java.util.concurrent.CompletionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.Function; @Singleton public final class IntelligentGuardian implements Guardian { private final Configs configs; private final FactoryOfTheFuture futuresFactory; + private final ScopeManager scopeManager; + private final InternalFormatter formatter; private final InternalSelector selector; private final UUIDManager uuidManager; private final MuteCache muteCache; @Inject - public IntelligentGuardian(Configs configs, FactoryOfTheFuture futuresFactory, - InternalSelector selector, UUIDManager uuidManager, MuteCache muteCache) { + public IntelligentGuardian(Configs configs, FactoryOfTheFuture futuresFactory, ScopeManager scopeManager, + InternalFormatter formatter, InternalSelector selector, UUIDManager uuidManager, MuteCache muteCache) { this.configs = configs; this.futuresFactory = futuresFactory; + this.scopeManager = scopeManager; + this.formatter = formatter; this.selector = selector; this.uuidManager = uuidManager; this.muteCache = muteCache; } + private static Function timeoutHandler(String where) { + return (ex) -> { + if (ex instanceof TimeoutException) { + throw new IllegalStateException( + "Database timeout while attempting to execute " + where + ". " + + "Your database likely took too long to respond.", + ex); + } else if (ex instanceof CompletionException) { + throw (CompletionException) ex; + } + throw new CompletionException(ex); + }; + } + @Override - public CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address) { + public CentralisedFuture<@Nullable Component> executeAndCheckConnection(UUID uuid, String name, + NetworkAddress address) { uuidManager.addCache(uuid, name); - return selector.executeAndCheckConnection(uuid, name, address) + return selector + .executeAndCheckConnection( + uuid, name, address, scopeManager.scopesApplicableToCurrentServer() + ) .thenCompose((component) -> { // Contact the mute cache, but only if needed if (component != null) { @@ -65,17 +94,30 @@ public CentralisedFuture executeAndCheckConnection(UUID uuid, String return muteCache.cacheOnLogin(uuid, address).thenApply((ignore) -> null); }) .orTimeout(12, TimeUnit.SECONDS) - .exceptionally((ex) -> { - if (ex instanceof TimeoutException) { - throw new IllegalStateException( - "Database timeout while attempting to execute incoming login. " + - "Your database likely took too long to respond.", - ex); - } else if (ex instanceof CompletionException) { - throw (CompletionException) ex; + .exceptionally(timeoutHandler("incoming login")); + } + + @Override + public CentralisedFuture<@Nullable Component> checkServerSwitch(UUID uuid, InetAddress address, + String destinationServer) { + if (!configs.getMainConfig().platforms().proxies().enforceServerSwitch()) { + return futuresFactory.completedFuture(null); + } + return selector + .selectionByApplicabilityBuilder(uuid, address) + .type(PunishmentType.BAN) + .scope(scopeManager.specificScope(destinationServer)) + .build() + .getFirstSpecificPunishment(SortPunishments.LATEST_END_DATE_FIRST) + .thenCompose((punishment) -> { + if (punishment.isEmpty()) { + return futuresFactory.completedFuture(null); } - throw new CompletionException(ex); - }); + return formatter.getPunishmentMessage(punishment.get()); + }) + .toCompletableFuture() + .orTimeout(12, TimeUnit.SECONDS) + .exceptionally(timeoutHandler("server switch")); } @Override diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/InternalSelector.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/InternalSelector.java index 066138e08..bb3a43cad 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/InternalSelector.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/InternalSelector.java @@ -21,10 +21,12 @@ import net.kyori.adventure.text.Component; import space.arim.libertybans.api.NetworkAddress; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.api.select.PunishmentSelector; import space.arim.libertybans.api.select.SelectionOrderBuilder; import space.arim.omnibus.util.concurrent.CentralisedFuture; +import java.util.Set; import java.util.UUID; public interface InternalSelector extends PunishmentSelector { @@ -44,8 +46,10 @@ public interface InternalSelector extends PunishmentSelector { * @param uuid the player uuid * @param name the player name * @param address the player address + * @param scopes the server scopes to include in the selection query * @return a future which yields the denial message, or null if there is none */ - CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address); + CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address, + Set scopes); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionBaseSQL.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionBaseSQL.java index 460bce718..4d872dcd0 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionBaseSQL.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionBaseSQL.java @@ -41,8 +41,10 @@ import space.arim.libertybans.core.database.sql.ApplicableViewFields; import space.arim.libertybans.core.database.sql.EndTimeCondition; import space.arim.libertybans.core.database.sql.PunishmentFields; +import space.arim.libertybans.core.database.sql.ScopeCondition; import space.arim.libertybans.core.database.sql.SimpleViewFields; import space.arim.libertybans.core.database.sql.TableForType; +import space.arim.libertybans.core.scope.ScopeType; import space.arim.omnibus.util.concurrent.ReactionStage; import java.time.Instant; @@ -142,6 +144,7 @@ private List> getColumnsToRetrieve(List> additionalColumns) { } columns.add(fields.reason()); if (getScopes().isNotSimpleEquality()) { + columns.add(fields.scopeType()); columns.add(fields.scope()); } columns.add(fields.start()); @@ -164,14 +167,12 @@ private Condition getPredication(Supplier timeSupplier) { // Type is ensured by selected table } else { condition = condition - .and(new Criteria<>(getTypes()).matchesField(fields.type())); + .and(new SingleFieldCriterion<>(fields.type()).matches(getTypes())); } condition = condition - .and(new Criteria<>(getOperators()).matchesField(fields.operator())) - .and(new Criteria<>(getScopes()).matchesField(fields.scope())) - .and(Criteria.createViaTransform(getEscalationTracks(), (optTrack) -> optTrack.orElse(null)) - .matchesField(fields.track()) - ); + .and(new SingleFieldCriterion<>(fields.operator()).matches(getOperators())) + .and(new SingleFieldCriterion<>(fields.track()).matches(getEscalationTracks(), (optTrack) -> optTrack.orElse(null))) + .and(new ScopeCondition(fields, resources.scopeManager()).buildCondition(getScopes())); if (active) { condition = condition .and(new EndTimeCondition(fields).isNotExpired(timeSupplier.get())); @@ -236,9 +237,14 @@ private Punishment mapRecord(Record record) { Operator operator = retrieveValueFromRecordOrSelection( getOperators(), record, fields.operator() ); - ServerScope scope = retrieveValueFromRecordOrSelection( - getScopes(), record, fields.scope() - ); + ServerScope scope; + if (getScopes().isSimpleEquality()) { + scope = getScopes().acceptedValues().iterator().next(); + } else { + ScopeType scopeType = record.get(aggregateIfNeeded(fields.scopeType())); + String scopeValue = record.get(aggregateIfNeeded(fields.scope())); + scope = resources.scopeManager().deserialize(scopeType, scopeValue); + } EscalationTrack escalationTrack = retrieveValueFromRecordOrSelection( getEscalationTracks(), record, fields.track(), (optTrack) -> optTrack.orElse(null) diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionByApplicabilityBuilderImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionByApplicabilityBuilderImpl.java index 3adb8819c..cbb0f6747 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionByApplicabilityBuilderImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionByApplicabilityBuilderImpl.java @@ -21,9 +21,11 @@ import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.api.select.AddressStrictness; import space.arim.libertybans.api.select.SelectionByApplicability; import space.arim.libertybans.api.select.SelectionByApplicabilityBuilder; +import space.arim.libertybans.api.select.SelectionPredicate; import java.util.Objects; import java.util.UUID; @@ -77,6 +79,11 @@ public SelectionByApplicabilityBuilderImpl type(PunishmentType type) { return (SelectionByApplicabilityBuilderImpl) super.type(type); } + @Override + public SelectionByApplicabilityBuilderImpl scopes(SelectionPredicate scopes) { + return (SelectionByApplicabilityBuilderImpl) super.scopes(scopes); + } + @Override public SelectionByApplicabilityImpl build() { return (SelectionByApplicabilityImpl) super.build(); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderImpl.java index 5f53f43ba..84b3788ef 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderImpl.java @@ -34,7 +34,6 @@ import java.util.Objects; import static org.jooq.impl.DSL.noCondition; -import static org.jooq.impl.DSL.not; final class SelectionOrderImpl extends SelectionBaseSQL implements SelectionOrder { @@ -70,29 +69,10 @@ Query requestQuery(QueryParameters parameters) { additionalColumns.add(fields.victimUuid()); additionalColumns.add(fields.victimAddress()); } - Condition additionalPredication; - { - VictimCondition victimCondition = new VictimCondition(fields); - // Check victim is accepted - Condition victimAcceptedCondition = noCondition(); - for (Victim acceptedVictim : getVictims().acceptedValues()) { - victimAcceptedCondition = victimAcceptedCondition.or( - victimCondition.matchesVictim(acceptedVictim) - ); - } - // Check victim is not rejected - Condition victimNotRejectedCondition = noCondition(); - for (Victim rejectedVictim : getVictims().rejectedValues()) { - Condition notEqual = not( - victimCondition.matchesVictim(rejectedVictim) - ); - victimNotRejectedCondition = victimNotRejectedCondition.and(notEqual); - } - // Check victim type is accepted - additionalPredication = new Criteria<>(getVictimTypes()).matchesField(fields.victimType()) - .and(victimAcceptedCondition) - .and(victimNotRejectedCondition); - } + Condition additionalPredication = noCondition() + .and(new SingleFieldCriterion<>(fields.victimType()).matches(getVictimTypes())) + .and(new VictimCondition(fields).buildCondition(getVictims())); + return new QueryBuilder(parameters, fields, fields.table()) { @Override Victim victimFromRecord(Record record) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionResources.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionResources.java index 8b1026e4f..b39cc3f6d 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionResources.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionResources.java @@ -23,11 +23,13 @@ import jakarta.inject.Provider; import space.arim.libertybans.core.database.execute.QueryExecutor; import space.arim.libertybans.core.punish.PunishmentCreator; +import space.arim.libertybans.core.scope.InternalScopeManager; import space.arim.libertybans.core.service.Time; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; public record SelectionResources(FactoryOfTheFuture futuresFactory, Provider dbProvider, + InternalScopeManager scopeManager, PunishmentCreator creator, Time time) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImpl.java index a6f434824..630e97fb1 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImpl.java @@ -26,6 +26,7 @@ import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.api.select.AddressStrictness; import space.arim.libertybans.api.select.SelectionOrderBuilder; import space.arim.libertybans.core.config.Configs; @@ -35,6 +36,7 @@ import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.UUID; @Singleton @@ -105,8 +107,9 @@ public ReactionStage> getHistoricalPunishmentByIdAndType(lo */ @Override - public CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address) { - return gatekeeper.executeAndCheckConnection(uuid, name, address, this); + public CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address, + Set scopes) { + return gatekeeper.executeAndCheckConnection(uuid, name, address, scopes, this); } @Override diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/Criteria.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SingleFieldCriterion.java similarity index 76% rename from bans-core/src/main/java/space/arim/libertybans/core/selector/Criteria.java rename to bans-core/src/main/java/space/arim/libertybans/core/selector/SingleFieldCriterion.java index 91a0c4d6a..7cecf87be 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/Criteria.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SingleFieldCriterion.java @@ -21,8 +21,6 @@ import org.jooq.Condition; import org.jooq.Field; -import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.select.SelectionPredicate; import java.util.AbstractSet; @@ -35,56 +33,12 @@ import static org.jooq.impl.DSL.noCondition; import static org.jooq.impl.DSL.val; -record Criteria(Set acceptedValues, Set rejectedValues) { +record SingleFieldCriterion(Field field) { - Criteria(SelectionPredicate selection) { - this(selection.acceptedValues(), selection.rejectedValues()); - } - - static Criteria createViaTransform(SelectionPredicate selection, Function converter) { - class SetView extends AbstractSet { - - private final Set original; - - SetView(Set original) { - this.original = original; - } - - @Override - public Iterator iterator() { - Iterator iter = original.iterator(); - class IteratorView implements Iterator { - - @Override - public boolean hasNext() { - return iter.hasNext(); - } - - @Override - public F next() { - return converter.apply(iter.next()); - } - - @Override - public void forEachRemaining(Consumer action) { - iter.forEachRemaining((g) -> action.accept(converter.apply(g))); - } - } - return new IteratorView(); - } - - @Override - public int size() { - return original.size(); - } - } - return new Criteria<>(new SetView(selection.acceptedValues()), new SetView(selection.rejectedValues())); - } - - private static Field inlineIfNeeded(Field field, U value) { - // Automatically inline PunishmentType and VictimType comparisons - Class fieldType = field.getType(); - boolean shouldInline = fieldType.equals(PunishmentType.class) || fieldType.equals(Victim.VictimType.class); + private Field inlineIfNeeded(F value) { + // Automatically inline enum comparisons (e.g. PunishmentType, VictimType, and ScopeType) + Class fieldType = field.getType(); + boolean shouldInline = fieldType.isEnum(); return shouldInline ? inline(value) : val(value); } @@ -98,7 +52,7 @@ private static boolean containsNull(Set set) { return false; } - Condition matchesField(Field field) { + private Condition matches(Set acceptedValues, Set rejectedValues) { Condition acceptedCondition = switch (acceptedValues.size()) { case 0 -> noCondition(); case 1 -> { @@ -106,7 +60,7 @@ Condition matchesField(Field field) { if (singleAcceptedValue == null) { yield field.isNull(); } - yield field.eq(inlineIfNeeded(field, singleAcceptedValue)); + yield field.eq(inlineIfNeeded(singleAcceptedValue)); } default -> { if (containsNull(acceptedValues)) { @@ -123,7 +77,7 @@ Condition matchesField(Field field) { if (singleRejectedValue == null) { yield field.isNotNull(); } - yield field.notEqual(inlineIfNeeded(field, singleRejectedValue)); + yield field.notEqual(inlineIfNeeded(singleRejectedValue)); } default -> { if (containsNull(rejectedValues)) { @@ -136,4 +90,50 @@ Condition matchesField(Field field) { return acceptedCondition.and(notRejectedCondition); } + Condition matches(SelectionPredicate selection) { + return matches(selection.acceptedValues(), selection.rejectedValues()); + } + + Condition matches(SelectionPredicate selection, Function converter) { + class SetView extends AbstractSet { + + private final Set original; + + SetView(Set original) { + this.original = original; + } + + @Override + public Iterator iterator() { + Iterator iter = original.iterator(); + class IteratorView implements Iterator { + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public F next() { + return converter.apply(iter.next()); + } + + @Override + public void forEachRemaining(Consumer action) { + iter.forEachRemaining((g) -> action.accept(converter.apply(g))); + } + } + return new IteratorView(); + } + + @Override + public int size() { + return original.size(); + } + } + return matches( + new SetView(selection.acceptedValues()), new SetView(selection.rejectedValues()) + ); + } + } diff --git a/bans-core/src/main/resources/contributors b/bans-core/src/main/resources/contributors index d5b94be1a..8960fcb4c 100644 --- a/bans-core/src/main/resources/contributors +++ b/bans-core/src/main/resources/contributors @@ -12,6 +12,7 @@ FranMC23 FreakyRed Gamer153 gepron1x +MCMDEV IceWaffles potatoru SnakeAmazing diff --git a/bans-core/src/main/resources/database-migrations/V36__Server_scopes.sql b/bans-core/src/main/resources/database-migrations/V36__Server_scopes.sql new file mode 100644 index 000000000..7c76d681b --- /dev/null +++ b/bans-core/src/main/resources/database-migrations/V36__Server_scopes.sql @@ -0,0 +1,191 @@ + +CREATE TABLE "${tableprefix}scopes" ( + "id" INT NOT NULL, + "type" SMALLINT NOT NULL, + "value" CHARACTER VARYING(32) NOT NULL, + CONSTRAINT "${tableprefix}scope_id_uniqueness" UNIQUE ("id"), + CONSTRAINT "${tableprefix}scope_type_validity" CHECK ("type" IN (1, 2)), + CONSTRAINT "${tableprefix}scope_data_uniqueness" UNIQUE ("type", "value") +)${extratableoptions}; + +-- Include the scope ID column in the punishments table +-- For backwards compatibility, the old textual scope column is kept +ALTER TABLE "${tableprefix}punishments" ADD COLUMN "scope_id" INT NULL; + +-- Recreate every punishment view to include the scope-related columns + +-- Bans + +${alterviewstatement} "${tableprefix}simple_bans" AS + SELECT "puns"."id", "puns"."type", + "victims"."type" AS "victim_type", "victims"."uuid" AS "victim_uuid", "victims"."address" AS "victim_address", + "puns"."operator", "puns"."reason", + ${migratescopestart}(CASE + WHEN "puns"."scope_id" IS NULL THEN '' + ELSE "scopes"."value" + END)${migratescopeend} AS "scope", + "puns"."start", "puns"."end", + (CASE + WHEN "tracks"."namespace" IS NULL THEN NULL + ELSE (("tracks"."namespace" || ':') || "tracks"."value") + END) AS "track", + (CASE + WHEN "puns"."scope_id" IS NULL THEN ${zerosmallintliteral} + ELSE "scopes"."type" + END) AS "scope_type" + FROM "${tableprefix}bans" AS "thetype" + INNER JOIN "${tableprefix}punishments" AS "puns" + ON "thetype"."id" = "puns"."id" + INNER JOIN "${tableprefix}victims" AS "victims" + ON "thetype"."victim" = "victims"."id" + LEFT JOIN "${tableprefix}tracks" AS "tracks" + ON "puns"."track" = "tracks"."id" + LEFT JOIN "${tableprefix}scopes" AS "scopes" + ON "puns"."scope_id" = "scopes"."id"; + +${alterviewstatement} "${tableprefix}applicable_bans" AS + SELECT "puns"."id", "puns"."type", "puns"."victim_type", "puns"."victim_uuid", "puns"."victim_address", + "puns"."operator", "puns"."reason", "puns"."scope", "puns"."start", "puns"."end", "addrs"."uuid", "addrs"."address", + "puns"."track", "puns"."scope_type" + FROM "${tableprefix}simple_bans" AS "puns" + INNER JOIN "${tableprefix}addresses" AS "addrs" + ON ("puns"."victim_type" = 0 AND "puns"."victim_uuid" = "addrs"."uuid" + OR "puns"."victim_type" = 1 AND "puns"."victim_address" = "addrs"."address" + OR "puns"."victim_type" = 2 AND ("puns"."victim_uuid" = "addrs"."uuid" OR "puns"."victim_address" = "addrs"."address")); + +-- Mutes + +${alterviewstatement} "${tableprefix}simple_mutes" AS + SELECT "puns"."id", "puns"."type", + "victims"."type" AS "victim_type", "victims"."uuid" AS "victim_uuid", "victims"."address" AS "victim_address", + "puns"."operator", "puns"."reason", + ${migratescopestart}(CASE + WHEN "puns"."scope_id" IS NULL THEN '' + ELSE "scopes"."value" + END)${migratescopeend} AS "scope", + "puns"."start", "puns"."end", + (CASE + WHEN "tracks"."namespace" IS NULL THEN NULL + ELSE (("tracks"."namespace" || ':') || "tracks"."value") + END) AS "track", + (CASE + WHEN "puns"."scope_id" IS NULL THEN ${zerosmallintliteral} + ELSE "scopes"."type" + END) AS "scope_type" + FROM "${tableprefix}mutes" AS "thetype" + INNER JOIN "${tableprefix}punishments" AS "puns" + ON "thetype"."id" = "puns"."id" + INNER JOIN "${tableprefix}victims" AS "victims" + ON "thetype"."victim" = "victims"."id" + LEFT JOIN "${tableprefix}tracks" AS "tracks" + ON "puns"."track" = "tracks"."id" + LEFT JOIN "${tableprefix}scopes" AS "scopes" + ON "puns"."scope_id" = "scopes"."id"; + +${alterviewstatement} "${tableprefix}applicable_mutes" AS + SELECT "puns"."id", "puns"."type", "puns"."victim_type", "puns"."victim_uuid", "puns"."victim_address", + "puns"."operator", "puns"."reason", "puns"."scope", "puns"."start", "puns"."end", "addrs"."uuid", "addrs"."address", + "puns"."track", "puns"."scope_type" + FROM "${tableprefix}simple_mutes" AS "puns" + INNER JOIN "${tableprefix}addresses" AS "addrs" + ON ("puns"."victim_type" = 0 AND "puns"."victim_uuid" = "addrs"."uuid" + OR "puns"."victim_type" = 1 AND "puns"."victim_address" = "addrs"."address" + OR "puns"."victim_type" = 2 AND ("puns"."victim_uuid" = "addrs"."uuid" OR "puns"."victim_address" = "addrs"."address")); + +-- Warns + +${alterviewstatement} "${tableprefix}simple_warns" AS + SELECT "puns"."id", "puns"."type", + "victims"."type" AS "victim_type", "victims"."uuid" AS "victim_uuid", "victims"."address" AS "victim_address", + "puns"."operator", "puns"."reason", + ${migratescopestart}(CASE + WHEN "puns"."scope_id" IS NULL THEN '' + ELSE "scopes"."value" + END)${migratescopeend} AS "scope", + "puns"."start", "puns"."end", + (CASE + WHEN "tracks"."namespace" IS NULL THEN NULL + ELSE (("tracks"."namespace" || ':') || "tracks"."value") + END) AS "track", + (CASE + WHEN "puns"."scope_id" IS NULL THEN ${zerosmallintliteral} + ELSE "scopes"."type" + END) AS "scope_type" + FROM "${tableprefix}warns" AS "thetype" + INNER JOIN "${tableprefix}punishments" AS "puns" + ON "thetype"."id" = "puns"."id" + INNER JOIN "${tableprefix}victims" AS "victims" + ON "thetype"."victim" = "victims"."id" + LEFT JOIN "${tableprefix}tracks" AS "tracks" + ON "puns"."track" = "tracks"."id" + LEFT JOIN "${tableprefix}scopes" AS "scopes" + ON "puns"."scope_id" = "scopes"."id"; + +${alterviewstatement} "${tableprefix}applicable_warns" AS + SELECT "puns"."id", "puns"."type", "puns"."victim_type", "puns"."victim_uuid", "puns"."victim_address", + "puns"."operator", "puns"."reason", "puns"."scope", "puns"."start", "puns"."end", "addrs"."uuid", "addrs"."address", + "puns"."track", "puns"."scope_type" + FROM "${tableprefix}simple_warns" AS "puns" + INNER JOIN "${tableprefix}addresses" AS "addrs" + ON ("puns"."victim_type" = 0 AND "puns"."victim_uuid" = "addrs"."uuid" + OR "puns"."victim_type" = 1 AND "puns"."victim_address" = "addrs"."address" + OR "puns"."victim_type" = 2 AND ("puns"."victim_uuid" = "addrs"."uuid" OR "puns"."victim_address" = "addrs"."address")); + +-- Other helpers + +${alterviewstatement} "${tableprefix}simple_history" AS + SELECT "puns"."id", "puns"."type", + "victims"."type" AS "victim_type", "victims"."uuid" AS "victim_uuid", "victims"."address" AS "victim_address", + "puns"."operator", "puns"."reason", + ${migratescopestart}(CASE + WHEN "puns"."scope_id" IS NULL THEN '' + ELSE "scopes"."value" + END)${migratescopeend} AS "scope", + "puns"."start", "puns"."end", + (CASE + WHEN "tracks"."namespace" IS NULL THEN NULL + ELSE (("tracks"."namespace" || ':') || "tracks"."value") + END) AS "track", + (CASE + WHEN "puns"."scope_id" IS NULL THEN ${zerosmallintliteral} + ELSE "scopes"."type" + END) AS "scope_type" + FROM "${tableprefix}history" AS "thetype" + INNER JOIN "${tableprefix}punishments" AS "puns" + ON "thetype"."id" = "puns"."id" + INNER JOIN "${tableprefix}victims" AS "victims" + ON "thetype"."victim" = "victims"."id" + LEFT JOIN "${tableprefix}tracks" AS "tracks" + ON "puns"."track" = "tracks"."id" + LEFT JOIN "${tableprefix}scopes" AS "scopes" + ON "puns"."scope_id" = "scopes"."id"; + +${alterviewstatement} "${tableprefix}simple_active" AS + SELECT "id", "type", "victim_type", "victim_uuid", "victim_address", "operator", "reason", "scope", "start", "end", "track", "scope_type" + FROM "${tableprefix}simple_bans" + UNION ALL + SELECT "id", "type", "victim_type", "victim_uuid", "victim_address", "operator", "reason", "scope", "start", "end", "track", "scope_type" + FROM "${tableprefix}simple_mutes" + UNION ALL + SELECT "id", "type", "victim_type", "victim_uuid", "victim_address", "operator", "reason", "scope", "start", "end", "track", "scope_type" + FROM "${tableprefix}simple_warns"; + +${alterviewstatement} "${tableprefix}applicable_active" AS + SELECT "id", "type", "victim_type", "victim_uuid", "victim_address", "operator", "reason", "scope", "start", "end", "uuid", "address", "track", "scope_type" + FROM "${tableprefix}applicable_bans" + UNION ALL + SELECT "id", "type", "victim_type", "victim_uuid", "victim_address", "operator", "reason", "scope", "start", "end", "uuid", "address", "track", "scope_type" + FROM "${tableprefix}applicable_mutes" + UNION ALL + SELECT "id", "type", "victim_type", "victim_uuid", "victim_address", "operator", "reason", "scope", "start", "end", "uuid", "address", "track", "scope_type" + FROM "${tableprefix}applicable_warns"; + +${alterviewstatement} "${tableprefix}applicable_history" AS + SELECT "puns"."id", "puns"."type", "puns"."victim_type", "puns"."victim_uuid", "puns"."victim_address", + "puns"."operator", "puns"."reason", "puns"."scope", "puns"."start", "puns"."end", "addrs"."uuid", "addrs"."address", + "puns"."track", "puns"."scope_type" + FROM "${tableprefix}simple_history" AS "puns" + INNER JOIN "${tableprefix}addresses" AS "addrs" + ON ("puns"."victim_type" = 0 AND "puns"."victim_uuid" = "addrs"."uuid" + OR "puns"."victim_type" = 1 AND "puns"."victim_address" = "addrs"."address" + OR "puns"."victim_type" = 2 AND ("puns"."victim_uuid" = "addrs"."uuid" OR "puns"."victim_address" = "addrs"."address")); diff --git a/bans-core/src/test/java/space/arim/libertybans/core/PillarOneReplacementModule.java b/bans-core/src/test/java/space/arim/libertybans/core/PillarOneReplacementModule.java index be6af5d12..658f7283e 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/PillarOneReplacementModule.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/PillarOneReplacementModule.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -16,6 +16,7 @@ * along with LibertyBans. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ + package space.arim.libertybans.core; import jakarta.inject.Singleton; @@ -34,7 +35,7 @@ public Configs configs(SpecifiedConfigs configs) { @Singleton public SettableTime time(ConfigSpec configSpec) { - return new SettableTimeImpl(configSpec.unixTime()); + return new SettableTimeImpl(configSpec.unixTimestamp()); } public Time time(SettableTime time) { diff --git a/bans-core/src/test/java/space/arim/libertybans/core/commands/CommandPackageTest.java b/bans-core/src/test/java/space/arim/libertybans/core/commands/CommandPackageTest.java index 9604197b4..db145c7d2 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/commands/CommandPackageTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/commands/CommandPackageTest.java @@ -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 @@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -176,4 +177,28 @@ public void multipleNormalAndHiddenArgsWithPeek(CommandPackageImpl impl) { assertFalse(cmd.hasNext()); } + @ParameterizedTest + @ArgumentsSource(CommandPackageImpl.Provider.class) + public void hiddenArgSpecifiedValue(CommandPackageImpl impl) { + CommandPackage cmd = impl.create("user1 -s -scope=hello 30d teaming -green in kitpvp"); + + assertFalse(cmd.findHiddenArgument("s"), "-s not yet visible"); + assertNull(cmd.findHiddenArgumentSpecifiedValue("scope"), "scope not yet visible"); + assertNull(cmd.findHiddenArgumentSpecifiedValue("track")); + + assertTrue(cmd.hasNext()); + assertEquals("user1", cmd.next()); + + assertTrue(cmd.hasNext()); + assertEquals("30d", cmd.next()); + + assertTrue(cmd.findHiddenArgument("s")); + assertEquals("hello", cmd.findHiddenArgumentSpecifiedValue("scope")); + assertFalse(cmd.findHiddenArgument("green")); + assertNull(cmd.findHiddenArgumentSpecifiedValue("track")); + + assertEquals("teaming -green in kitpvp", cmd.allRemaining()); + assertFalse(cmd.hasNext()); + } + } diff --git a/bans-core/src/test/java/space/arim/libertybans/core/commands/UnspecifiedReasonsTest.java b/bans-core/src/test/java/space/arim/libertybans/core/commands/UnspecifiedReasonsTest.java index 2c9a2b157..2533a95ff 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/commands/UnspecifiedReasonsTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/commands/UnspecifiedReasonsTest.java @@ -30,21 +30,24 @@ import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.punish.DraftPunishmentBuilder; import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.scope.ScopeManager; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.addon.exempt.Exemption; import space.arim.libertybans.core.commands.extra.ArgumentParser; -import space.arim.libertybans.core.config.AdditionAssistant; -import space.arim.libertybans.core.config.ParsedDuration; -import space.arim.libertybans.core.event.FireEventWithTimeout; -import space.arim.libertybans.core.punish.permission.DurationPermissionsConfig; +import space.arim.libertybans.core.commands.extra.ParseScope; import space.arim.libertybans.core.commands.extra.ReasonsConfig; import space.arim.libertybans.core.commands.extra.TabCompletion; +import space.arim.libertybans.core.config.AdditionAssistant; import space.arim.libertybans.core.config.AdditionsSection; import space.arim.libertybans.core.config.Configs; import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.config.MainConfig; import space.arim.libertybans.core.config.MessagesConfig; +import space.arim.libertybans.core.config.ParsedDuration; import space.arim.libertybans.core.env.CmdSender; import space.arim.libertybans.core.env.EnvUserResolver; +import space.arim.libertybans.core.event.FireEventWithTimeout; +import space.arim.libertybans.core.punish.permission.DurationPermissionsConfig; import space.arim.omnibus.DefaultOmnibus; import space.arim.omnibus.util.concurrent.impl.IndifferentFactoryOfTheFuture; @@ -84,7 +87,7 @@ public UnspecifiedReasonsTest(@Mock PunishmentDrafter drafter, @Mock InternalFor @BeforeEach public void setConfigSection(AbstractSubCommandGroup.Dependencies dependencies, /* Mock */ Configs configs, /* Mock */ ArgumentParser argParser, - @Mock EnvUserResolver envUserResolver) { + @Mock ScopeManager scopeManager, @Mock EnvUserResolver envUserResolver) { { MainConfig mainConfig = mock(MainConfig.class); when(configs.getMainConfig()).thenReturn(mainConfig); @@ -106,6 +109,10 @@ public void setConfigSection(AbstractSubCommandGroup.Dependencies dependencies, } when(argParser.parseVictim(any(), eq("A248"), any())).thenAnswer((i) -> new IndifferentFactoryOfTheFuture().completedFuture(PlayerVictim.of(UUID.randomUUID()))); + when(argParser.parseScope(any(), any(), any())).thenAnswer((invocation) -> { + return invocation.getArgument(2, ParseScope.class).defaultValue(scopeManager); + }); + when(scopeManager.defaultPunishingScope()).thenReturn(mock(ServerScope.class)); punishCommands = new PlayerPunishCommands( dependencies, drafter, formatter, diff --git a/bans-core/src/test/java/space/arim/libertybans/core/commands/extra/StandardArgumentParserTest.java b/bans-core/src/test/java/space/arim/libertybans/core/commands/extra/StandardArgumentParserTest.java index 37c15607e..ddc149dd3 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/commands/extra/StandardArgumentParserTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/commands/extra/StandardArgumentParserTest.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -36,11 +36,17 @@ import space.arim.libertybans.api.PlayerOperator; import space.arim.libertybans.api.PlayerVictim; import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.formatter.PunishmentFormatter; +import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.commands.CommandPackage; import space.arim.libertybans.core.commands.ComponentMatcher; import space.arim.libertybans.core.config.Configs; import space.arim.libertybans.core.config.MessagesConfig; +import space.arim.libertybans.core.config.ScopeConfig; import space.arim.libertybans.core.env.CmdSender; import space.arim.libertybans.core.env.UUIDAndAddress; +import space.arim.libertybans.core.scope.InternalScopeManager; +import space.arim.libertybans.core.scope.ScopeParsing; import space.arim.libertybans.core.uuid.UUIDManager; import space.arim.libertybans.it.util.RandomUtil; import space.arim.omnibus.util.concurrent.CentralisedFuture; @@ -53,8 +59,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -66,33 +74,38 @@ public class StandardArgumentParserTest { private final FactoryOfTheFuture futuresFactory = new IndifferentFactoryOfTheFuture(); private final Configs configs; + private final InternalScopeManager scopeManager; private final UUIDManager uuidManager; private final CmdSender sender; private final ArgumentParser argumentParser; private final MessagesConfig messagesConfig; - private final MessagesConfig.All.NotFound notFound; + private final MessagesConfig.All allConfig; + private final MessagesConfig.All.NotFound notFoundConfig; private final Component notFoundMessage = Component.text("Not found", NamedTextColor.RED); - public StandardArgumentParserTest(@Mock Configs configs, @Mock UUIDManager uuidManager, @Mock CmdSender sender, - @Mock MessagesConfig messagesConfig, @Mock MessagesConfig.All.NotFound notFound) { + public StandardArgumentParserTest( + @Mock Configs configs, @Mock InternalScopeManager scopeManager, @Mock PunishmentFormatter formatter, + @Mock UUIDManager uuidManager, @Mock CmdSender sender, @Mock MessagesConfig messagesConfig, + @Mock MessagesConfig.All allConfig, @Mock MessagesConfig.All.NotFound notFoundConfig) { this.configs = configs; + this.scopeManager = scopeManager; this.uuidManager = uuidManager; this.sender = sender; this.messagesConfig = messagesConfig; - this.notFound = notFound; + this.allConfig = allConfig; + this.notFoundConfig = notFoundConfig; - argumentParser = new StandardArgumentParser(futuresFactory, configs, uuidManager); + argumentParser = new StandardArgumentParser(futuresFactory, configs, scopeManager, formatter, uuidManager); } @BeforeEach public void setupMocks() { lenient().when(configs.getMessagesConfig()).thenReturn(messagesConfig).getMock(); - MessagesConfig.All all = mock(MessagesConfig.All.class); - lenient().when(messagesConfig.all()).thenReturn(all); - lenient().when(all.notFound()).thenReturn(notFound); + lenient().when(messagesConfig.all()).thenReturn(allConfig); + lenient().when(allConfig.notFound()).thenReturn(notFoundConfig); } private CentralisedFuture completedFuture(T value) { @@ -143,7 +156,7 @@ public void lookupPlayerVictim() { public void unknownPlayerVictimForName() { String name = "A248"; when(uuidManager.lookupUUID(name)).thenReturn(completedFuture(Optional.empty())); - when(notFound.player()).thenReturn(ComponentText.create(notFoundMessage)); + when(notFoundConfig.player()).thenReturn(ComponentText.create(notFoundMessage)); assertNull(parseVictim(name)); @@ -179,7 +192,7 @@ public void lookupPlayerVictim() { public void unknownPlayerVictimForName() { String name = "A248"; when(uuidManager.lookupPlayer(name)).thenReturn(completedFuture(Optional.empty())); - when(notFound.player()).thenReturn(ComponentText.create(notFoundMessage)); + when(notFoundConfig.player()).thenReturn(ComponentText.create(notFoundMessage)); assertNull(parseVictim(name)); @@ -231,7 +244,7 @@ public void unknownPlayerOperatorForName() { String name = "A248"; mockConsoleArguments(Set.of()); when(uuidManager.lookupUUID(name)).thenReturn(completedFuture(Optional.empty())); - when(notFound.player()).thenReturn(ComponentText.create(notFoundMessage)); + when(notFoundConfig.player()).thenReturn(ComponentText.create(notFoundMessage)); assertNull(parseOperator(name)); @@ -282,7 +295,7 @@ public void lookupAddressVictim() { public void unknownAddressVictimForName() { String name = "A248"; when(uuidManager.lookupAddress(name)).thenReturn(completedFuture(null)); - when(notFound.playerOrAddress()).thenReturn(ComponentText.create(notFoundMessage)); + when(notFoundConfig.playerOrAddress()).thenReturn(ComponentText.create(notFoundMessage)); assertNull(parseVictim(name)); @@ -291,5 +304,98 @@ public void unknownAddressVictimForName() { } } - + + @Nested + public class ParseScopeFromCommand { + + @BeforeEach + public void setup(@Mock ScopeConfig scopeConfig) { + lenient().when(scopeManager.parseFrom(any())).thenAnswer((invocation) -> { + return new ScopeParsing() { + @Override + public ServerScope specificScope(String server) { + return scopeManager.specificScope(server); + } + + @Override + public ServerScope category(String category) { + return scopeManager.category(category); + } + + @Override + public ServerScope globalScope() { + return scopeManager.globalScope(); + } + }.parseInputOptionally(invocation.getArgument(0)); + }); + when(configs.getScopeConfig()).thenReturn(scopeConfig); + when(scopeConfig.requirePermissions()).thenReturn(false); + } + + @Test + public void noneSpecified(@Mock CommandPackage command, + @Mock ServerScope defaultScope) { + when(scopeManager.defaultPunishingScope()).thenReturn(defaultScope); + when(command.findHiddenArgumentSpecifiedValue(any())).thenReturn(null); + assertEquals(defaultScope, argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender, never()).sendMessage(any()); + } + + @Test + public void serverScope(@Mock CommandPackage command, @Mock ServerScope scope) { + when(scopeManager.specificScope("lobby")).thenReturn(scope); + when(command.findHiddenArgumentSpecifiedValue("server")).thenReturn("lobby"); + assertEquals(scope, argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender, never()).sendMessage(any()); + } + + @Test + public void serverScopeLiteral(@Mock CommandPackage command, @Mock ServerScope scope) { + when(scopeManager.specificScope("lobby")).thenReturn(scope); + when(command.findHiddenArgumentSpecifiedValue("scope")).thenReturn("server:lobby"); + when(command.findHiddenArgumentSpecifiedValue(not(eq("scope")))).thenReturn(null); + assertEquals(scope, argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender, never()).sendMessage(any()); + } + + @Test + public void categoryScope(@Mock CommandPackage command, @Mock ServerScope scope) { + when(scopeManager.category("pvp")).thenReturn(scope); + when(command.findHiddenArgumentSpecifiedValue("category")).thenReturn("pvp"); + when(command.findHiddenArgumentSpecifiedValue(not(eq("category")))).thenReturn(null); + assertEquals(scope, argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender, never()).sendMessage(any()); + } + + @Test + public void categoryScopeLiteral(@Mock CommandPackage command, @Mock ServerScope scope) { + when(scopeManager.category("pvp")).thenReturn(scope); + when(command.findHiddenArgumentSpecifiedValue("scope")).thenReturn("category:pvp"); + when(command.findHiddenArgumentSpecifiedValue(not(eq("scope")))).thenReturn(null); + assertEquals(scope, argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender, never()).sendMessage(any()); + } + + @Test + public void globalLiteral(@Mock CommandPackage command, @Mock ServerScope globalScope) { + when(scopeManager.globalScope()).thenReturn(globalScope); + when(command.findHiddenArgumentSpecifiedValue("scope")).thenReturn(ScopeParsing.GLOBAL_SCOPE_USER_INPUT); + when(command.findHiddenArgumentSpecifiedValue(not(eq("scope")))).thenReturn(null); + assertEquals(globalScope, argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender, never()).sendMessage(any()); + } + + @Test + public void invalidScope(@Mock CommandPackage command, @Mock MessagesConfig.All.Scopes section) { + ComponentText errorMsg = ComponentText.create(Component.text("Not a valid scope")); + when(allConfig.scopes()).thenReturn(section); + when(section.invalid()).thenReturn(errorMsg); + + when(command.findHiddenArgumentSpecifiedValue("scope")).thenReturn("gibberish"); + when(command.findHiddenArgumentSpecifiedValue(not(eq("scope")))).thenReturn(null); + assertNull(argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender).sendMessage(errorMsg); + } + } + } diff --git a/bans-core/src/test/java/space/arim/libertybans/core/config/FormatterTest.java b/bans-core/src/test/java/space/arim/libertybans/core/config/FormatterTest.java index a2dddcfbf..2997e36c2 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/config/FormatterTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/config/FormatterTest.java @@ -1,7 +1,7 @@ /* * LibertyBans - * Copyright © 2020 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 @@ -99,7 +99,7 @@ public void setupMocks() { when(configs.getMessagesConfig()).thenReturn(messagesConfig); lenient().when(scopeManager.globalScope()).thenReturn(globalScope); - lenient().when(scopeManager.getServer(same(globalScope), any())).thenAnswer( + lenient().when(scopeManager.display(same(globalScope), any())).thenAnswer( invocationOnMock -> invocationOnMock.getArgument(1, String.class)); } @@ -333,7 +333,7 @@ private Punishment punishmentFor(FormatterTestInfo testInfo, Instant start, Inst private ServerScope specificScope(String server) { ServerScope scope = mock(ServerScope.class); - when(scopeManager.getServer(same(scope), any())).thenReturn(server); + when(scopeManager.display(same(scope), any())).thenReturn(server); return scope; } diff --git a/bans-core/src/test/java/space/arim/libertybans/core/config/SpecifiedConfigs.java b/bans-core/src/test/java/space/arim/libertybans/core/config/SpecifiedConfigs.java index a965968bb..3d894981a 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/config/SpecifiedConfigs.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/config/SpecifiedConfigs.java @@ -152,6 +152,11 @@ public ImportConfig getImportConfig() { return delegate.getImportConfig(); } + @Override + public ScopeConfig getScopeConfig() { + return delegate.getScopeConfig(); + } + @Override public CompletableFuture reloadConfigs() { return delegate.reloadConfigs(); diff --git a/bans-core/src/test/java/space/arim/libertybans/core/importing/AdvancedBanImportSourceTest.java b/bans-core/src/test/java/space/arim/libertybans/core/importing/AdvancedBanImportSourceTest.java index 8b4a3aa35..1d18b1aa9 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/importing/AdvancedBanImportSourceTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/importing/AdvancedBanImportSourceTest.java @@ -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 @@ -31,7 +31,6 @@ import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.api.scope.ServerScope; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.omnibus.util.UUIDUtil; import java.net.InetAddress; @@ -57,7 +56,7 @@ public class AdvancedBanImportSourceTest { private DSLContext context; private ImportSource importSource; - private ServerScope globalScope = ScopeImpl.GLOBAL; + private ServerScope globalScope; @BeforeEach public void setup(DSLContext context, ConnectionSource connectionSource) throws SQLException { diff --git a/bans-core/src/test/java/space/arim/libertybans/core/importing/BanManagerImportSourceTest.java b/bans-core/src/test/java/space/arim/libertybans/core/importing/BanManagerImportSourceTest.java index b00f14e99..e4e143ccb 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/importing/BanManagerImportSourceTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/importing/BanManagerImportSourceTest.java @@ -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 @@ -34,7 +34,6 @@ import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.api.scope.ServerScope; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.libertybans.it.util.RandomUtil; import space.arim.omnibus.util.UUIDUtil; @@ -63,7 +62,7 @@ public class BanManagerImportSourceTest { private ImportSource importSource; private final UUID consoleUuid = UUID.randomUUID(); - private ServerScope globalScope = ScopeImpl.GLOBAL; + private ServerScope globalScope; @BeforeEach public void setup(DSLContext context, ConnectionSource connectionSource) throws SQLException { diff --git a/bans-core/src/test/java/space/arim/libertybans/core/importing/LiteBansImportSourceTest.java b/bans-core/src/test/java/space/arim/libertybans/core/importing/LiteBansImportSourceTest.java index 77aa193db..c0e421b44 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/importing/LiteBansImportSourceTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/importing/LiteBansImportSourceTest.java @@ -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 @@ -31,7 +31,6 @@ import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.api.scope.ServerScope; -import space.arim.libertybans.core.scope.ScopeImpl; import java.io.IOException; import java.net.InetAddress; @@ -57,14 +56,17 @@ public class LiteBansImportSourceTest { private ImportSource importSource; private DSLContext context; - private final ServerScope globalScope = ScopeImpl.GLOBAL; - private final ServerScope kitpvpScope = ScopeImpl.specificServer("kitpvp"); - private final ServerScope lobbyScope = ScopeImpl.specificServer("lobby"); + private ServerScope globalScope; + private ServerScope kitpvpScope; + private ServerScope lobbyScope; @BeforeEach public void setup(DSLContext context, ConnectionSource connectionSource) throws SQLException, IOException { this.context = context; + globalScope = mock(ServerScope.class); + kitpvpScope = mock(ServerScope.class); + lobbyScope = mock(ServerScope.class); ScopeManager scopeManager = mock(ScopeManager.class); lenient().when(scopeManager.globalScope()).thenReturn(globalScope); lenient().when(scopeManager.specificScope("kitpvp")).thenReturn(kitpvpScope); diff --git a/bans-core/src/test/java/space/arim/libertybans/core/punish/IntelligentGuardianTest.java b/bans-core/src/test/java/space/arim/libertybans/core/punish/IntelligentGuardianTest.java index 74aec5e1b..c9608fa5e 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/punish/IntelligentGuardianTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/punish/IntelligentGuardianTest.java @@ -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 @@ -27,7 +27,9 @@ import org.mockito.junit.jupiter.MockitoExtension; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.core.config.Configs; +import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.config.MainConfig; import space.arim.libertybans.core.selector.EnforcementConfig; import space.arim.libertybans.core.selector.Guardian; @@ -65,11 +67,12 @@ public IntelligentGuardianTest(@Mock MuteCache muteCache) { } @BeforeEach - public void setup(@Mock Configs configs, @Mock InternalSelector selector, @Mock UUIDManager uuidManager) { + public void setup(@Mock Configs configs, @Mock ScopeManager scopeManager, @Mock InternalFormatter formatter, + @Mock InternalSelector selector, @Mock UUIDManager uuidManager) { uuid = UUID.randomUUID(); address = RandomUtil.randomAddress(); - guardian = new IntelligentGuardian(configs, futuresFactory, selector, uuidManager, muteCache); + guardian = new IntelligentGuardian(configs, futuresFactory, scopeManager, formatter, selector, uuidManager, muteCache); MainConfig mainConfig = mock(MainConfig.class); EnforcementConfig enforcementConfig = mock(EnforcementConfig.class); diff --git a/bans-core/src/test/java/space/arim/libertybans/core/punish/PunishmentBuilderTest.java b/bans-core/src/test/java/space/arim/libertybans/core/punish/PunishmentBuilderTest.java index 61abf0270..2a601dab6 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/punish/PunishmentBuilderTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/punish/PunishmentBuilderTest.java @@ -38,8 +38,9 @@ import space.arim.libertybans.api.punish.EscalationTrack; import space.arim.libertybans.api.punish.PunishmentDetailsCalculator; import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.scope.GlobalScope; import space.arim.libertybans.core.scope.InternalScopeManager; -import space.arim.libertybans.core.scope.ScopeImpl; +import space.arim.libertybans.core.scope.SpecificServerScope; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -49,6 +50,8 @@ import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -71,7 +74,8 @@ private void addBuilderParam(Class clazz, T value) { @BeforeEach public void setup() { when(enactor.scopeManager()).thenReturn(scopeManager); - when(scopeManager.globalScope()).thenReturn(ScopeImpl.GLOBAL); + when(scopeManager.globalScope()).thenReturn(GlobalScope.INSTANCE); + lenient().when(scopeManager.checkScope(any())).thenAnswer((i) -> i.getArgument(0)); addBuilderParam(Victim.class, PlayerVictim.of(UUID.randomUUID())); addBuilderParam(Operator.class, PlayerOperator.of(UUID.randomUUID())); @@ -120,7 +124,7 @@ public void draftPunishmentBuilderBuild() { addBuilderParam(PunishmentType.class, PunishmentType.BAN); addBuilderParam(String.class, "Test"); - addBuilderParam(ServerScope.class, ScopeImpl.specificServer("serveme!")); + addBuilderParam(ServerScope.class, new SpecificServerScope("serveme!")); addBuilderParam(Duration.class, Duration.ofHours(3L)); testBuilder( diff --git a/bans-core/src/test/java/space/arim/libertybans/core/selector/SelectionBaseSQLTest.java b/bans-core/src/test/java/space/arim/libertybans/core/selector/SelectionBaseSQLTest.java index cd5b90cc7..ef468351b 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/selector/SelectionBaseSQLTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/selector/SelectionBaseSQLTest.java @@ -31,6 +31,7 @@ import space.arim.libertybans.core.database.execute.QueryExecutor; import space.arim.libertybans.core.database.jooq.JooqContext; import space.arim.libertybans.core.punish.PunishmentCreator; +import space.arim.libertybans.core.scope.InternalScopeManager; import space.arim.libertybans.core.service.Time; import space.arim.omnibus.util.concurrent.impl.IndifferentFactoryOfTheFuture; @@ -48,7 +49,7 @@ public class SelectionBaseSQLTest { public void optimizedApplicabilityQuery(AddressStrictness strictness) { SelectionResources selectionResources = new SelectionResources( new IndifferentFactoryOfTheFuture(), () -> mock(QueryExecutor.class), - mock(PunishmentCreator.class), mock(Time.class) + mock(InternalScopeManager.class), mock(PunishmentCreator.class), mock(Time.class) ); UUID uuid = UUID.randomUUID(); NetworkAddress address = NetworkAddress.of(InetAddress.getLoopbackAddress()); @@ -67,9 +68,9 @@ public void optimizedApplicabilityQuery(AddressStrictness strictness) { private String expectedSql(AddressStrictness strictness) { return switch (strictness) { case LENIENT -> """ -select "libertybans_simple_bans"."victim_type", \ -"libertybans_simple_bans"."victim_uuid", "libertybans_simple_bans"."victim_address", \ -"libertybans_simple_bans"."operator", "libertybans_simple_bans"."reason", "libertybans_simple_bans"."scope", \ +select "libertybans_simple_bans"."victim_type", "libertybans_simple_bans"."victim_uuid", \ +"libertybans_simple_bans"."victim_address", "libertybans_simple_bans"."operator", \ +"libertybans_simple_bans"."reason", "libertybans_simple_bans"."scope_type", "libertybans_simple_bans"."scope", \ "libertybans_simple_bans"."start", "libertybans_simple_bans"."end", "libertybans_simple_bans"."track", \ "libertybans_simple_bans"."id" \ from "libertybans_simple_bans" where \ @@ -85,7 +86,7 @@ private String expectedSql(AddressStrictness strictness) { select "libertybans_applicable_bans"."victim_type", \ "libertybans_applicable_bans"."victim_uuid", "libertybans_applicable_bans"."victim_address", \ "libertybans_applicable_bans"."operator", "libertybans_applicable_bans"."reason", \ -"libertybans_applicable_bans"."scope", "libertybans_applicable_bans"."start", \ +"libertybans_applicable_bans"."scope_type", "libertybans_applicable_bans"."scope", "libertybans_applicable_bans"."start", \ "libertybans_applicable_bans"."end", "libertybans_applicable_bans"."track", "libertybans_applicable_bans"."id" \ from "libertybans_applicable_bans" where \ (("libertybans_applicable_bans"."end" = 0 or "libertybans_applicable_bans"."end" > cast(? as bigint)) \ @@ -97,7 +98,7 @@ private String expectedSql(AddressStrictness strictness) { select "libertybans_applicable_bans"."victim_type", \ "libertybans_applicable_bans"."victim_uuid", "libertybans_applicable_bans"."victim_address", \ "libertybans_applicable_bans"."operator", "libertybans_applicable_bans"."reason", \ -"libertybans_applicable_bans"."scope", "libertybans_applicable_bans"."start", \ +"libertybans_applicable_bans"."scope_type", "libertybans_applicable_bans"."scope", "libertybans_applicable_bans"."start", \ "libertybans_applicable_bans"."end", "libertybans_applicable_bans"."track", "libertybans_applicable_bans"."id" \ from "libertybans_applicable_bans" \ join "libertybans_strict_links" on "libertybans_applicable_bans"."uuid" = "libertybans_strict_links"."uuid1" \ @@ -111,7 +112,7 @@ private String expectedSql(AddressStrictness strictness) { select "libertybans_applicable_bans"."victim_type", \ "libertybans_applicable_bans"."victim_uuid", "libertybans_applicable_bans"."victim_address", \ "libertybans_applicable_bans"."operator", "libertybans_applicable_bans"."reason", \ -"libertybans_applicable_bans"."scope", "libertybans_applicable_bans"."start", \ +"libertybans_applicable_bans"."scope_type", "libertybans_applicable_bans"."scope", "libertybans_applicable_bans"."start", \ "libertybans_applicable_bans"."end", "libertybans_applicable_bans"."track", "libertybans_applicable_bans"."id" \ from "libertybans_applicable_bans" \ join "libertybans_strict_links" on "libertybans_applicable_bans"."uuid" = "libertybans_strict_links"."uuid1" \ diff --git a/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTime.java b/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTime.java index 8b1c68352..e8e471478 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTime.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTime.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -37,4 +37,11 @@ public interface SettableTime extends Time { * @param progression the progression */ void advanceBy(Duration progression); + + /** + * Sets the time back to the original test timestamp + * + */ + void reset(); + } diff --git a/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTimeImpl.java b/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTimeImpl.java index 010118069..a2c7d42d1 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTimeImpl.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTimeImpl.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -25,10 +25,12 @@ public final class SettableTimeImpl implements SettableTime { + private final Instant original; /** Milliseconds from the epoch */ private final AtomicLong timestamp; public SettableTimeImpl(Instant timestamp) { + original = timestamp; this.timestamp = new AtomicLong(timestamp.toEpochMilli()); } @@ -51,4 +53,10 @@ public long currentTime() { public long arbitraryNanoTime() { return timestamp.get() * 1_000_000L; // nanoseconds } + + @Override + public void reset() { + setTimestamp(original); + } + } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpec.java b/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpec.java index 2d7eb01f7..b9460ae06 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpec.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpec.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -16,71 +16,29 @@ * along with LibertyBans. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ + package space.arim.libertybans.it; import space.arim.libertybans.core.database.Vendor; import space.arim.libertybans.api.select.AddressStrictness; +import space.arim.libertybans.core.env.InstanceType; import space.arim.libertybans.core.uuid.ServerType; import java.time.Instant; import java.util.Objects; -public final class ConfigSpec { - - private final Vendor vendor; - private final AddressStrictness addressStrictness; - private final ServerType serverType; - private final long unixTime; - - ConfigSpec(Vendor vendor, AddressStrictness addressStrictness, ServerType serverType, long unixTime) { - this.vendor = Objects.requireNonNull(vendor, "vendor"); - this.addressStrictness = Objects.requireNonNull(addressStrictness, "addressStrictness"); - this.serverType = Objects.requireNonNull(serverType, "serverType"); - this.unixTime = unixTime; - } - - public Vendor vendor() { - return vendor; - } - - public AddressStrictness addressStrictness() { - return addressStrictness; - } +public record ConfigSpec(Vendor vendor, AddressStrictness addressStrictness, ServerType serverType, + InstanceType instanceType, boolean pluginMessaging, long unixTime) { - public ServerType serverType() { - return serverType; + public ConfigSpec { + Objects.requireNonNull(vendor, "vendor"); + Objects.requireNonNull(addressStrictness, "addressStrictness"); + Objects.requireNonNull(serverType, "serverType"); + Objects.requireNonNull(instanceType, "instanceType"); } - public Instant unixTime() { + public Instant unixTimestamp() { return Instant.ofEpochSecond(unixTime); } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ConfigSpec that = (ConfigSpec) o; - return unixTime == that.unixTime && vendor == that.vendor - && addressStrictness == that.addressStrictness && serverType == that.serverType; - } - - @Override - public int hashCode() { - int result = vendor.hashCode(); - result = 31 * result + addressStrictness.hashCode(); - result = 31 * result + serverType.hashCode(); - result = 31 * result + (int) (unixTime ^ (unixTime >>> 32)); - return result; - } - - @Override - public String toString() { - return "ConfigSpec{" + - "vendor=" + vendor + - ", addressStrictness=" + addressStrictness + - ", serverType=" + serverType + - ", unixTime=" + unixTime + - '}'; - } - } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpecPossiblities.java b/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpecPossiblities.java index a87632e76..140d03f2c 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpecPossiblities.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpecPossiblities.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -20,10 +20,12 @@ import space.arim.libertybans.core.database.Vendor; import space.arim.libertybans.api.select.AddressStrictness; +import space.arim.libertybans.core.env.InstanceType; import space.arim.libertybans.core.uuid.ServerType; import java.lang.reflect.AnnotatedElement; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import java.util.stream.Stream; @@ -32,22 +34,24 @@ class ConfigSpecPossiblities { private final AnnotatedElement element; ConfigSpecPossiblities(AnnotatedElement element) { - this.element = element; + this.element = Objects.requireNonNull(element); } - private Stream getAllPossible(long time) { + private Stream getAllPossible(InstanceType instanceType, boolean pluginMessaging, long time) { Set possibilities = new HashSet<>(); for (Vendor vendor : Vendor.values()) { for (AddressStrictness addressStrictness : AddressStrictness.values()) { for (ServerType serverType : ServerType.values()) { - possibilities.add(new ConfigSpec(vendor, addressStrictness, serverType, time)); + possibilities.add(new ConfigSpec( + vendor, addressStrictness, serverType, instanceType, pluginMessaging, time + )); } } } return possibilities.stream(); } - private ConfigConstraints getConstraints() { + private ConfigConstraints getConstraints(PlatformSpecs platformSpecs) { if (element.getAnnotation(NoDbAccess.class) != null) { return new ConfigConstraints( Set.of(Vendor.HSQLDB), Set.of(AddressStrictness.NORMAL), Set.of(ServerType.ONLINE)); @@ -64,15 +68,13 @@ private ConfigConstraints getConstraints() { } } Set serverTypes; - { - SetServerType serverTypeConstraint = element.getAnnotation(SetServerType.class); - if (serverTypeConstraint == null) { - serverTypes = Set.of(ServerType.ONLINE); - } else if (serverTypeConstraint.all()) { - serverTypes = Set.of(ServerType.values()); - } else { - serverTypes = Set.of(serverTypeConstraint.value()); - } + PlatformSpecs.ServerTypes serverTypeConstraint; + if (platformSpecs == null) { + serverTypes = Set.of(ServerType.ONLINE); + } else if ((serverTypeConstraint = platformSpecs.serverTypes()).all()) { + serverTypes = Set.of(ServerType.values()); + } else { + serverTypes = Set.of(serverTypeConstraint.value()); } Set vendors; { @@ -86,39 +88,38 @@ private ConfigConstraints getConstraints() { return new ConfigConstraints(vendors, addressStrictnesses, serverTypes); } - private static class ConfigConstraints { - - private final Set strictnesses; - private final Set vendors; - private final Set serverTypes; - - ConfigConstraints(Set vendors, Set strictnesses, Set serverTypes) { - - this.vendors = vendors; - this.strictnesses = strictnesses; - this.serverTypes = serverTypes; - } + private record ConfigConstraints(Set vendors, Set strictnesses, + Set serverTypes) { boolean allows(ConfigSpec configSpec) { - return vendors.contains(configSpec.vendor()) - && strictnesses.contains(configSpec.addressStrictness()) - && serverTypes.contains(configSpec.serverType()); + return vendors.contains(configSpec.vendor()) + && strictnesses.contains(configSpec.addressStrictness()) + && serverTypes.contains(configSpec.serverType()); } } Stream getAll() { - long defaultTime = SetTime.DEFAULT_TIME; - if (element == null) { - return getAllPossible(defaultTime); - } - long time = defaultTime; + long time; SetTime setTime = element.getAnnotation(SetTime.class); - if (setTime != null) { + if (setTime == null) { + time = SetTime.DEFAULT_TIME; + } else { time = setTime.unixTime(); } - Stream configurations = getAllPossible(time); - ConfigConstraints constraints = getConstraints(); + InstanceType instanceType; + boolean pluginMessaging; + PlatformSpecs platformSpecs = element.getAnnotation(PlatformSpecs.class); + if (platformSpecs == null) { + // Make sure these defaults match those in PlatformSpecs + instanceType = InstanceType.PROXY; + pluginMessaging = false; + } else { + instanceType = platformSpecs.instanceType(); + pluginMessaging = platformSpecs.pluginMessaging(); + } + Stream configurations = getAllPossible(instanceType, pluginMessaging, time); + ConfigConstraints constraints = getConstraints(platformSpecs); return configurations.filter(constraints::allows); } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/DatabaseInstance.java b/bans-core/src/test/java/space/arim/libertybans/it/DatabaseInstance.java index 16599edca..caf9c3dd3 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/DatabaseInstance.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/DatabaseInstance.java @@ -92,14 +92,13 @@ Optional createInfo() { } private void createDatabase(String database) { - switch (this) { - case MARIADB_RETRO, MARIADB_LEGACY, MARIADB_MODERN, MYSQL -> { - createDatabaseUsing("jdbc:mariadb", database, " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); - } - case POSTGRES_LEGACY, POSTGRES_MODERN, COCKROACHDB -> { - createDatabaseUsing("jdbc:postgresql", database, ""); - } - default -> throw new IllegalStateException("No database creation exists"); + switch (vendor) { + case MARIADB, MYSQL -> + createDatabaseUsing("jdbc:mariadb", database, " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); + case POSTGRES, COCKROACH -> + createDatabaseUsing("jdbc:postgresql", database, ""); + default -> + throw new IllegalStateException("No database creation exists for " + this); } } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/InjectionInvocationContextProvider.java b/bans-core/src/test/java/space/arim/libertybans/it/InjectionInvocationContextProvider.java index c6094e15f..28db6eb57 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/InjectionInvocationContextProvider.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/InjectionInvocationContextProvider.java @@ -47,37 +47,28 @@ public Stream provideTestTemplateInvocationContex boolean irrelevantData = context.getRequiredTestMethod().isAnnotationPresent(IrrelevantData.class); ResourceCreator creator = new ResourceCreator(context.getRoot().getStore(NAMESPACE)); - return new ConfigSpecPossiblities(context.getElement().orElse(null)) + return new ConfigSpecPossiblities(context.getElement().orElseThrow()) .getAll() .flatMap((throwaway) ? creator::createIsolated : creator::create) .map((injector) -> new InjectorInvocationContext(injector, throwaway, irrelevantData)); } - private static class InjectorInvocationContext implements TestTemplateInvocationContext { - - private final Injector injector; - private final boolean throwaway; - private final boolean irrelevantData; - - InjectorInvocationContext(Injector injector, boolean throwaway, boolean irrelevantData) { - this.injector = injector; - this.throwaway = throwaway; - this.irrelevantData = irrelevantData; - } + private record InjectorInvocationContext(Injector injector, boolean throwaway, + boolean irrelevantData) implements TestTemplateInvocationContext { @Override - public List getAdditionalExtensions() { - List extensions = new ArrayList<>(3); - extensions.add(new InjectorParameterResolver(injector)); - if (!throwaway) { - extensions.add(new InjectorCleanupCallback(injector)); - } - if (irrelevantData) { - extensions.add(new IrrelevantDataCallback(injector)); + public List getAdditionalExtensions() { + List extensions = new ArrayList<>(3); + extensions.add(new InjectorParameterResolver(injector)); + if (!throwaway) { + extensions.add(new InjectorCleanupCallback(injector)); + } + if (irrelevantData) { + extensions.add(new IrrelevantDataCallback(injector)); + } + return extensions; } - return extensions; - } - } + } } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/InjectorCleanupCallback.java b/bans-core/src/test/java/space/arim/libertybans/it/InjectorCleanupCallback.java index 39699850c..e223826db 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/InjectorCleanupCallback.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/InjectorCleanupCallback.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -21,14 +21,11 @@ import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; -import space.arim.injector.Identifier; import space.arim.injector.Injector; import space.arim.libertybans.core.database.InternalDatabase; import space.arim.libertybans.core.punish.sync.SQLSynchronizationMessenger; import space.arim.libertybans.core.service.SettableTime; -import java.time.Instant; - final class InjectorCleanupCallback implements AfterEachCallback { private final Injector injector; @@ -42,8 +39,7 @@ public void afterEach(ExtensionContext context) throws Exception { // Reset database injector.request(InternalDatabase.class).truncateAllTables(); // Reset global clock - Instant startTime = injector.request(Identifier.ofTypeAndNamed(Instant.class, "testStartTime")); - injector.request(SettableTime.class).setTimestamp(startTime); + injector.request(SettableTime.class).reset(); // Reset synchronization injector.request(SQLSynchronizationMessenger.class).resetLastTimestamp(); } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/PlatformSpecs.java b/bans-core/src/test/java/space/arim/libertybans/it/PlatformSpecs.java new file mode 100644 index 000000000..2dd3c0868 --- /dev/null +++ b/bans-core/src/test/java/space/arim/libertybans/it/PlatformSpecs.java @@ -0,0 +1,65 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.it; + +import space.arim.libertybans.core.env.InstanceType; +import space.arim.libertybans.core.uuid.ServerType; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Details related to the platform implementation + * + */ +// Thankfully, annotations implement sane equals and hashCode +@Retention(RUNTIME) +@Target(METHOD) +public @interface PlatformSpecs { + + ServerTypes serverTypes() default @ServerTypes(value = {}, all = true); + + // Make sure these defaults match those in ConfigSpecPossibilities + + InstanceType instanceType() default InstanceType.PROXY; + + boolean pluginMessaging() default false; + + @interface ServerTypes { + + /** + * Sets the server types + * + * @return the server types to test + */ + ServerType[] value(); + + /** + * Whether to use all server types. Overrides the value + * + * @return true to use all types + */ + boolean all() default false; + + } +} diff --git a/bans-core/src/test/java/space/arim/libertybans/it/ResourceCreator.java b/bans-core/src/test/java/space/arim/libertybans/it/ResourceCreator.java index eeb138ab9..2315db010 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/ResourceCreator.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/ResourceCreator.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -29,12 +29,12 @@ import space.arim.libertybans.core.CommandsModule; import space.arim.libertybans.core.PillarOneReplacementModule; import space.arim.libertybans.core.PillarTwoBindModule; +import space.arim.libertybans.core.env.InstanceType; import space.arim.libertybans.it.env.QuackBindModule; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.time.Instant; import java.util.Optional; import java.util.function.BiFunction; import java.util.stream.Stream; @@ -79,11 +79,9 @@ private Optional createSingle(ConfigSpec configSpec, DatabaseInstance Injector injector = new InjectorBuilder() .bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), tempDirectory) + .bindInstance(InstanceType.class, InstanceType.PROXY) .bindInstance(ConfigSpec.class, configSpec) .bindInstance(DatabaseInfo.class, databaseInfo) - // The next two bindings are for ITs only - .bindInstance(DatabaseInstance.class, database) - .bindInstance(Identifier.ofTypeAndNamed(Instant.class, "testStartTime"), configSpec.unixTime()) .addBindModules( new ApiBindModule(), new PillarOneReplacementModule(), diff --git a/bans-core/src/test/java/space/arim/libertybans/it/SetServerType.java b/bans-core/src/test/java/space/arim/libertybans/it/SetServerType.java index 00914f735..76063031f 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/SetServerType.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/SetServerType.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -31,18 +31,6 @@ @Target(METHOD) public @interface SetServerType { - /** - * Sets the server types - * - * @return the server types to test - */ - ServerType[] value() default {}; - - /** - * Whether to use all server types. Overrides {@code value} - * - * @return true to use all server types - */ - boolean all() default false; + ServerType value(); } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/env/QuackBindModule.java b/bans-core/src/test/java/space/arim/libertybans/it/env/QuackBindModule.java index c580dfb77..ee0a4532e 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/env/QuackBindModule.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/env/QuackBindModule.java @@ -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 @@ -22,6 +22,8 @@ import jakarta.inject.Singleton; import space.arim.api.env.PlatformHandle; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; import space.arim.libertybans.core.importing.PlatformImportSource; @@ -59,6 +61,14 @@ public EnvUserResolver resolver(QuackUserResolver resolver) { return resolver; } + public EnvMessageChannel messageChannel(EnvMessageChannel.NoOp messageChannel) { + return messageChannel; + } + + public EnvServerNameDetection serverNameDetection() { + return (scopeManager) -> {}; + } + public PlatformImportSource platformImportSource() { throw new UnsupportedOperationException("PlatformImportSource not available"); } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/env/QuackEnforcer.java b/bans-core/src/test/java/space/arim/libertybans/it/env/QuackEnforcer.java index f5628024c..cd73c52c4 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/env/QuackEnforcer.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/env/QuackEnforcer.java @@ -25,12 +25,15 @@ import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.env.AbstractEnvEnforcer; import space.arim.libertybans.core.env.Interlocutor; +import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.libertybans.it.env.platform.QuackPlatform; import space.arim.libertybans.it.env.platform.QuackPlayer; +import space.arim.libertybans.it.env.platform.ReceivedPluginMessage; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.net.InetAddress; +import java.util.Collection; import java.util.UUID; import java.util.function.Consumer; @@ -46,8 +49,8 @@ public QuackEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter format } @Override - protected CentralisedFuture doForAllPlayers(Consumer callback) { - platform.getAllPlayers().forEach(callback); + public CentralisedFuture doForAllPlayers(Consumer> callback) { + callback.accept(platform.getAllPlayers()); return completedVoid(); } @@ -62,6 +65,12 @@ public void kickPlayer(QuackPlayer player, Component message) { player.kickPlayer(message); } + @Override + public boolean sendPluginMessageIfListening(QuackPlayer player, PluginMessage pluginMessage, D data) { + player.receivedPluginMessages().add(new ReceivedPluginMessage<>(pluginMessage, data)); + return true; + } + @Override public UUID getUniqueIdFor(QuackPlayer player) { return player.getUniqueId(); @@ -72,6 +81,11 @@ public InetAddress getAddressFor(QuackPlayer player) { return player.getAddress(); } + @Override + public String getNameFor(QuackPlayer player) { + return player.getName(); + } + @Override public boolean hasPermission(QuackPlayer player, String permission) { return player.hasPermission(permission); diff --git a/bans-core/src/test/java/space/arim/libertybans/it/env/platform/PlatformSpecsEqualityTest.java b/bans-core/src/test/java/space/arim/libertybans/it/env/platform/PlatformSpecsEqualityTest.java new file mode 100644 index 000000000..4df180017 --- /dev/null +++ b/bans-core/src/test/java/space/arim/libertybans/it/env/platform/PlatformSpecsEqualityTest.java @@ -0,0 +1,60 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.it.env.platform; + +import org.junit.jupiter.api.Test; +import space.arim.libertybans.core.env.InstanceType; +import space.arim.libertybans.it.PlatformSpecs; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +public class PlatformSpecsEqualityTest { + + private boolean equalityFor(String otherMethod) throws NoSuchMethodException { + var thisClass = getClass(); + Method firstMethod = thisClass.getDeclaredMethod("value1"); + Method secondMethod = thisClass.getDeclaredMethod(otherMethod); + PlatformSpecs firstAnnotation = firstMethod.getAnnotation(PlatformSpecs.class); + PlatformSpecs secondAnnotation = secondMethod.getAnnotation(PlatformSpecs.class); + return firstAnnotation.equals(secondAnnotation); + } + + @Test + public void isEqual() throws NoSuchMethodException { + assertFalse(equalityFor("value2")); + assertTrue(equalityFor("value3")); + assertFalse(equalityFor("value4")); + } + + @PlatformSpecs(instanceType = InstanceType.PROXY, pluginMessaging = false) + void value1() {} + @PlatformSpecs(instanceType = InstanceType.GAME_SERVER) + void value2() {} + @PlatformSpecs(instanceType = InstanceType.PROXY, pluginMessaging = false) + void value3() {} + @PlatformSpecs(instanceType = InstanceType.PROXY, pluginMessaging = true) + void value4() {} + +} diff --git a/bans-core/src/test/java/space/arim/libertybans/it/env/platform/QuackPlayer.java b/bans-core/src/test/java/space/arim/libertybans/it/env/platform/QuackPlayer.java index e75d85323..35081b255 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/env/platform/QuackPlayer.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/env/platform/QuackPlayer.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -28,6 +28,7 @@ import space.arim.omnibus.util.ThisClass; import java.net.InetAddress; +import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -39,6 +40,7 @@ public class QuackPlayer implements MessageOnlyAudience { private final InetAddress address; private final Set permissions; + private final Set> receivedPluginMessages = new HashSet<>(); private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); @@ -79,6 +81,10 @@ public void readdToPlatform() { platform.addPlayer(this); } + public Set> receivedPluginMessages() { + return receivedPluginMessages; + } + @Override public void sendMessage(@NonNull Identity source, @NonNull Component message, @NonNull MessageType type) { String displayMessage = platform.toDisplay(message); diff --git a/bans-core/src/test/java/space/arim/libertybans/it/env/platform/ReceivedPluginMessage.java b/bans-core/src/test/java/space/arim/libertybans/it/env/platform/ReceivedPluginMessage.java new file mode 100644 index 000000000..09b49a392 --- /dev/null +++ b/bans-core/src/test/java/space/arim/libertybans/it/env/platform/ReceivedPluginMessage.java @@ -0,0 +1,25 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.it.env.platform; + +import space.arim.libertybans.core.env.message.PluginMessage; + +public record ReceivedPluginMessage(PluginMessage pluginMessage, D data) { +} diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/applicable/OptimizedQueriesIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/applicable/OptimizedQueriesIT.java index 86c1747e9..b1b012ff7 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/applicable/OptimizedQueriesIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/applicable/OptimizedQueriesIT.java @@ -92,9 +92,8 @@ private String renderHandwrittenQuery(DSLContext context, UUID uuid, NetworkAddr case LENIENT -> context .select( simpleView.victimType(), simpleView.victimUuid(), simpleView.victimAddress(), - simpleView.operator(), simpleView.reason(), - simpleView.scope(), simpleView.start(), simpleView.end(), simpleView.track(), - simpleView.id() + simpleView.operator(), simpleView.reason(), simpleView.scopeType(), simpleView.scope(), + simpleView.start(), simpleView.end(), simpleView.track(), simpleView.id() ) .from(simpleView.table()) .where(new EndTimeCondition(simpleView).isNotExpired(Instant.EPOCH)) @@ -104,9 +103,8 @@ private String renderHandwrittenQuery(DSLContext context, UUID uuid, NetworkAddr case NORMAL -> context .select( applView.victimType(), applView.victimUuid(), applView.victimAddress(), - applView.operator(), applView.reason(), - applView.scope(), applView.start(), applView.end(), applView.track(), - applView.id() + applView.operator(), applView.reason(), applView.scopeType(), applView.scope(), + applView.start(), applView.end(), applView.track(), applView.id() ).from(applView.table()) .where(new EndTimeCondition(applView).isNotExpired(Instant.EPOCH)) .and(applView.uuid().eq(uuid)) @@ -115,9 +113,8 @@ private String renderHandwrittenQuery(DSLContext context, UUID uuid, NetworkAddr case STERN, STRICT -> context .select( applView.victimType(), applView.victimUuid(), applView.victimAddress(), - applView.operator(), applView.reason(), - applView.scope(), applView.start(), applView.end(), applView.track(), - applView.id() + applView.operator(), applView.reason(), applView.scopeType(), applView.scope(), + applView.start(), applView.end(), applView.track(), applView.id() ).from(applView.table()) .innerJoin(STRICT_LINKS) .on(applView.uuid().eq(STRICT_LINKS.UUID1)) diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/database/DatabaseRequirementsIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/database/DatabaseRequirementsIT.java index 941cc12d9..2e2d9e7fb 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/database/DatabaseRequirementsIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/database/DatabaseRequirementsIT.java @@ -23,8 +23,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import space.arim.libertybans.core.database.DatabaseManager; import space.arim.libertybans.core.database.DatabaseRequirements; +import space.arim.libertybans.core.database.InternalDatabase; import space.arim.libertybans.core.database.Vendor; -import space.arim.libertybans.it.DatabaseInstance; import space.arim.libertybans.it.InjectionInvocationContextProvider; import java.sql.Connection; @@ -36,10 +36,10 @@ public class DatabaseRequirementsIT { @TestTemplate - public void databaseVersion(DatabaseManager databaseManager, - DatabaseInstance databaseInstance) throws SQLException { - Vendor vendor = databaseInstance.getVendor(); - try (Connection connection = databaseManager.getInternal().getConnection()) { + public void databaseVersion(DatabaseManager databaseManager) throws SQLException { + InternalDatabase database = databaseManager.getInternal(); + Vendor vendor = database.getVendor(); + try (Connection connection = database.getConnection()) { assertDoesNotThrow( new DatabaseRequirements(vendor, connection)::checkRequirementsAndYieldRetroSupport); } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/MigrationResult.java b/bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/MigrationResult.java deleted file mode 100644 index f9d163758..000000000 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/MigrationResult.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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 - * and navigate to version 3 of the GNU Affero General Public License. - */ - -package space.arim.libertybans.it.test.database.migrate08; - -import org.jooq.DSLContext; -import space.arim.libertybans.api.NetworkAddress; -import space.arim.libertybans.api.Operator; -import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.api.Victim; -import space.arim.libertybans.api.punish.Punishment; -import space.arim.libertybans.core.importing.NameAddressRecord; -import space.arim.libertybans.core.punish.PunishmentCreator; -import space.arim.libertybans.core.scope.ScopeImpl; -import space.arim.libertybans.core.service.Time; - -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; - -import static space.arim.libertybans.core.schema.tables.SimpleActive.SIMPLE_ACTIVE; -import static space.arim.libertybans.core.schema.tables.SimpleHistory.SIMPLE_HISTORY; - -record MigrationResult(List activePunishments, - List historicalPunishments) { - - MigrationResult { - activePunishments = List.copyOf(activePunishments); - historicalPunishments = List.copyOf(historicalPunishments); - } - - static MigrationResult retrieveFrom(DSLContext context, PunishmentCreator creator) { - return new MigrationResult( - context - .selectFrom(SIMPLE_ACTIVE) - .orderBy(SIMPLE_ACTIVE.START) - .fetch(creator.punishmentMapper()), - context - .selectFrom(SIMPLE_HISTORY) - .orderBy(SIMPLE_HISTORY.START) - .fetch(creator.punishmentMapper()) - ); - } - - static final class Builder { - - private final Time time; - private final ZeroeightInterlocutor zeroeightInterlocutor; - private final PunishmentCreator creator; - - private final List activePunishments = new ArrayList<>(); - private final List historicalPunishments = new ArrayList<>(); - - private final AtomicInteger idGenerator = new AtomicInteger(); - - Builder(Time time, ZeroeightInterlocutor zeroeightInterlocutor, PunishmentCreator creator) { - this.time = Objects.requireNonNull(time, "time"); - this.zeroeightInterlocutor = Objects.requireNonNull(zeroeightInterlocutor, "zeroeightInterlocutor"); - this.creator = Objects.requireNonNull(creator, "creator"); - } - - Builder addUser(UUID uuid, String name, NetworkAddress address) { - Instant currentTime = time.currentTimestamp(); - NameAddressRecord nameAddressRecord = new NameAddressRecord(uuid, name, address, currentTime); - zeroeightInterlocutor.insertUser(nameAddressRecord); - return this; - } - - void addActivePunishment(PunishmentType type, Victim victim, - Operator operator, String reason, Duration duration) { - addPunishment(type, victim, operator, reason, duration, true); - } - - void addHistoricalPunishment(PunishmentType type, Victim victim, - Operator operator, String reason, Duration duration) { - addPunishment(type, victim, operator, reason, duration, false); - } - - private void addPunishment(PunishmentType type, Victim victim, - Operator operator, String reason, Duration duration, - boolean active) { - Instant start = time.currentTimestamp(); - Instant end; - if (duration.equals(Duration.ZERO)) { // Permanent - end = Instant.MAX; - } else { - end = start.plus(duration); - } - long id = idGenerator.getAndIncrement(); - Punishment punishment = creator.createPunishment( - id, type, victim, operator, reason, ScopeImpl.GLOBAL, start, end, null - ); - zeroeightInterlocutor.insertPunishment(punishment, active); - if (active) { - activePunishments.add(punishment); - } - historicalPunishments.add(punishment); - - } - - MigrationResult build() { - return new MigrationResult(activePunishments, historicalPunishments); - } - } -} diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/ZeroeightInterlocutor.java b/bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/ZeroeightInterlocutor.java deleted file mode 100644 index 54e04e773..000000000 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/ZeroeightInterlocutor.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * LibertyBans - * Copyright © 2021 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 - * and navigate to version 3 of the GNU Affero General Public License. - */ - -package space.arim.libertybans.it.test.database.migrate08; - -import org.flywaydb.core.Flyway; -import org.jooq.DSLContext; -import org.jooq.Record3; -import org.jooq.SQLDialect; -import org.jooq.Table; -import org.jooq.impl.TableImpl; -import space.arim.libertybans.api.AddressVictim; -import space.arim.libertybans.api.NetworkAddress; -import space.arim.libertybans.api.PlayerVictim; -import space.arim.libertybans.api.Victim; -import space.arim.libertybans.api.punish.Punishment; -import space.arim.libertybans.core.database.DatabaseConstants; -import space.arim.libertybans.core.database.jooq.OperatorBinding; -import space.arim.libertybans.core.importing.NameAddressRecord; -import space.arim.libertybans.core.schema.Sequences; -import space.arim.omnibus.util.UUIDUtil; - -import javax.sql.DataSource; -import java.sql.SQLException; -import java.time.Instant; -import java.util.Map; -import java.util.UUID; - -import static org.jooq.impl.DSL.table; -import static space.arim.libertybans.core.database.DatabaseConstants.LIBERTYBANS_08X_FLYWAY_TABLE; -import static space.arim.libertybans.core.schema.tables.SchemaHistory.SCHEMA_HISTORY; -import static space.arim.libertybans.core.schema.tables.ZeroeightAddresses.ZEROEIGHT_ADDRESSES; -import static space.arim.libertybans.core.schema.tables.ZeroeightBans.ZEROEIGHT_BANS; -import static space.arim.libertybans.core.schema.tables.ZeroeightHistory.ZEROEIGHT_HISTORY; -import static space.arim.libertybans.core.schema.tables.ZeroeightMutes.ZEROEIGHT_MUTES; -import static space.arim.libertybans.core.schema.tables.ZeroeightNames.ZEROEIGHT_NAMES; -import static space.arim.libertybans.core.schema.tables.ZeroeightPunishments.ZEROEIGHT_PUNISHMENTS; -import static space.arim.libertybans.core.schema.tables.ZeroeightWarns.ZEROEIGHT_WARNS; - -final class ZeroeightInterlocutor { - - private final DSLContext context; - - ZeroeightInterlocutor(DSLContext context) { - this.context = context; - } - - private void dropAllTables() { - SQLDialect dialect = context.family(); - // Drop views - for (Table table : DatabaseConstants.allViews()) { - context.dropView(table).execute(); - } - // Drop tables - for (Table table : DatabaseConstants.allTables(DatabaseConstants.TableOrder.REFERENTS_LAST)) { - context.dropTable(table).execute(); - } - context.dropTable(SCHEMA_HISTORY).execute(); - // Drop sequences - if (dialect == SQLDialect.MYSQL) { - context.dropTable(table(Sequences.LIBERTYBANS_PUNISHMENT_IDS.getName())).execute(); - context.dropTable(table(Sequences.LIBERTYBANS_VICTIM_IDS.getName())).execute(); - } else { - context.dropSequence(Sequences.LIBERTYBANS_PUNISHMENT_IDS).execute(); - context.dropSequence(Sequences.LIBERTYBANS_VICTIM_IDS).execute(); - } - } - - void prepareTables(DataSource dataSource) throws SQLException { - // Delete existing tables - dropAllTables(); - // Create 0.8.x tables using Flyway - //noinspection deprecation - Flyway - .configure(getClass().getClassLoader()) - .table(LIBERTYBANS_08X_FLYWAY_TABLE) - .placeholders(Map.of( - "zeroeighttableprefix", "libertybans_" - )) - .locations("classpath:extra-database-migrations/zeroeight") - // This will need to be modified when Flyway 9 removes this deprecated method - .ignoreFutureMigrations(false) - .validateMigrationNaming(true) - .dataSource(dataSource) - .baselineOnMigrate(true).baselineVersion("0") - .load() - .migrate(); - } - - private byte[] serializeVictim(Victim victim) { - return switch (victim.getType()) { - case PLAYER -> UUIDUtil.toByteArray(((PlayerVictim) victim).getUUID()); - case ADDRESS -> ((AddressVictim) victim).getAddress().getRawAddress(); - default -> throw new UnsupportedOperationException("Victim type " + victim.getType()); - }; - } - - void insertUser(NameAddressRecord nameAddressRecord) { - Instant time = nameAddressRecord.timeRecorded(); - UUID uuid = nameAddressRecord.uuid(); - String name = nameAddressRecord.name().orElseThrow(AssertionError::new); - NetworkAddress address = nameAddressRecord.address().orElseThrow(AssertionError::new); - context - .insertInto(ZEROEIGHT_NAMES) - .columns( - ZEROEIGHT_NAMES.UUID, ZEROEIGHT_NAMES.NAME, ZEROEIGHT_NAMES.UPDATED - ) - .values(UUIDUtil.toByteArray(uuid), name, time) - .onConflict(ZEROEIGHT_NAMES.UUID, ZEROEIGHT_NAMES.NAME) - .doUpdate() - .set(ZEROEIGHT_NAMES.UPDATED, time) - .execute(); - context - .insertInto(ZEROEIGHT_ADDRESSES) - .columns( - ZEROEIGHT_ADDRESSES.UUID, ZEROEIGHT_ADDRESSES.ADDRESS, ZEROEIGHT_ADDRESSES.UPDATED - ).values(UUIDUtil.toByteArray(uuid), address, time) - .onConflict(ZEROEIGHT_ADDRESSES.UUID, ZEROEIGHT_ADDRESSES.ADDRESS) - .doUpdate() - .set(ZEROEIGHT_ADDRESSES.UPDATED, time) - .execute(); - } - - void insertPunishment(Punishment punishment, boolean active) { - int id = (int) punishment.getIdentifier(); - byte[] operator = UUIDUtil.toByteArray(new OperatorBinding().operatorToUuid(punishment.getOperator())); - context - .insertInto(ZEROEIGHT_PUNISHMENTS) - .columns( - ZEROEIGHT_PUNISHMENTS.ID, ZEROEIGHT_PUNISHMENTS.TYPE, - ZEROEIGHT_PUNISHMENTS.OPERATOR, ZEROEIGHT_PUNISHMENTS.REASON, - ZEROEIGHT_PUNISHMENTS.SCOPE, ZEROEIGHT_PUNISHMENTS.START, ZEROEIGHT_PUNISHMENTS.END - ) - .values( - id, punishment.getType().name(), - operator, punishment.getReason(), - punishment.getScope(), punishment.getStartDate(), punishment.getEndDate() - ) - .execute(); - Victim victim = punishment.getVictim(); - byte[] victimData = serializeVictim(victim); - String victimType = victim.getType().name(); - context - .insertInto(ZEROEIGHT_HISTORY) - .columns(ZEROEIGHT_HISTORY.ID, ZEROEIGHT_HISTORY.VICTIM, ZEROEIGHT_HISTORY.VICTIM_TYPE) - .values(id, victimData, victimType) - .execute(); - TableImpl> dataTable = switch (punishment.getType()) { - case BAN -> ZEROEIGHT_BANS; - case MUTE -> ZEROEIGHT_MUTES; - case WARN -> ZEROEIGHT_WARNS; - case KICK -> null; - }; - if (!active || dataTable == null) { - return; - } - var fields = dataTable.newRecord(); - context - .insertInto(dataTable) - .columns(fields.field1(), fields.field2(), fields.field3()) - .values(id, victimData, victimType) - .execute(); - - } -} diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/AdvancedBanImportIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/AdvancedBanImportIT.java index de821ecca..35caaf55f 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/AdvancedBanImportIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/AdvancedBanImportIT.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -31,11 +31,11 @@ import space.arim.libertybans.core.importing.ImportStatistics; import space.arim.libertybans.core.importing.LocalDatabaseSetup; import space.arim.libertybans.core.importing.PluginDatabaseSetup; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.libertybans.core.uuid.ServerType; import space.arim.libertybans.core.uuid.UUIDManager; import space.arim.libertybans.it.DontInject; import space.arim.libertybans.it.InjectionInvocationContextProvider; +import space.arim.libertybans.it.PlatformSpecs; import space.arim.libertybans.it.SetServerType; import space.arim.libertybans.it.SetTime; import space.arim.omnibus.util.UUIDUtil; @@ -46,7 +46,6 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; @ExtendWith(InjectionInvocationContextProvider.class) @ExtendWith(MockitoExtension.class) @@ -55,12 +54,14 @@ public class AdvancedBanImportIT { private final ImportExecutor importExecutor; + private final ScopeManager scopeManager; private final UUIDManager uuidManager; private PluginDatabaseSetup pluginDatabaseSetup; @Inject - public AdvancedBanImportIT(ImportExecutor importExecutor, UUIDManager uuidManager) { + public AdvancedBanImportIT(ImportExecutor importExecutor, ScopeManager scopeManager, UUIDManager uuidManager) { this.importExecutor = importExecutor; + this.scopeManager = scopeManager; this.uuidManager = uuidManager; } @@ -71,15 +72,8 @@ public void setup(@DontInject ConnectionSource connectionSource) { this.pluginDatabaseSetup = pluginDatabaseSetup; } - private ScopeManager createScopeManager() { - ScopeManager scopeManager = mock(ScopeManager.class); - when(scopeManager.globalScope()).thenReturn(ScopeImpl.GLOBAL); - return scopeManager; - } - private void importFrom(String dataFile, ImportStatistics expectedStatistics) { pluginDatabaseSetup.runSqlFromResource("import-data/advancedban/" + dataFile + ".sql"); - ScopeManager scopeManager = createScopeManager(); ImportSource importSource = pluginDatabaseSetup.createAdvancedBanImportSource(scopeManager); CompletableFuture futureImport = importExecutor.performImport(importSource); assertDoesNotThrow(futureImport::join, "Import failed: error"); @@ -89,14 +83,14 @@ private void importFrom(String dataFile, ImportStatistics expectedStatistics) { @TestTemplate @SetTime(unixTime = SetTime.DEFAULT_TIME) - @SetServerType(ServerType.OFFLINE) + @PlatformSpecs(serverTypes = @PlatformSpecs.ServerTypes(ServerType.OFFLINE)) public void sampleOneOffline() { importFrom("sample-one-offline", new ImportStatistics(103, 332, 768)); } @TestTemplate @SetTime(unixTime = SetTime.DEFAULT_TIME) - @SetServerType(ServerType.ONLINE) + @PlatformSpecs(serverTypes = @PlatformSpecs.ServerTypes(ServerType.ONLINE)) public void sampleTwoOnline() { addToCache("ed5f12cd600745d9a4b9940524ddaecf", "A248", "Aerodactyl_"); addToCache("840d1667a0e24934a3bd1a7ebbbc0732", "Cxleos"); diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/BanManagerImportIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/BanManagerImportIT.java index e5ba5ced6..7414407c3 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/BanManagerImportIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/BanManagerImportIT.java @@ -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 @@ -33,13 +33,10 @@ import space.arim.libertybans.core.importing.ImportStatistics; import space.arim.libertybans.core.importing.LocalDatabaseSetup; import space.arim.libertybans.core.importing.PluginDatabaseSetup; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.libertybans.it.DontInject; import space.arim.libertybans.it.InjectionInvocationContextProvider; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import static space.arim.libertybans.core.schema.tables.Addresses.ADDRESSES; import static space.arim.libertybans.core.schema.tables.Names.NAMES; import static space.arim.libertybans.core.schema.tables.SimpleActive.SIMPLE_ACTIVE; @@ -65,12 +62,6 @@ public void setup(@DontInject ConnectionSource connectionSource) { this.pluginDatabaseSetup = new PluginDatabaseSetup(connectionSource); } - private ScopeManager createScopeManager() { - ScopeManager scopeManager = mock(ScopeManager.class); - when(scopeManager.globalScope()).thenReturn(ScopeImpl.GLOBAL); - return scopeManager; - } - private int selectCount(Table table) { return queryExecutor.get().query((context) -> { return context.selectCount().from(table).fetchSingle().value1(); @@ -78,8 +69,7 @@ private int selectCount(Table table) { } @TestTemplate - public void fabricatedData() { - ScopeManager scopeManager = createScopeManager(); + public void fabricatedData(ScopeManager scopeManager) { pluginDatabaseSetup.initBanManagerSchema(); pluginDatabaseSetup.runSqlFromResource("import-data/banmanager/fabricated-data.sql"); ImportSource importSource = pluginDatabaseSetup.createBanManagerImportSource(scopeManager); diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/LiteBansImportIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/LiteBansImportIT.java index ed10efbba..6db3a1794 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/LiteBansImportIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/LiteBansImportIT.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -31,7 +31,6 @@ import space.arim.libertybans.core.importing.ImportStatistics; import space.arim.libertybans.core.importing.LocalDatabaseSetup; import space.arim.libertybans.core.importing.PluginDatabaseSetup; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.libertybans.it.DontInject; import space.arim.libertybans.it.InjectionInvocationContextProvider; import space.arim.libertybans.it.SetTime; @@ -40,8 +39,6 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; @ExtendWith(InjectionInvocationContextProvider.class) @ExtendWith(MockitoExtension.class) @@ -50,11 +47,13 @@ public class LiteBansImportIT { private final ImportExecutor importExecutor; + private final ScopeManager scopeManager; private PluginDatabaseSetup pluginDatabaseSetup; @Inject - public LiteBansImportIT(ImportExecutor importExecutor) { + public LiteBansImportIT(ImportExecutor importExecutor, ScopeManager scopeManager) { this.importExecutor = importExecutor; + this.scopeManager = scopeManager; } @BeforeEach @@ -62,16 +61,8 @@ public void setup(@DontInject ConnectionSource connectionSource) { this.pluginDatabaseSetup = new PluginDatabaseSetup(connectionSource); } - private ScopeManager createScopeManager() { - ScopeManager scopeManager = mock(ScopeManager.class); - // Specific server scopes not covered by this IT - when(scopeManager.globalScope()).thenReturn(ScopeImpl.GLOBAL); - return scopeManager; - } - private void importFrom(String dataFile, ImportStatistics expectedStatistics) { pluginDatabaseSetup.runSqlFromResource("import-data/litebans/" + dataFile + ".sql"); - ScopeManager scopeManager = createScopeManager(); ImportSource importSource = pluginDatabaseSetup.createLiteBansImportSource(scopeManager); CompletableFuture futureImport = importExecutor.performImport(importSource); assertDoesNotThrow(futureImport::join, "Import failed: error"); diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/SelfImportIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/SelfImportIT.java index e28543237..a739cad3f 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/SelfImportIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/SelfImportIT.java @@ -35,10 +35,10 @@ import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.punish.EnforcementOptions; import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.core.database.execute.QueryExecutor; import space.arim.libertybans.core.importing.SelfImportProcess; import space.arim.libertybans.core.punish.PunishmentCreator; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.libertybans.it.DontInject; import space.arim.libertybans.it.InjectionInvocationContextProvider; @@ -66,13 +66,16 @@ public class SelfImportIT { private final SelfImportProcess selfImportProcess; private final Provider queryExecutor; + private final ScopeManager scopeManager; private SelfImportData selfImportData; @Inject - public SelfImportIT(SelfImportProcess selfImportProcess, Provider queryExecutor) { + public SelfImportIT(SelfImportProcess selfImportProcess, Provider queryExecutor, + ScopeManager scopeManager) { this.selfImportProcess = selfImportProcess; this.queryExecutor = queryExecutor; + this.scopeManager = scopeManager; } @BeforeEach @@ -132,7 +135,7 @@ public void blueTree242(PunishmentCreator creator) throws IOException { creator.createPunishment( 17L, PunishmentType.BAN, AddressVictim.of(addressUnchecked("80.100.23.146")), PlayerOperator.of(UUID.fromString("f360da52-6304-3af4-8b30-b7d9c5e6e162")), "swearing", - ScopeImpl.GLOBAL, Instant.ofEpochSecond(1636139831L), Instant.MAX, null + scopeManager.globalScope(), Instant.ofEpochSecond(1636139831L), Instant.MAX, null ), context .selectFrom(SIMPLE_ACTIVE) @@ -143,7 +146,7 @@ public void blueTree242(PunishmentCreator creator) throws IOException { creator.createPunishment( 44L, PunishmentType.WARN, PlayerVictim.of(UUID.fromString("ef1275f7-5c3a-36ed-92f6-6b3716c72896")), PlayerOperator.of(UUID.fromString("f360da52-6304-3af4-8b30-b7d9c5e6e162")), "abusing and getting items from creative", - ScopeImpl.GLOBAL, Instant.ofEpochSecond(1636916014L), Instant.MAX, null + scopeManager.globalScope(), Instant.ofEpochSecond(1636916014L), Instant.MAX, null ), context .selectFrom(SIMPLE_ACTIVE) @@ -154,7 +157,7 @@ public void blueTree242(PunishmentCreator creator) throws IOException { creator.createPunishment( 135L, PunishmentType.BAN, PlayerVictim.of(UUID.fromString("47df0fc2-3213-3401-af68-58cafb0e99f5")), ConsoleOperator.INSTANCE, "Everyone wants you banned, nerd", - ScopeImpl.GLOBAL, Instant.ofEpochSecond(1638635845L), Instant.ofEpochSecond(1639240645L), null + scopeManager.globalScope(), Instant.ofEpochSecond(1638635845L), Instant.ofEpochSecond(1639240645L), null ), context .selectFrom(SIMPLE_HISTORY) diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/punish/EscalationIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/punish/EscalationIT.java index 33817161f..c445ab4a2 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/punish/EscalationIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/punish/EscalationIT.java @@ -28,7 +28,7 @@ import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.punish.PunishmentDetailsCalculator; import space.arim.libertybans.api.punish.PunishmentDrafter; -import space.arim.libertybans.core.scope.ScopeImpl; +import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.it.DontInject; import space.arim.libertybans.it.InjectionInvocationContextProvider; import space.arim.libertybans.it.IrrelevantData; @@ -46,12 +46,14 @@ public class EscalationIT { private final PunishmentDrafter drafter; + private final ScopeManager scopeManager; private final Victim victim; private final EscalationTrack escalationTrack; - public EscalationIT(PunishmentDrafter drafter, + public EscalationIT(PunishmentDrafter drafter, ScopeManager scopeManager, @DontInject Victim victim, @DontInject @NonNullTrack EscalationTrack escalationTrack) { this.drafter = drafter; + this.scopeManager = scopeManager; this.victim = victim; this.escalationTrack = escalationTrack; } @@ -101,7 +103,7 @@ public void escalateSimply(@DontInject Operator operator1, @DontInject Operator } PunishmentType type = PunishmentType.WARN; return new PunishmentDetailsCalculator.CalculationResult( - type, "Now at " + (existingPunishments + 1), Duration.ZERO, ScopeImpl.GLOBAL + type, "Now at " + (existingPunishments + 1), Duration.ZERO, scopeManager.globalScope() ); }; addPunishment(PunishmentType.WARN, operator1, "first warn"); @@ -129,7 +131,7 @@ public void escalateWithExtraData(@DontInject Operator operator1, @DontInject Op } PunishmentType type = PunishmentType.WARN; return new PunishmentDetailsCalculator.CalculationResult( - type, "Now at " + (existingPunishments + 1), Duration.ZERO, ScopeImpl.GLOBAL + type, "Now at " + (existingPunishments + 1), Duration.ZERO, scopeManager.globalScope() ); }; addPunishment(PunishmentType.WARN, operator1, "first warn"); diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/select/SelectionIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/select/SelectionIT.java index 0035817d8..cb4c44357 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/select/SelectionIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/select/SelectionIT.java @@ -33,6 +33,7 @@ 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.scope.ServerScope; import space.arim.libertybans.api.select.PunishmentSelector; import space.arim.libertybans.api.select.SelectionOrderBuilder; import space.arim.libertybans.api.select.SelectionPredicate; @@ -57,6 +58,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static space.arim.libertybans.api.select.SelectionPredicate.matchingNone; @ExtendWith(InjectionInvocationContextProvider.class) @ExtendWith({RandomPunishmentTypeResolver.class, RandomOperatorResolver.class, RandomVictimResolver.class}) @@ -247,7 +249,7 @@ public void selectVictimsBySelfOrType() { assertEquals( List.of(banAddress1, muteAddress2), getPunishments(selector.selectionBuilder().victimTypes( - SelectionPredicate.matchingNone(Victim.VictimType.PLAYER) + matchingNone(Victim.VictimType.PLAYER) )), "Should be identical to previous assertion"); assertEquals( @@ -261,7 +263,7 @@ public void selectVictimsBySelfOrType() { getPunishments(selector.selectionBuilder().victims(SelectionPredicate.matchingAnyOf(uuid2, address1)))); assertEquals( List.of(warnUuid2, banAddress1), - getPunishments(selector.selectionBuilder().victims(SelectionPredicate.matchingNone(uuid1, address2))), + getPunishments(selector.selectionBuilder().victims(matchingNone(uuid1, address2))), "Should be identical to previous assertion (in context)"); } @@ -330,4 +332,71 @@ public void selectByTrackOrItsInexistence() { ))); } + @TestTemplate + public void selectScopes(@DontInject Victim victim1, @DontInject Victim victim2, @DontInject Victim victim3) { + ServerScope globalScope = scopeManager.globalScope(); + ServerScope kitpvpScope = scopeManager.specificScope("kitpvp"); + ServerScope tntpvpScope = scopeManager.specificScope("tntpvp"); + ServerScope pvpScope = scopeManager.category("pvp"); + ServerScope creativeServerScope = scopeManager.specificScope("creative"); + ServerScope creativeCategoryScope = scopeManager.category("creative"); + + Punishment banGlobal = getPunishment( + draftBuilder(PunishmentType.BAN, victim1, "global ban")); + + Punishment muteKitpvp = getPunishment( + draftBuilder(PunishmentType.MUTE, victim1, "mute on kitpvp").scope(kitpvpScope)); + Punishment warnKitpvp = getPunishment( + draftBuilder(PunishmentType.WARN, victim2, "warn on kitpvp").scope(kitpvpScope)); + Punishment warnTntpvp = getPunishment( + draftBuilder(PunishmentType.WARN, victim2, "warn on tntpvp").scope(tntpvpScope)); + Punishment banPvp = getPunishment( + draftBuilder(PunishmentType.BAN, victim2, "banned on pvp servers").scope(pvpScope)); + + Punishment muteCreativeServer = getPunishment( + draftBuilder(PunishmentType.MUTE, victim2, "mute on creative server").scope(creativeServerScope)); + Punishment muteCreativeCategory = getPunishment( + draftBuilder(PunishmentType.MUTE, victim3, "mute on creative category").scope(creativeCategoryScope)); + + assertEmpty(selector.selectionBuilder().selectAll().scope(scopeManager.specificScope("nonexistent"))); + assertEmpty(selector.selectionBuilder().selectAll().scope(scopeManager.category("nonexistent"))); + + assertEquals( + List.of(banGlobal, muteKitpvp, warnKitpvp, warnTntpvp, banPvp, muteCreativeServer, muteCreativeCategory), + getPunishments(selector.selectionBuilder()) + ); + assertEquals( + List.of(banGlobal, muteKitpvp), + getPunishments(selector.selectionBuilder().victim(victim1)) + ); + assertEquals( + List.of(muteKitpvp, warnKitpvp, warnTntpvp, banPvp, muteCreativeServer, muteCreativeCategory), + getPunishments(selector.selectionBuilder().scopes(matchingNone(globalScope))) + ); + assertEquals( + List.of(banGlobal), + getPunishments(selector.selectionBuilder().scope(globalScope)) + ); + assertEquals( + List.of(muteKitpvp, warnKitpvp), + getPunishments(selector.selectionBuilder().scope(kitpvpScope)) + ); + assertEquals( + List.of(warnTntpvp), + getPunishments(selector.selectionBuilder().scope(tntpvpScope)) + ); + assertEquals( + List.of(banPvp), + getPunishments(selector.selectionBuilder().scope(pvpScope)) + ); + assertEquals( + List.of(muteCreativeServer), + getPunishments(selector.selectionBuilder().scope(creativeServerScope)) + ); + assertEquals( + List.of(muteCreativeCategory), + getPunishments(selector.selectionBuilder().scope(creativeCategoryScope)) + ); + } + } diff --git a/bans-core/src/test/resources/extra-database-migrations/codegen/V0__Sequences.sql b/bans-core/src/test/resources/extra-database-migrations/codegen/V0__Sequences.sql index d6daa870b..cc87216ff 100644 --- a/bans-core/src/test/resources/extra-database-migrations/codegen/V0__Sequences.sql +++ b/bans-core/src/test/resources/extra-database-migrations/codegen/V0__Sequences.sql @@ -6,3 +6,5 @@ CREATE SEQUENCE "libertybans_punishment_ids" AS BIGINT; CREATE SEQUENCE "libertybans_victim_ids" AS INT; CREATE SEQUENCE "libertybans_track_ids" AS INT; + +CREATE SEQUENCE "libertybans_scope_ids" AS INT; diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/AddressReporter.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/AddressReporter.java index 346c9cf69..63e6314eb 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/AddressReporter.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/AddressReporter.java @@ -1,19 +1,19 @@ -/* - * LibertyBans-env-bungee - * Copyright © 2020 Anand Beh - * - * LibertyBans-env-bungee 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-env-bungee 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-env-bungee. If not, see + * along with LibertyBans. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ package space.arim.libertybans.env.bungee; @@ -22,7 +22,7 @@ import net.md_5.bungee.api.connection.Connection; -interface AddressReporter { +public interface AddressReporter { InetAddress getAddress(Connection bungeePlayer); diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeBindModule.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeBindModule.java index 83c44a5f9..0dc2b0a33 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeBindModule.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeBindModule.java @@ -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 @@ -28,6 +28,8 @@ import space.arim.api.env.bungee.BungeePlatformHandle; import space.arim.api.env.PlatformHandle; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; import space.arim.libertybans.core.importing.PlatformImportSource; @@ -61,10 +63,18 @@ public EnvUserResolver resolver(BungeeUserResolver resolver) { return resolver; } + public EnvMessageChannel messageChannel(BungeeMessageChannel messageChannel) { + return messageChannel; + } + public AddressReporter reporter(StandardAddressReporter reporter) { return reporter; } + public EnvServerNameDetection serverNameDetection() { + return (scopeManager) -> scopeManager.detectServerName("proxy"); + } + public PlatformImportSource platformImportSource() { throw new UnsupportedOperationException("It is impossible to import from vanilla on BungeeCord"); } diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnforcer.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnforcer.java index d5767945e..24630a587 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnforcer.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnforcer.java @@ -31,10 +31,12 @@ import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.env.AbstractEnvEnforcer; import space.arim.libertybans.core.env.Interlocutor; +import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.net.InetAddress; +import java.util.Collection; import java.util.UUID; import java.util.function.Consumer; @@ -43,19 +45,21 @@ public class BungeeEnforcer extends AbstractEnvEnforcer { private final ProxyServer server; private final AddressReporter addressReporter; + private final BungeeMessageChannel messageChannel; @Inject public BungeeEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter formatter, Interlocutor interlocutor, AudienceRepresenter audienceRepresenter, - ProxyServer server, AddressReporter addressReporter) { + ProxyServer server, AddressReporter addressReporter, BungeeMessageChannel messageChannel) { super(futuresFactory, formatter, interlocutor, audienceRepresenter); this.server = server; this.addressReporter = addressReporter; + this.messageChannel = messageChannel; } @Override - protected CentralisedFuture doForAllPlayers(Consumer callback) { - server.getPlayers().forEach(callback); + public CentralisedFuture doForAllPlayers(Consumer> callback) { + callback.accept(server.getPlayers()); return completedVoid(); } @@ -74,6 +78,12 @@ public void kickPlayer(ProxiedPlayer player, Component message) { LegacyComponentSerializer.legacySection().serialize(message))); } + @Override + public boolean sendPluginMessageIfListening(ProxiedPlayer player, PluginMessage pluginMessage, D data) { + messageChannel.sendPluginMessage(player, pluginMessage, data); + return true; + } + @Override public UUID getUniqueIdFor(ProxiedPlayer player) { return player.getUniqueId(); @@ -84,6 +94,11 @@ public InetAddress getAddressFor(ProxiedPlayer player) { return addressReporter.getAddress(player); } + @Override + public String getNameFor(ProxiedPlayer player) { + return player.getName(); + } + @Override public boolean hasPermission(ProxiedPlayer player, String permission) { return player.hasPermission(permission); diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeLauncher.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeLauncher.java index b5876be44..001caac59 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeLauncher.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeLauncher.java @@ -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 @@ -35,6 +35,7 @@ import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.plugin.Plugin; import space.arim.libertybans.core.addon.AddonLoader; +import space.arim.libertybans.core.env.InstanceType; import space.arim.omnibus.Omnibus; import space.arim.omnibus.OmnibusProvider; @@ -60,6 +61,7 @@ public BaseFoundation launch() { .bindInstance(Plugin.class, plugin) .bindInstance(ProxyServer.class, plugin.getProxy()) .bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), folder) + .bindInstance(InstanceType.class, InstanceType.PROXY) .bindInstance(Omnibus.class, omnibus) .addBindModules( new ApiBindModule(), diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeMessageChannel.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeMessageChannel.java new file mode 100644 index 000000000..16d99f6f6 --- /dev/null +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeMessageChannel.java @@ -0,0 +1,81 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.env.bungee; + +import jakarta.inject.Inject; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.event.EventHandler; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.PluginMessageAsBytes; +import space.arim.libertybans.core.env.message.PluginMessage; + +import java.util.function.Consumer; + +public final class BungeeMessageChannel implements EnvMessageChannel { + + private final Plugin plugin; + + private static final String BUNGEE_CHANNEL = "BungeeCord"; + + @Inject + public BungeeMessageChannel(Plugin plugin) { + this.plugin = plugin; + } + + void sendPluginMessage(ProxiedPlayer player, PluginMessage pluginMessage, D data) { + Server server = player.getServer(); + if (server != null) { + server.sendData(BUNGEE_CHANNEL, new PluginMessageAsBytes<>(pluginMessage).generateBytes(data)); + } + } + + @Override + public void installHandler(Listener handler) { + plugin.getProxy().getPluginManager().registerListener(plugin, handler); + } + + @Override + public void uninstallHandler(Listener handler) { + plugin.getProxy().getPluginManager().unregisterListener(handler); + } + + @Override + public Listener createHandler(Consumer acceptor, PluginMessage pluginMessage) { + return new Handler<>(acceptor, pluginMessage); + } + + // Public for reflection purposes + public record Handler(Consumer acceptor, PluginMessage pluginMessage) implements Listener { + + @EventHandler + public void onReceive(PluginMessageEvent event) { + if (event.getSender() instanceof Server && event.getTag().equals(BUNGEE_CHANNEL)) { + new PluginMessageAsBytes<>(pluginMessage) + .readBytes(event.getData()) + .ifPresent(acceptor); + } + } + } + +} diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ConnectionListener.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ConnectionListener.java index e259a31ee..ef24d4848 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ConnectionListener.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ConnectionListener.java @@ -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 @@ -20,36 +20,43 @@ package space.arim.libertybans.env.bungee; import jakarta.inject.Inject; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.event.LoginEvent; +import net.md_5.bungee.api.event.ServerConnectEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventPriority; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import space.arim.api.env.AudienceRepresenter; import space.arim.libertybans.core.env.PlatformListener; import space.arim.libertybans.core.selector.Guardian; import space.arim.omnibus.util.ThisClass; import java.net.InetAddress; -import java.util.UUID; public final class ConnectionListener implements Listener, PlatformListener { private final Plugin plugin; private final Guardian guardian; private final AddressReporter addressReporter; + private final AudienceRepresenter audienceRepresenter; private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); @Inject - public ConnectionListener(Plugin plugin, Guardian guardian, AddressReporter addressReporter) { + public ConnectionListener(Plugin plugin, Guardian guardian, AddressReporter addressReporter, + AudienceRepresenter audienceRepresenter) { this.plugin = plugin; this.guardian = guardian; this.addressReporter = addressReporter; + this.audienceRepresenter = audienceRepresenter; } @Override @@ -65,17 +72,17 @@ public void unregister() { @EventHandler(priority = EventPriority.LOW) public void onConnect(LoginEvent event) { if (event.isCancelled()) { - logger.debug("Event {} is already blocked", event); + logger.trace("Event {} is already blocked", event); return; } PendingConnection connection = event.getConnection(); - UUID uuid = connection.getUniqueId(); - String name = connection.getName(); InetAddress address = addressReporter.getAddress(connection); event.registerIntent(plugin); - guardian.executeAndCheckConnection(uuid, name, address).thenAccept((message) -> { + guardian.executeAndCheckConnection( + connection.getUniqueId(), connection.getName(), address + ).thenAccept((message) -> { if (message == null) { logger.trace("Event {} will be permitted", event); } else { @@ -91,4 +98,22 @@ public void onConnect(LoginEvent event) { }); } + @EventHandler(priority = EventPriority.HIGH) + public void onServerSwitch(ServerConnectEvent event) { + if (event.getReason() == ServerConnectEvent.Reason.LOBBY_FALLBACK) { + // Don't kick players if there is no alternative server for them + return; + } + ProxiedPlayer player = event.getPlayer(); + InetAddress address = addressReporter.getAddress(player); + + Component message = guardian.checkServerSwitch( + player.getUniqueId(), address, event.getTarget().getName() + ).join(); + if (message != null) { + event.setCancelled(true); + audienceRepresenter.toAudience(player).sendMessage(message); + } + } + } diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ServerNameListener.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ServerNameListener.java new file mode 100644 index 000000000..57c73e0e6 --- /dev/null +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ServerNameListener.java @@ -0,0 +1,63 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.env.spigot; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.plugin.Plugin; +import space.arim.libertybans.core.env.PlatformListener; +import space.arim.libertybans.core.scope.ServerNameListenerBase; + +@Singleton +public final class ServerNameListener implements PlatformListener, Listener { + + private final Plugin plugin; + private final ServerNameListenerBase baseImpl; + + @Inject + public ServerNameListener(Plugin plugin, ServerNameListenerBase baseImpl) { + this.plugin = plugin; + this.baseImpl = baseImpl; + } + + @Override + public void register() { + baseImpl.register(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void unregister() { + HandlerList.unregisterAll(this); + baseImpl.unregister(); + } + + @EventHandler(priority = EventPriority.LOW) + public void onJoin(PlayerJoinEvent event) { + baseImpl.onJoin(event.getPlayer()); + } + +} diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotBindModule.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotBindModule.java index ea7dbe592..11c147eb5 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotBindModule.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotBindModule.java @@ -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 @@ -27,6 +27,8 @@ import space.arim.api.env.bukkit.BukkitAudienceRepresenter; import space.arim.api.env.bukkit.BukkitPlatformHandle; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; import space.arim.libertybans.core.importing.PlatformImportSource; @@ -62,6 +64,10 @@ public EnvUserResolver resolver(SpigotUserResolver resolver) { return resolver; } + public EnvMessageChannel messageChannel(SpigotMessageChannel messageChannel) { + return messageChannel; + } + @Singleton public CommandMapHelper commandMapHelper(SimpleCommandMapHelper scmh) { return new CachingCommandMapHelper(scmh); @@ -77,6 +83,10 @@ public MorePaperLibAdventure morePaperLibAdventure(MorePaperLib morePaperLib) { return new MorePaperLibAdventure(morePaperLib); } + public EnvServerNameDetection serverNameDetection() { + return (scopeManager) -> {}; + } + public PlatformImportSource platformImportSource(BukkitImportSource importSource) { return importSource; } diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnforcer.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnforcer.java index 8704fa719..934fef5e8 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnforcer.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnforcer.java @@ -22,42 +22,39 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.Server; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import space.arim.api.env.AudienceRepresenter; -import space.arim.libertybans.core.config.Configs; import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.env.AbstractEnvEnforcer; import space.arim.libertybans.core.env.Interlocutor; +import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.morepaperlib.adventure.MorePaperLibAdventure; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.UncheckedIOException; import java.net.InetAddress; +import java.util.Collection; import java.util.UUID; import java.util.function.Consumer; @Singleton public class SpigotEnforcer extends AbstractEnvEnforcer { - private final Configs configs; private final Server server; private final MorePaperLibAdventure morePaperLibAdventure; + private final SpigotMessageChannel messageChannel; @Inject public SpigotEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter formatter, Interlocutor interlocutor, AudienceRepresenter audienceRepresenter, - Configs configs, Server server, MorePaperLibAdventure morePaperLibAdventure) { + Server server, MorePaperLibAdventure morePaperLibAdventure, + SpigotMessageChannel messageChannel) { super(futuresFactory, formatter, interlocutor, audienceRepresenter); - this.configs = configs; this.server = server; this.morePaperLibAdventure = morePaperLibAdventure; + this.messageChannel = messageChannel; } @SuppressWarnings("unchecked") @@ -67,36 +64,22 @@ private CentralisedFuture runSync(Runnable command) { } @Override - protected CentralisedFuture doForAllPlayers(Consumer callback) { + public CentralisedFuture doForAllPlayers(Consumer> callback) { if (morePaperLibAdventure.getMorePaperLib().scheduling().isUsingFolia()) { - server.getOnlinePlayers().forEach(callback); + callback.accept(server.getOnlinePlayers()); return completedVoid(); } - return runSync(() -> server.getOnlinePlayers().forEach(callback)); + return runSync(() -> callback.accept(server.getOnlinePlayers())); } @Override public void kickPlayer(Player player, Component message) { - if (configs.getMainConfig().platforms().bukkit().kickViaPluginMessaging()) { - byte[] kickData; - try (ByteArrayOutputStream kickDataStream = new ByteArrayOutputStream(); - DataOutputStream write = new DataOutputStream(kickDataStream)) { - - write.writeUTF("KickPlayer"); - write.writeUTF(player.getName()); - write.writeUTF(LegacyComponentSerializer.legacySection().serialize(message)); - kickData = kickDataStream.toByteArray(); - } catch (IOException ex) { - throw new UncheckedIOException(ex); - } - player.sendPluginMessage( - morePaperLibAdventure.getMorePaperLib().getPlugin(), - ChannelRegistration.BUNGEE_CHANNEL, - kickData - ); - } else { - morePaperLibAdventure.kickPlayer(player, message); - } + morePaperLibAdventure.kickPlayer(player, message); + } + + @Override + public boolean sendPluginMessageIfListening(Player player, PluginMessage pluginMessage, D data) { + return messageChannel.sendPluginMessage(player, pluginMessage, data); } @Override @@ -126,6 +109,11 @@ public InetAddress getAddressFor(Player player) { return player.getAddress().getAddress(); } + @Override + public String getNameFor(Player player) { + return player.getName(); + } + @Override public boolean hasPermission(Player player, String permission) { return player.hasPermission(permission); diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnv.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnv.java index 179e878a9..d2e711cb1 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnv.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnv.java @@ -31,16 +31,19 @@ public final class SpigotEnv implements Environment { private final Provider connectionListener; private final Provider chatListener; + private final Provider serverNameListener; + private final Provider pluginMessageChannel; private final CommandHandler.CommandHelper commandHelper; - private final ChannelRegistration channelRegistration; @Inject public SpigotEnv(Provider connectionListener, Provider chatListener, - CommandHandler.CommandHelper commandHelper, ChannelRegistration channelRegistration) { + Provider serverNameListener, Provider pluginMessageChannel, + CommandHandler.CommandHelper commandHelper) { this.connectionListener = connectionListener; this.chatListener = chatListener; + this.serverNameListener = serverNameListener; + this.pluginMessageChannel = pluginMessageChannel; this.commandHelper = commandHelper; - this.channelRegistration = channelRegistration; } @Override @@ -48,8 +51,9 @@ public Set createListeners() { return Set.of( connectionListener.get(), chatListener.get(), - new CommandHandler(commandHelper, Commands.BASE_COMMAND_NAME, false), - channelRegistration + serverNameListener.get(), + pluginMessageChannel.get(), + new CommandHandler(commandHelper, Commands.BASE_COMMAND_NAME, false) ); } @@ -57,5 +61,5 @@ public Set createListeners() { public PlatformListener createAliasCommand(String command) { return new CommandHandler(commandHelper, command, true); } - + } diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotLauncher.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotLauncher.java index cd35eb5f3..5c20192b5 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotLauncher.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotLauncher.java @@ -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 @@ -36,6 +36,7 @@ import org.bukkit.Server; import org.bukkit.plugin.java.JavaPlugin; import space.arim.libertybans.core.addon.AddonLoader; +import space.arim.libertybans.core.env.InstanceType; import space.arim.omnibus.Omnibus; import space.arim.omnibus.OmnibusProvider; @@ -62,6 +63,7 @@ public BaseFoundation launch() { .bindInstance(Plugin.class, plugin) .bindInstance(Server.class, plugin.getServer()) .bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), folder) + .bindInstance(InstanceType.class, InstanceType.GAME_SERVER) .bindInstance(Omnibus.class, omnibus) .addBindModules( new ApiBindModule(), diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotMessageChannel.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotMessageChannel.java new file mode 100644 index 000000000..883286fa3 --- /dev/null +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotMessageChannel.java @@ -0,0 +1,92 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.env.spigot; + +import jakarta.inject.Inject; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.messaging.PluginMessageListener; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.PlatformListener; +import space.arim.libertybans.core.env.PluginMessageAsBytes; +import space.arim.libertybans.core.env.message.PluginMessage; + +import java.util.function.Consumer; + +public final class SpigotMessageChannel implements PlatformListener, EnvMessageChannel { + + private final Plugin plugin; + + static final String BUNGEE_CHANNEL = "BungeeCord"; + + @Inject + public SpigotMessageChannel(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public void register() { + plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, BUNGEE_CHANNEL); + } + + @Override + public void unregister() { + plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, BUNGEE_CHANNEL); + } + + boolean sendPluginMessage(Player player, PluginMessage pluginMessage, D data) { + boolean listened = player.getListeningPluginChannels().contains(BUNGEE_CHANNEL); + if (listened) { + player.sendPluginMessage( + plugin, BUNGEE_CHANNEL, + new PluginMessageAsBytes<>(pluginMessage).generateBytes(data) + ); + } + return listened; + } + + @Override + public void installHandler(PluginMessageListener handler) { + plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, BUNGEE_CHANNEL, handler); + } + + @Override + public void uninstallHandler(PluginMessageListener handler) { + plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, BUNGEE_CHANNEL, handler); + } + + @Override + public PluginMessageListener createHandler(Consumer acceptor, PluginMessage pluginMessage) { + return new Handler<>(acceptor, pluginMessage); + } + + record Handler(Consumer acceptor, PluginMessage pluginMessage) implements PluginMessageListener { + + @Override + public void onPluginMessageReceived(String channel, Player player, byte[] message) { + if (channel.equals(BUNGEE_CHANNEL)) { + new PluginMessageAsBytes<>(pluginMessage) + .readBytes(message) + .ifPresent(acceptor); + } + } + } + +} diff --git a/bans-env/spigot/src/test/java/space/arim/libertybans/env/spigot/BukkitImportSourceTest.java b/bans-env/spigot/src/test/java/space/arim/libertybans/env/spigot/BukkitImportSourceTest.java index ee0783b11..db5b20553 100644 --- a/bans-env/spigot/src/test/java/space/arim/libertybans/env/spigot/BukkitImportSourceTest.java +++ b/bans-env/spigot/src/test/java/space/arim/libertybans/env/spigot/BukkitImportSourceTest.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -20,15 +20,17 @@ package space.arim.libertybans.env.spigot; import org.bukkit.Server; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ScopeManager; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.importing.PortablePunishment; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import space.arim.omnibus.util.concurrent.impl.IndifferentFactoryOfTheFuture; @@ -47,10 +49,15 @@ @ExtendWith(MockitoExtension.class) public class BukkitImportSourceTest { - private Set sourcePunishments(Server server) { - ScopeManager scopeManager = mock(ScopeManager.class); - lenient().when(scopeManager.globalScope()).thenReturn(ScopeImpl.GLOBAL); + private ScopeManager scopeManager; + + @BeforeEach + public void setScopeManager(@Mock ScopeManager scopeManager) { + this.scopeManager = scopeManager; + lenient().when(scopeManager.globalScope()).thenReturn(mock(ServerScope.class)); + } + private Set sourcePunishments(Server server) { FactoryOfTheFuture futuresFactory = new IndifferentFactoryOfTheFuture(); return new BukkitImportSource(futuresFactory, scopeManager, server) .sourcePunishments().collect(Collectors.toUnmodifiableSet()); @@ -81,7 +88,7 @@ public void userBan() { Set.of(new PortablePunishment( null, new PortablePunishment.KnownDetails( - PunishmentType.BAN, reason, ScopeImpl.GLOBAL, + PunishmentType.BAN, reason, scopeManager.globalScope(), start, Punishment.PERMANENT_END_DATE), new PortablePunishment.VictimInfo(null, username, null), PortablePunishment.OperatorInfo.createUser(null, operator), @@ -104,7 +111,7 @@ public void ipBan() throws UnknownHostException { Set.of(new PortablePunishment( null, new PortablePunishment.KnownDetails( - PunishmentType.BAN, reason, ScopeImpl.GLOBAL, + PunishmentType.BAN, reason, scopeManager.globalScope(), start, Punishment.PERMANENT_END_DATE), new PortablePunishment.VictimInfo(null, null, NetworkAddress.of(address)), PortablePunishment.OperatorInfo.createUser(null, operator), @@ -127,7 +134,7 @@ public void temporaryBan() { Set.of(new PortablePunishment( null, new PortablePunishment.KnownDetails( - PunishmentType.BAN, reason, ScopeImpl.GLOBAL, + PunishmentType.BAN, reason, scopeManager.globalScope(), start, end), new PortablePunishment.VictimInfo(null, username, null), PortablePunishment.OperatorInfo.createUser(null, operator), diff --git a/bans-env/spigot/src/test/java/space/arim/libertybans/env/spigot/SpigotEnvTest.java b/bans-env/spigot/src/test/java/space/arim/libertybans/env/spigot/SpigotEnvTest.java index 82bb3efea..2a14c27a4 100644 --- a/bans-env/spigot/src/test/java/space/arim/libertybans/env/spigot/SpigotEnvTest.java +++ b/bans-env/spigot/src/test/java/space/arim/libertybans/env/spigot/SpigotEnvTest.java @@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test; import space.arim.api.util.testing.InjectableConstructor; -import space.arim.libertybans.core.env.ParallelisedListener; import space.arim.libertybans.core.env.PlatformListener; public class SpigotEnvTest { @@ -31,8 +30,9 @@ public void allListenersDeclared() { new InjectableConstructor(SpigotEnv.class) .verifyParametersContainSubclassesOf(PlatformListener.class, (clazz) -> { // Exclude CommandHandler since it is constructed directly - // Exclude ParallelisedListener, which would never be injected - return !clazz.equals(CommandHandler.class) && !clazz.equals(ParallelisedListener.class); + // Use only classes in our package or subpackages + return !clazz.equals(CommandHandler.class) + && clazz.getPackageName().startsWith(getClass().getPackageName()); }); } } diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ChatListener.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ChatListener.java index 5488e07a3..cf6899510 100644 --- a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ChatListener.java +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ChatListener.java @@ -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 @@ -61,7 +61,7 @@ public void onChat(PlayerChatEvent event) { combinedChatEvent(event, null); } - @Listener + @Listener(order = Order.LATE) public void onCommand(ExecuteCommandEvent.Pre event) { combinedChatEvent(event, event.command()); } diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ServerNameListener.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ServerNameListener.java new file mode 100644 index 000000000..5ad4f4b9c --- /dev/null +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ServerNameListener.java @@ -0,0 +1,61 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.env.sponge; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.network.ServerSideConnectionEvent; +import space.arim.libertybans.core.env.PlatformListener; +import space.arim.libertybans.core.scope.ServerNameListenerBase; +import space.arim.libertybans.env.sponge.listener.RegisterListeners; + +@Singleton +public final class ServerNameListener implements PlatformListener { + + private final RegisterListeners registerListeners; + private final ServerNameListenerBase baseImpl; + + @Inject + public ServerNameListener(RegisterListeners registerListeners, ServerNameListenerBase baseImpl) { + this.registerListeners = registerListeners; + this.baseImpl = baseImpl; + } + + @Override + public void register() { + baseImpl.register(); + registerListeners.register(this); + } + + @Override + public void unregister() { + registerListeners.unregister(this); + baseImpl.unregister(); + } + + @Listener(order = Order.EARLY) + public void onJoin(ServerSideConnectionEvent.Join event) { + baseImpl.onJoin(event.player()); + } + +} diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeBindModule.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeBindModule.java index 1e189860f..150def4f7 100644 --- a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeBindModule.java +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeBindModule.java @@ -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 @@ -28,6 +28,8 @@ import space.arim.api.env.sponge.SpongeAudienceRepresenter; import space.arim.api.env.sponge.SpongePlatformHandle; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; import space.arim.libertybans.core.importing.PlatformImportSource; @@ -63,6 +65,14 @@ public EnvUserResolver envUserResolver(SpongeUserResolver resolver) { return resolver; } + public EnvMessageChannel envMessageChannel(SpongeMessageChannel messageChannel) { + return messageChannel; + } + + public EnvServerNameDetection serverNameDetection() { + return (scopeManager) -> {}; + } + public PlatformImportSource platformImportSource(SpongeImportSource importSource) { return importSource; } diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnforcer.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnforcer.java index a17ad8abb..7fd1624d9 100644 --- a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnforcer.java +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnforcer.java @@ -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 @@ -30,10 +30,12 @@ import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.env.AbstractEnvEnforcer; import space.arim.libertybans.core.env.Interlocutor; +import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.net.InetAddress; +import java.util.Collection; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; @@ -41,12 +43,14 @@ public final class SpongeEnforcer extends AbstractEnvEnforcer { private final Game game; + private final SpongeMessageChannel messageChannel; @Inject public SpongeEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter formatter, - Interlocutor interlocutor, Game game) { + Interlocutor interlocutor, Game game, SpongeMessageChannel messageChannel) { super(futuresFactory, formatter, interlocutor, AudienceRepresenter.identity()); this.game = game; + this.messageChannel = messageChannel; } @SuppressWarnings("unchecked") @@ -56,8 +60,8 @@ private CentralisedFuture runSync(Runnable command) { } @Override - protected CentralisedFuture doForAllPlayers(Consumer callback) { - return runSync(() -> game.server().onlinePlayers().forEach(callback)); + public CentralisedFuture doForAllPlayers(Consumer> callback) { + return runSync(() -> callback.accept(game.server().onlinePlayers())); } @Override @@ -70,6 +74,11 @@ public void kickPlayer(ServerPlayer player, Component message) { player.kick(message); } + @Override + public boolean sendPluginMessageIfListening(ServerPlayer player, PluginMessage pluginMessage, D data) { + return messageChannel.sendPluginMessage(player, pluginMessage, data); + } + @Override public UUID getUniqueIdFor(ServerPlayer player) { return player.uniqueId(); @@ -80,6 +89,11 @@ public InetAddress getAddressFor(ServerPlayer player) { return player.connection().address().getAddress(); } + @Override + public String getNameFor(ServerPlayer player) { + return player.name(); + } + @Override public boolean hasPermission(ServerPlayer player, String permission) { return player.hasPermission(permission); diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnv.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnv.java index b41864bba..75dddc499 100644 --- a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnv.java +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnv.java @@ -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 @@ -31,13 +31,15 @@ public final class SpongeEnv implements Environment { private final Provider connectionListener; private final Provider chatListener; + private final Provider serverNameListener; private final PlatformAccess platformAccess; @Inject public SpongeEnv(Provider connectionListener, Provider chatListener, - PlatformAccess platformAccess) { + Provider serverNameListener, PlatformAccess platformAccess) { this.connectionListener = connectionListener; this.chatListener = chatListener; + this.serverNameListener = serverNameListener; this.platformAccess = platformAccess; } @@ -45,7 +47,8 @@ public SpongeEnv(Provider connectionListener, Provider createListeners() { return Set.of( connectionListener.get(), - chatListener.get() + chatListener.get(), + serverNameListener.get() ); } diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeLauncher.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeLauncher.java index 6dedb940a..fc0d0a096 100644 --- a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeLauncher.java +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeLauncher.java @@ -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 @@ -32,6 +32,7 @@ import space.arim.libertybans.core.CommandsModule; import space.arim.libertybans.core.PillarOneBindModule; import space.arim.libertybans.core.PillarTwoBindModule; +import space.arim.libertybans.core.env.InstanceType; import space.arim.libertybans.env.sponge.listener.RegisterListeners; import space.arim.libertybans.env.sponge.listener.RegisterListenersByMethodScan; import space.arim.libertybans.env.sponge.listener.RegisterListenersStandard; @@ -86,6 +87,7 @@ public BaseFoundation launch() { .bindInstance(PluginContainer.class, plugin) .bindInstance(Game.class, game) .bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), folder) + .bindInstance(InstanceType.class, InstanceType.GAME_SERVER) .bindInstance(Omnibus.class, omnibus) .addBindModules( new ApiBindModule(), diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeMessageChannel.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeMessageChannel.java new file mode 100644 index 000000000..3149bfe53 --- /dev/null +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeMessageChannel.java @@ -0,0 +1,110 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.env.sponge; + +import jakarta.inject.Inject; +import org.slf4j.LoggerFactory; +import org.spongepowered.api.Game; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; +import org.spongepowered.api.network.EngineConnectionSide; +import org.spongepowered.api.network.ServerSideConnection; +import org.spongepowered.api.network.channel.ChannelBuf; +import org.spongepowered.api.network.channel.raw.RawDataChannel; +import org.spongepowered.api.network.channel.raw.play.RawPlayDataChannel; +import org.spongepowered.api.network.channel.raw.play.RawPlayDataHandler; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.message.PluginMessage; +import space.arim.libertybans.core.env.message.PluginMessageInput; +import space.arim.libertybans.core.env.message.PluginMessageOutput; + +import java.io.IOException; +import java.util.function.Consumer; + +public final class SpongeMessageChannel implements EnvMessageChannel> { + + private final Game game; + + @Inject + public SpongeMessageChannel(Game game) { + this.game = game; + } + + private RawPlayDataChannel channel() { + return game.channelManager() + .ofType(ResourceKey.of("bungeecord", "main"), RawDataChannel.class) + .play(); + } + + boolean sendPluginMessage(ServerPlayer player, PluginMessage pluginMessage, D data) { + var channel = channel(); + boolean supported = channel.isSupportedBy(player.connection()); + if (supported) { + channel.sendTo(player, (buffer) -> { + pluginMessage.writeTo(data, new ChannelBufAsOutput(buffer)); + }).exceptionally((ex) -> { + LoggerFactory.getLogger(getClass()).error("Failed to send plugin message", ex); + return null; + }); + } + return supported; + } + + @Override + public void installHandler(RawPlayDataHandler handler) { + channel().addHandler(EngineConnectionSide.SERVER, handler); + } + + @Override + public void uninstallHandler(RawPlayDataHandler handler) { + channel().removeHandler(handler); + } + + @Override + public RawPlayDataHandler createHandler(Consumer acceptor, + PluginMessage pluginMessage) { + return new Handler<>(acceptor, pluginMessage); + } + + record Handler(Consumer handler, PluginMessage pluginMessage) + implements RawPlayDataHandler { + + @Override + public void handlePayload(ChannelBuf data, ServerSideConnection connection) { + pluginMessage.readFrom(new ChannelBufAsInput(data)).ifPresent(handler); + } + + } + + private record ChannelBufAsOutput(ChannelBuf buffer) implements PluginMessageOutput { + @Override + public void writeUTF(String utf) throws IOException { + buffer.writeUTF(utf); + } + } + + private record ChannelBufAsInput(ChannelBuf buffer) implements PluginMessageInput { + @Override + public String readUTF() throws IOException { + return buffer.readUTF(); + } + } + +} diff --git a/bans-env/sponge/src/test/java/space/arim/libertybans/env/sponge/SpongeEnvTest.java b/bans-env/sponge/src/test/java/space/arim/libertybans/env/sponge/SpongeEnvTest.java index 0b59b5715..2a581c220 100644 --- a/bans-env/sponge/src/test/java/space/arim/libertybans/env/sponge/SpongeEnvTest.java +++ b/bans-env/sponge/src/test/java/space/arim/libertybans/env/sponge/SpongeEnvTest.java @@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test; import space.arim.api.util.testing.InjectableConstructor; -import space.arim.libertybans.core.env.ParallelisedListener; import space.arim.libertybans.core.env.PlatformListener; public class SpongeEnvTest { @@ -31,10 +30,10 @@ public void allListenersDeclared() { new InjectableConstructor(SpongeEnv.class) .verifyParametersContainSubclassesOf(PlatformListener.class, (clazz) -> { // Exclude CommandHandler since it is constructed directly - // Exclude ParallelisedListener, which would never be injected + // Use only classes in our package or subpackages // Exclude anonymous or local classes return !clazz.equals(CommandHandler.class) - && !clazz.equals(ParallelisedListener.class) + && clazz.getPackageName().startsWith(getClass().getPackageName()) && !clazz.isAnonymousClass() && !clazz.isLocalClass(); }); diff --git a/bans-env/spongeplugin/pom.xml b/bans-env/spongeplugin/pom.xml index 1a7a3a760..28bce5913 100644 --- a/bans-env/spongeplugin/pom.xml +++ b/bans-env/spongeplugin/pom.xml @@ -1,3 +1,22 @@ + + diff --git a/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneBindModule.java b/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneBindModule.java index 54110285e..0fe377a0a 100644 --- a/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneBindModule.java +++ b/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneBindModule.java @@ -23,6 +23,8 @@ import space.arim.api.env.PlatformHandle; import space.arim.api.env.PlatformPluginInfo; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; import space.arim.libertybans.core.importing.PlatformImportSource; @@ -81,6 +83,14 @@ public EnvUserResolver resolver(StandaloneResolver resolver) { return resolver; } + public EnvMessageChannel messageChannel(EnvMessageChannel.NoOp messageChannel) { + return messageChannel; + } + + public EnvServerNameDetection serverNameDetection() { + return (scopeManager) -> scopeManager.detectServerName("standalone"); + } + public PlatformImportSource platformImportSource() { throw new UnsupportedOperationException("It is impossible to import from vanilla on the standalone application"); } diff --git a/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneEnforcer.java b/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneEnforcer.java index b236be032..a6c386a1d 100644 --- a/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneEnforcer.java +++ b/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneEnforcer.java @@ -25,10 +25,12 @@ import net.kyori.adventure.text.ComponentLike; import space.arim.libertybans.core.env.EnvEnforcer; import space.arim.libertybans.core.env.TargetMatcher; +import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.net.InetAddress; +import java.util.Collection; import java.util.UUID; import java.util.function.Consumer; @@ -53,11 +55,21 @@ public CentralisedFuture doForPlayerIfOnline(UUID uuid, Consumer cal return completedVoid(); } + @Override + public CentralisedFuture doForAllPlayers(Consumer> action) { + return completedVoid(); + } + @Override public void kickPlayer(Void player, Component message) { throw new UnsupportedOperationException(); } + @Override + public void sendPluginMessage(Void player, PluginMessage pluginMessage, D data) { + throw new UnsupportedOperationException(); + } + @Override public void sendMessageNoPrefix(Void player, ComponentLike message) { throw new UnsupportedOperationException(); @@ -78,6 +90,11 @@ public InetAddress getAddressFor(Void player) { throw new UnsupportedOperationException(); } + @Override + public String getNameFor(Void player) { + throw new UnsupportedOperationException(); + } + @Override public boolean hasPermission(Void player, String permission) { return false; diff --git a/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneLauncher.java b/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneLauncher.java index 36aba259f..0a6a15630 100644 --- a/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneLauncher.java +++ b/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneLauncher.java @@ -31,6 +31,7 @@ import space.arim.libertybans.core.PillarOneBindModule; import space.arim.libertybans.core.PillarTwoBindModule; import space.arim.libertybans.core.addon.AddonLoader; +import space.arim.libertybans.core.env.InstanceType; import space.arim.omnibus.Omnibus; import space.arim.omnibus.OmnibusProvider; @@ -53,6 +54,7 @@ public StandaloneLauncher(Path folder, Omnibus omnibus) { public Injector createInjector(ConsoleAudience consoleAudience) { return new InjectorBuilder() .bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), folder) + .bindInstance(InstanceType.class, InstanceType.STANDALONE) .bindInstance(Omnibus.class, omnibus) .bindInstance(ConsoleAudience.class, consoleAudience) .addBindModules( diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ChatListener.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ChatListener.java index 612911e9a..edca8cda5 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ChatListener.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ChatListener.java @@ -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 @@ -19,41 +19,69 @@ package space.arim.libertybans.env.velocity; +import com.velocitypowered.api.event.EventTask; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.command.CommandExecuteEvent; import com.velocitypowered.api.event.player.PlayerChatEvent; import com.velocitypowered.api.event.player.PlayerChatEvent.ChatResult; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import jakarta.inject.Inject; -import net.kyori.adventure.text.Component; +import space.arim.libertybans.core.env.PlatformListener; import space.arim.libertybans.core.selector.Guardian; -import space.arim.omnibus.util.concurrent.CentralisedFuture; -public final class ChatListener extends VelocityAsyncListener { +public final class ChatListener implements PlatformListener { + private final PluginContainer plugin; + private final ProxyServer server; private final Guardian guardian; @Inject public ChatListener(PluginContainer plugin, ProxyServer server, Guardian guardian) { - super(plugin, server); + this.plugin = plugin; + this.server = server; this.guardian = guardian; } @Override - public Class getEventClass() { - return PlayerChatEvent.class; + public void register() { + server.getEventManager().register(plugin, this); } @Override - protected CentralisedFuture beginComputation(PlayerChatEvent event) { - Player player = event.getPlayer(); - return guardian.checkChat(player.getUniqueId(), player.getRemoteAddress().getAddress(), null); + public void unregister() { + server.getEventManager().unregisterListener(plugin, this); } - @Override - protected void executeNonNullResult(PlayerChatEvent event, Component message) { - event.setResult(ChatResult.denied()); - event.getPlayer().sendMessage(message); + @Subscribe(order = PostOrder.EARLY) + public EventTask onChat(PlayerChatEvent event) { + return combinedChatEvent(event, event.getPlayer(), null, ChatResult.denied()); + } + + @Subscribe(order = PostOrder.EARLY) + public EventTask onCommand(CommandExecuteEvent event) { + if (!(event.getCommandSource() instanceof Player player)) { + return null; + } + return combinedChatEvent(event, player, event.getCommand(), CommandExecuteEvent.CommandResult.denied()); + } + + private , R extends ResultedEvent.Result> EventTask combinedChatEvent( + E event, Player player, String command, R deniedResult) { + if (!event.getResult().isAllowed()) { + return null; + } + return EventTask.resumeWhenComplete(guardian.checkChat( + player.getUniqueId(), player.getRemoteAddress().getAddress(), command + ).thenAccept((message) -> { + if (message != null) { + event.setResult(deniedResult); + player.sendMessage(message); + } + })); } } diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/CommandListener.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/CommandListener.java deleted file mode 100644 index 5ac6e9bb3..000000000 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/CommandListener.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * LibertyBans - * Copyright © 2022 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 - * and navigate to version 3 of the GNU Affero General Public License. - */ - -package space.arim.libertybans.env.velocity; - -import com.velocitypowered.api.event.command.CommandExecuteEvent; -import com.velocitypowered.api.event.command.CommandExecuteEvent.CommandResult; -import com.velocitypowered.api.plugin.PluginContainer; -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ProxyServer; -import jakarta.inject.Inject; -import net.kyori.adventure.text.Component; -import space.arim.libertybans.core.selector.Guardian; -import space.arim.omnibus.util.concurrent.CentralisedFuture; - -public final class CommandListener extends VelocityAsyncListener { - - private final Guardian guardian; - - @Inject - public CommandListener(PluginContainer plugin, ProxyServer server, Guardian guardian) { - super(plugin, server); - this.guardian = guardian; - } - - @Override - public Class getEventClass() { - return CommandExecuteEvent.class; - } - - @Override - protected boolean skipEvent(CommandExecuteEvent event) { - return !(event.getCommandSource() instanceof Player); - } - - @Override - protected CentralisedFuture beginComputation(CommandExecuteEvent event) { - Player player = (Player) event.getCommandSource(); - return guardian.checkChat(player.getUniqueId(), player.getRemoteAddress().getAddress(), event.getCommand()); - } - - @Override - protected void executeNonNullResult(CommandExecuteEvent event, Component message) { - event.setResult(CommandResult.denied()); - event.getCommandSource().sendMessage(message); - } - -} diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ConnectionListener.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ConnectionListener.java index af845a4ca..ce019f2c6 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ConnectionListener.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ConnectionListener.java @@ -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 @@ -19,46 +19,85 @@ package space.arim.libertybans.env.velocity; +import com.velocitypowered.api.event.EventTask; +import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.ResultedEvent.ComponentResult; +import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.connection.LoginEvent; +import com.velocitypowered.api.event.player.ServerPreConnectEvent; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.server.RegisteredServer; import jakarta.inject.Inject; -import net.kyori.adventure.text.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import space.arim.libertybans.core.env.PlatformListener; import space.arim.libertybans.core.selector.Guardian; -import space.arim.omnibus.util.concurrent.CentralisedFuture; +import space.arim.omnibus.util.ThisClass; -import java.net.InetAddress; -import java.util.UUID; - -public final class ConnectionListener extends VelocityAsyncListener { +public final class ConnectionListener implements PlatformListener { + private final PluginContainer plugin; + private final ProxyServer server; private final Guardian guardian; + private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); + @Inject public ConnectionListener(PluginContainer plugin, ProxyServer server, Guardian guardian) { - super(plugin, server); + this.plugin = plugin; + this.server = server; this.guardian = guardian; } @Override - public Class getEventClass() { - return LoginEvent.class; + public void register() { + server.getEventManager().register(plugin, this); } @Override - protected CentralisedFuture beginComputation(LoginEvent event) { + public void unregister() { + server.getEventManager().unregisterListener(plugin, this); + } + + @Subscribe(order = PostOrder.EARLY) + public EventTask onConnect(LoginEvent event) { + if (!event.getResult().isAllowed()) { + logger.trace("Event {} is already blocked", event); + return null; + } Player player = event.getPlayer(); - UUID uuid = player.getUniqueId(); - String name = player.getUsername(); - InetAddress address = player.getRemoteAddress().getAddress(); - return guardian.executeAndCheckConnection(uuid, name, address); + return EventTask.resumeWhenComplete(guardian.executeAndCheckConnection( + player.getUniqueId(), player.getUsername(), player.getRemoteAddress().getAddress() + ).thenAccept((message) -> { + if (message == null) { + logger.trace("Event {} will be permitted", event); + } else { + event.setResult(ComponentResult.denied(message)); + } + })); } - @Override - protected void executeNonNullResult(LoginEvent event, Component message) { - event.setResult(ComponentResult.denied(message)); + @Subscribe(order = PostOrder.EARLY) + public EventTask onServerSwitch(ServerPreConnectEvent event) { + if (!event.getResult().isAllowed()) { + return null; + } + RegisteredServer destination = event.getResult().getServer().orElse(null); + if (destination == null) { + // Properly speaking, the API does not exclude this possibility + return null; + } + Player player = event.getPlayer(); + return EventTask.resumeWhenComplete(guardian.checkServerSwitch( + player.getUniqueId(), player.getRemoteAddress().getAddress(), destination.getServerInfo().getName() + ).thenAccept((message) -> { + if (message != null) { + event.setResult(ServerPreConnectEvent.ServerResult.denied()); + player.sendMessage(message); + } + })); } } diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityAsyncListener.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityAsyncListener.java deleted file mode 100644 index 97c05cd01..000000000 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityAsyncListener.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * LibertyBans - * Copyright © 2022 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 - * and navigate to version 3 of the GNU Affero General Public License. - */ - -package space.arim.libertybans.env.velocity; - -import com.velocitypowered.api.event.AwaitingEventExecutor; -import com.velocitypowered.api.event.Continuation; -import com.velocitypowered.api.event.EventManager; -import com.velocitypowered.api.event.EventTask; -import com.velocitypowered.api.event.PostOrder; -import com.velocitypowered.api.event.ResultedEvent; -import com.velocitypowered.api.plugin.PluginContainer; -import com.velocitypowered.api.proxy.ProxyServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import space.arim.libertybans.core.env.PlatformListener; -import space.arim.omnibus.util.ThisClass; -import space.arim.omnibus.util.concurrent.CentralisedFuture; -import space.arim.omnibus.util.concurrent.impl.IndifferentCentralisedFuture; - -abstract class VelocityAsyncListener, R> implements PlatformListener { - - private final PluginContainer plugin; - private final ProxyServer server; - - // Visible for testing - final AsyncHandler handler = new AsyncHandler(); - - private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); - - VelocityAsyncListener(PluginContainer plugin, ProxyServer server) { - this.plugin = plugin; - this.server = server; - } - - @Override - public final void register() { - Class eventClass = getEventClass(); - EventManager eventManager = server.getEventManager(); - eventManager.register(plugin, eventClass, PostOrder.EARLY, handler); - } - - abstract Class getEventClass(); - - @Override - public void unregister() { - EventManager eventManager = server.getEventManager(); - eventManager.unregister(plugin, handler); - } - - /** Can be overridden to skip events */ - protected boolean skipEvent(E event) { - return false; - } - - protected abstract CentralisedFuture beginComputation(E event); - - protected abstract void executeNonNullResult(E event, R result); - - // Visible for testing - class AsyncHandler implements AwaitingEventExecutor { - - @Override - public EventTask executeAsync(E event) { - if (skipEvent(event)) { - return null; - } - if (!event.getResult().isAllowed()) { - logger.debug("Event {} is already blocked", event); - return null; - } - CentralisedFuture future = beginComputation(event); - return EventTask.resumeWhenComplete(future.thenAccept((result) -> { - if (result == null) { - logger.trace("Event {} will be permitted", event); - return; - } - executeNonNullResult(event, result); - })); - } - - void executeAndWait(E event) { - EventTask eventTask = executeAsync(event); - if (eventTask == null) { - return; - } - CentralisedFuture future = new IndifferentCentralisedFuture<>(); - eventTask.execute(new Continuation() { - @Override - public void resume() { - future.complete(null); - } - - @Override - public void resumeWithException(Throwable exception) { - future.completeExceptionally(exception); - } - }); - future.join(); - } - } - -} diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityBindModule.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityBindModule.java index b8fc2f3f4..180fee720 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityBindModule.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityBindModule.java @@ -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 @@ -25,6 +25,8 @@ import space.arim.api.env.PlatformHandle; import space.arim.api.env.velocity.VelocityPlatformHandle; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; import space.arim.libertybans.core.importing.PlatformImportSource; @@ -54,6 +56,14 @@ public EnvUserResolver resolver(VelocityUserResolver resolver) { return resolver; } + public EnvMessageChannel messageChannel(VelocityMessageChannel messageChannel) { + return messageChannel; + } + + public EnvServerNameDetection serverNameDetection() { + return (scopeManager) -> scopeManager.detectServerName("proxy"); + } + public PlatformImportSource platformImportSource() { throw new UnsupportedOperationException("It is impossible to import from vanilla on Velocity"); } diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnforcer.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnforcer.java index 2a01f0d64..d3ec72758 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnforcer.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnforcer.java @@ -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 @@ -30,10 +30,12 @@ import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.env.AbstractEnvEnforcer; import space.arim.libertybans.core.env.Interlocutor; +import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.net.InetAddress; +import java.util.Collection; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; @@ -42,17 +44,19 @@ public class VelocityEnforcer extends AbstractEnvEnforcer { private final ProxyServer server; + private final VelocityMessageChannel messageChannel; @Inject public VelocityEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter formatter, - Interlocutor interlocutor, ProxyServer server) { + Interlocutor interlocutor, ProxyServer server, VelocityMessageChannel messageChannel) { super(futuresFactory, formatter, interlocutor, AudienceRepresenter.identity()); this.server = server; + this.messageChannel = messageChannel; } @Override - protected CentralisedFuture doForAllPlayers(Consumer callback) { - server.getAllPlayers().forEach(callback); + public CentralisedFuture doForAllPlayers(Consumer> callback) { + callback.accept(server.getAllPlayers()); return completedVoid(); } @@ -61,6 +65,12 @@ public void kickPlayer(Player player, Component message) { player.disconnect(message); } + @Override + public boolean sendPluginMessageIfListening(Player player, PluginMessage pluginMessage, D data) { + messageChannel.sendPluginMessage(player, pluginMessage, data); + return true; + } + @Override public CentralisedFuture doForPlayerIfOnline(UUID uuid, Consumer callback) { server.getPlayer(uuid).ifPresent(callback); @@ -77,6 +87,11 @@ public InetAddress getAddressFor(Player player) { return player.getRemoteAddress().getAddress(); } + @Override + public String getNameFor(Player player) { + return player.getUsername(); + } + @Override public boolean hasPermission(Player player, String permission) { return player.hasPermission(permission); diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnv.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnv.java index a0197701e..c80e1b8f4 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnv.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnv.java @@ -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 @@ -31,15 +31,13 @@ public final class VelocityEnv implements Environment { private final Provider connectionListener; private final Provider chatListener; - private final Provider commandListener; private final CommandHandler.CommandHelper commandHelper; @Inject public VelocityEnv(Provider connectionListener, Provider chatListener, - Provider commandListener, CommandHandler.CommandHelper commandHelper) { + CommandHandler.CommandHelper commandHelper) { this.connectionListener = connectionListener; this.chatListener = chatListener; - this.commandListener = commandListener; this.commandHelper = commandHelper; } @@ -48,7 +46,6 @@ public Set createListeners() { return Set.of( connectionListener.get(), chatListener.get(), - commandListener.get(), new CommandHandler(commandHelper, Commands.BASE_COMMAND_NAME, false) ); } diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityLauncher.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityLauncher.java index 664654325..f1cb1e208 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityLauncher.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityLauncher.java @@ -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 @@ -31,6 +31,7 @@ import space.arim.libertybans.core.PillarOneBindModule; import space.arim.libertybans.core.PillarTwoBindModule; import space.arim.libertybans.core.addon.AddonLoader; +import space.arim.libertybans.core.env.InstanceType; import space.arim.omnibus.Omnibus; import space.arim.omnibus.OmnibusProvider; @@ -60,6 +61,7 @@ public BaseFoundation launch() { .bindInstance(PluginContainer.class, plugin) .bindInstance(ProxyServer.class, server) .bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), folder) + .bindInstance(InstanceType.class, InstanceType.PROXY) .bindInstance(Omnibus.class, omnibus) .addBindModules( new ApiBindModule(), diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityMessageChannel.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityMessageChannel.java new file mode 100644 index 000000000..38addb156 --- /dev/null +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityMessageChannel.java @@ -0,0 +1,84 @@ +/* + * 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 + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.env.velocity; + +import com.velocitypowered.api.event.EventHandler; +import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; +import jakarta.inject.Inject; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.PluginMessageAsBytes; +import space.arim.libertybans.core.env.message.PluginMessage; + +import java.util.function.Consumer; + +public final class VelocityMessageChannel implements EnvMessageChannel> { + + private final PluginContainer plugin; + private final ProxyServer server; + + private static final ChannelIdentifier BUNGEE_CHANNEL = MinecraftChannelIdentifier.create("bungeecord", "main"); + + @Inject + public VelocityMessageChannel(PluginContainer plugin, ProxyServer server) { + this.plugin = plugin; + this.server = server; + } + + void sendPluginMessage(Player player, PluginMessage pluginMessage, D data) { + player.getCurrentServer().ifPresent((server) -> { + server.sendPluginMessage( + BUNGEE_CHANNEL, + new PluginMessageAsBytes<>(pluginMessage).generateBytes(data) + ); + }); + } + + @Override + public void installHandler(EventHandler handler) { + server.getEventManager().register(plugin, PluginMessageEvent.class, handler); + } + + @Override + public void uninstallHandler(EventHandler handler) { + server.getEventManager().unregister(plugin, handler); + } + + @Override + public EventHandler createHandler(Consumer acceptor, PluginMessage pluginMessage) { + class AsHandler implements EventHandler { + + @Override + public void execute(PluginMessageEvent event) { + if (event.getSource() instanceof ServerConnection && event.getIdentifier().equals(BUNGEE_CHANNEL)) { + new PluginMessageAsBytes<>(pluginMessage) + .readBytes(event.getData()) + .ifPresent(acceptor); + } + } + } + return new AsHandler(); + } +} diff --git a/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/ConnectionListenerTest.java b/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/ConnectionListenerTest.java index 2c0034847..e20047ef4 100644 --- a/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/ConnectionListenerTest.java +++ b/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/ConnectionListenerTest.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 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 @@ -19,6 +19,8 @@ package space.arim.libertybans.env.velocity; +import com.velocitypowered.api.event.Continuation; +import com.velocitypowered.api.event.EventTask; import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.plugin.PluginContainer; @@ -40,6 +42,7 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -97,13 +100,32 @@ private void deniedResult(String text) { fixedResult(Component.text(text)); } + private void fireAndAwait(LoginEvent event) { + EventTask task = listener.onConnect(event); + if (task != null) { + var future = new CompletableFuture<>(); + task.execute(new Continuation() { + @Override + public void resume() { + future.complete(null); + } + + @Override + public void resumeWithException(Throwable exception) { + future.completeExceptionally(exception); + } + }); + future.join(); + } + } + @Test public void allowed() { allowedResult(); Player player = mockPlayer(); LoginEvent event = new LoginEvent(player); var originalResult = event.getResult(); - listener.handler.executeAndWait(event); + fireAndAwait(event); assertEquals(originalResult.getReasonComponent(), event.getResult().getReasonComponent()); } @@ -112,11 +134,12 @@ public void denied() { deniedResult("denied"); Player player = mockPlayer(); LoginEvent event = new LoginEvent(player); - listener.handler.executeAndWait(event); + fireAndAwait(event); assertEquals( "denied", event.getResult().getReasonComponent() - .map(PlainComponentSerializer.plain()::serialize).orElse(null)); + .map(PlainComponentSerializer.plain()::serialize).orElse(null) + ); } @Test @@ -126,9 +149,11 @@ public void deniedByOtherPlugin() { LoginEvent event = new LoginEvent(player); var denial = ResultedEvent.ComponentResult.denied(Component.text("denial")); event.setResult(denial); - listener.handler.executeAndWait(event); + fireAndAwait(event); assertEquals( denial.getReasonComponent(), - event.getResult().getReasonComponent()); + event.getResult().getReasonComponent() + ); } + } diff --git a/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/VelocityEnvTest.java b/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/VelocityEnvTest.java index e51147ae3..8b74febfc 100644 --- a/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/VelocityEnvTest.java +++ b/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/VelocityEnvTest.java @@ -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 @@ -21,11 +21,8 @@ import org.junit.jupiter.api.Test; import space.arim.api.util.testing.InjectableConstructor; -import space.arim.libertybans.core.env.ParallelisedListener; import space.arim.libertybans.core.env.PlatformListener; -import java.util.Set; - public class VelocityEnvTest { @Test @@ -33,11 +30,9 @@ public void allListenersDeclared() { new InjectableConstructor(VelocityEnv.class) .verifyParametersContainSubclassesOf(PlatformListener.class, (clazz) -> { // Exclude CommandHandler since it is constructed directly - // Exclude VelocityAsyncListener and ParallelisedListener, which would never be injected - boolean excluded = Set.of( - CommandHandler.class, VelocityAsyncListener.class, ParallelisedListener.class - ).contains(clazz); - return !excluded; + // Use only classes in our package or subpackages + return !clazz.equals(CommandHandler.class) && + clazz.getPackageName().startsWith(getClass().getPackageName()); }); } } diff --git a/docs/Comparison-to-LiteBans.md b/docs/Comparison-to-LiteBans.md index ab8c1f031..131d055a8 100644 --- a/docs/Comparison-to-LiteBans.md +++ b/docs/Comparison-to-LiteBans.md @@ -77,9 +77,9 @@ LibertyBans requires Java 17 whereas LiteBans permits Java 8. Neither LibertyBans nor LiteBans requires an external database. LibertyBans uses HSQLDB by default, and LiteBans uses H2 by default. -Both LibertyBans and LiteBans supports MariaDB, MySQL, and PostgreSQL. LiteBans also has support for SQLite, but SQLite usage is discouraged by LiteBans. +Both LibertyBans and LiteBans support MariaDB, MySQL, and PostgreSQL. LiteBans also has support for SQLite, but SQLite usage is discouraged by LiteBans. -LibertyBans requires certain minimum versions for database servers. At least MySQL 8.0, MariaDB 10.6, or PostgreSQL 12 is required. Older versions are not supported. +LibertyBans requires certain minimum versions for database servers. At least MySQL 8.0, MariaDB 10.3, or PostgreSQL 12 is required. Older versions are not supported. ## Platform Support @@ -94,7 +94,9 @@ LiteBans' support for Velocity came after repeated user requests. It was suggest ### Geyser Support -LibertyBans has Geyser support (since 30 May 2021), whereas LiteBans does not. +LibertyBans and LiteBans have Geyser support. + +However, documentation suggests LiteBans requires the prefix to be the period character (".").[4](#note4) ### Core punishment types @@ -116,7 +118,7 @@ Although both plugins support the exemption feature which prevents staff from ba For offline players, LiteBans's permission checking depends on Vault on single servers. Without a Vault-compatible permissions plugin, the feature breaks silently for offline players. -LibertyBans' exemption feature will never break silently. However, this imposes greater requirements. LibertyBans requires the installation of a supported exemption provider - currently LuckPerms or Vault. Without an exemption provider, the feature is entirely unavailable. +LibertyBans' exemption feature will never break silently. However, it requires the installation of a supported exemption provider - currently LuckPerms or Vault. Without an exemption provider, the feature is entirely unavailable. ### Importing From Other Plugins @@ -126,9 +128,9 @@ LiteBans supports importing from AdvancedBan, BanManager, BungeeAdminTools, MaxB ### Server Scopes -LiteBans enables punishments scoped to certain servers. +Both plugins enable punishments scoped to certain servers. -LibertyBans does not implement this feature. +However, LiteBans lacks scope categories, or the ability to group servers into a single scope.[5](#note5) ### Multi-Proxy / Multi-Instance Synchronization @@ -142,6 +144,10 @@ Both LibertyBans and LiteBans provide synchronization across multiple instances, 3: Ruan. "[Feature] Spongepowered?". LiteBans Gitlab Issue comment. https://gitlab.com/ruany/LiteBans/-/issues/41#note_324182783 [↩](#note3ret) +4: Ruan. "LiteBans 2.7.5 (Connection error fix)". SpigotMC Resource Update. https://www.spigotmc.org/resources/litebans.3715/update?update=414133 + +5: lewisakura. "Server scope groups". LiteBans Gitlab Issue. https://gitlab.com/ruany/LiteBans/-/issues/452 + ### Disclaimer Please note that no harm is meant to subjects of criticism. If the writing sounds harsh, we apologize; please let us know and we will make the language less harsh. diff --git a/docs/Permissions.md b/docs/Permissions.md index db7879eb3..f01be3b0c 100644 --- a/docs/Permissions.md +++ b/docs/Permissions.md @@ -8,6 +8,8 @@ As a pre-requisite for any command, you must have the permission: `libertybans.commands` +## Punishment ## + ### Bans ### * `libertybans.ban.do.target.uuid` - ban players @@ -66,6 +68,10 @@ For obvious reasons, there are no duration permissions for kicks; moreover there * `libertybans.admin.import` - /libertybans import * `libertybans.admin.viewips` - Allows staff to view IP addresses if *censor-ip-addresses* is turned on in the configuration. +### Scopes + +If scope permissions are enabled, additional permissions are required to punish and list punishments. See the [Scoped Punishments](Scoped-Punishments.md) page. + ## Addons Addon-specific permissions are described on the [Addons](Addons) page. diff --git a/docs/Plugin-Comparison-Conclusions.md b/docs/Plugin-Comparison-Conclusions.md index c2be7a9c0..2e9b331c5 100644 --- a/docs/Plugin-Comparison-Conclusions.md +++ b/docs/Plugin-Comparison-Conclusions.md @@ -50,37 +50,29 @@ A good reason to use BanManager would be if you absolutely needed to use Java 8, ## Why not LiteBans -Little information is known about LiteBans. In fact, it may be illegal for us to disclose the results of our investigation into the workings of LiteBans. +Most importantly, LiteBans is proprietary and closed-source. The developer team consists of a single individual, and the plugin jar is obfuscated. If LiteBans were ever to halt development, or the author to vanish, the whole plugin would have to be rewritten from scratch, because the source code could not be recovered. -Because it is impossible for us to audit LiteBans, we cannot make any hard conclusions regarding the LiteBans codebase. It may be wholly *incorrect* or *unreliable*. We can't say. +Due to LiteBans' proprietary nature, even simple updates and bug fixes must wait for the lone author. Contrast this to LibertyBans, which has two official maintainers, A248 and Simon. If one of them is absent, LibertyBans can still receive critical bug fixes such as updates to newer Minecraft versions, which has happened before. -We will focus on the major *known* reason not to use LiteBans. There may be other reasons than this. +As a further consequence, it is impossible for us to fully audit LiteBans. We cannot make any hard conclusions regarding the LiteBans codebase. It may be wholly *incorrect* or *unreliable*. We can't say. LiteBans might break down in specific situations, or fail silently in some cases. Or it might run fine all the time. LiteBans might have security vulnerabilities from using H2, a database which has [a history of vulnerabilities](https://www.cvedetails.com/vulnerability-list.php?vendor_id=17893&product_id=45580). We'll never know. -### The Future is Uncertain +There are other reasons not to use LiteBans. Evidence suggests LiteBans does not employ automated testing much, increasing the prevalence of bugs in releases. Moreover, its database schema does not utilize integrity constraints, which can lead to data corruption. Yet because accessing the database is recommended over API use, LiteBans has to keep backwards compatibility with its backwards database, hindering development. -The most compelling reason not to use LiteBans regards its future. LiteBans' future is arguably in peril. By relying on LiteBans, you place your server at risk. +Features that languish on the LiteBans issue tracker will stay incomplete until the author decides to add them. No one else can contribute them or prioritize them. Even if a feature is heavily demanded, only the LiteBans author decides. As of 18 August 2023, the following LiteBans feature requests are fully implemented by LibertyBans: +* [Define scopes in punishment templates](https://gitlab.com/ruany/LiteBans/-/issues/502) +* [Extend punishment duration](https://gitlab.com/ruany/LiteBans/-/issues/494) +* [Purge a punishment completely](https://gitlab.com/ruany/LiteBans/-/issues/494) (same link) +* [Server scope groups](https://gitlab.com/ruany/LiteBans/-/issues/452) +* [Default reason for kicking](https://gitlab.com/ruany/LiteBans/-/issues/406) +* [Tab complete offline player names](https://gitlab.com/ruany/LiteBans/-/issues/349) +* [Add /ipkick command](https://gitlab.com/ruany/LiteBans/-/issues/301) +* [Confirmation for /staffrollback](https://gitlab.com/ruany/LiteBans/-/issues/185) +* [Notification permissions per punishment type](https://gitlab.com/ruany/LiteBans/-/issues/130) +* [Support Sponge platform](https://gitlab.com/ruany/LiteBans/-/issues/41) +* [Import vanilla IP bans](https://gitlab.com/ruany/LiteBans/-/issues/22) -The development of LiteBans depends on a single person. This has several implications, which bear on both the present and future: +For developers, the LiteBans API is poorly defined and commonly cited as a pain to work with. This makes it harder to integrate other plugins with LiteBans, unless the developer resorts to brittle command execution, forfeiting API guarantees. Not to mention that the inability to look at method implementations shackles debugging attempts. The API does not fully follow semantic versioning, either. -* If, for any reason, the author loses interest, has a better job, or is otherwise busy with other matters, development of LiteBans will stop. - * Unlike other proprietary plugins, LiteBans is a 1-person team. Other popular proprietary plugins are often composed of a team, so that if one developer leaves, development as a whole may continue. - * If the development of LiteBans stops, that is the end of LiteBans. No on else has a copy of the source code. No amount of money would bring LiteBans back if the author decided to abandon it permanently. +Ultimately, little information is known about LiteBans. In fact, it may even be illegal for us to disclose the results of our investigation into the workings of LiteBans. -* Only the author can debug the plugin. - * Even if you never plan to debug any of your plugins, other users will. As a user, you benefit from *other users* who *do* debug a plugin you both use. - * On a large network, it is imperative that you retain the ability to debug your own plugins not only *in isolation*, but also *in relation to one another*. Complex bugs can arise through interactions between plugins. - * See also [The Bug Nobody is Allowed to Understand](https://www.gnu.org/philosophy/bug-nobody-allowed-to-understand.en.html) - * Complex bugs arising from multiple plugins need not be LiteBans' fault, but they can still exist because of the presence of LiteBans. Sometimes, a bug is dependent on the most extraneous circumstances. Retaining the ability to debug your own proxy, with all its various intricacies, is essential. - * The LiteBans author will not personally debug your entire proxy. - -* Only the author can make a new release. LiteBans follows a fixed release model. - * Suppose a bug is fixed. The LiteBans author tells you to wait for the next release. You *must* wait until the next release; there is no alternative. - * If a critical security vulnerability is discovered, you will need to shut down your server until a new LiteBans release is made. - * It is naïve to think that the LiteBans author will *always* respond in a timely manner to the latest security vulnerabilities. Sometimes, you need to patch your own software. Even if you aren't a developer, you can apply someone else's patch. With LiteBans, this is impossible. - * Think security vulnerabilities don't happen to plugins? Think again. The H2 database, bundled by LiteBans, has [multiple security notices (CVEs)](https://www.cvedetails.com/vulnerability-list.php?vendor_id=17893&product_id=45580). It is possible to be vulnerable to problems in H2 even if you do not use H2 with LiteBans. - * More importantly, the modern software chain depends on code from dozens of libraries. Through no fault of a plugin, a plugin can rely on a library which has a security exploit. This can, in extreme situations, lead to a system takeover. - -* Have a feature request, but the author of LiteBans doesn't want to implement it? You will *never* see the request implemented. - * No one can fork LiteBans and add the feature. - * Even if you try to hire another developer, the other developer will not be able to modify LiteBans. - * Because only LiteBans can implement feature requests, and no one can fork it, competition is reduced. Intentionally limiting competition is an indicator of a bad product. +Imagine one day that you find a very complex bug and track it down to LiteBans. Complex bugs arising from multiple plugins need not even be LiteBans' fault, but they can still exist because of the presence of LiteBans. Sometimes, a bug is dependent on the most extraneous circumstances. However, with LiteBans, we'd have a case of [The Bug Nobody is Allowed to Understand](https://www.gnu.org/philosophy/bug-nobody-allowed-to-understand.en.html). diff --git a/docs/Quick-Plugin-Comparison.md b/docs/Quick-Plugin-Comparison.md index 096bac549..361307a0e 100644 --- a/docs/Quick-Plugin-Comparison.md +++ b/docs/Quick-Plugin-Comparison.md @@ -1,4 +1,4 @@ -This is a quick table for comparing between LibertyBans, AdvancedBan, and LiteBans. +This is a quick table for comparing between LibertyBans, AdvancedBan, BanManager, and LiteBans. **NB: *Tables and graphs do not tell the full picture!*** To really understand what's going on here, read the detailed and explanatory comparisons. You can find these on the sidebar at the right. For each plugin here, there is a detailed comparison between LibertyBans and the plugin. @@ -16,19 +16,19 @@ comparisons. You can find these on the sidebar at the right. For each plugin her [GPL]:https://www.gnu.org/graphics/gplv3-127x51.png [CC-BY-NC]:http://mirrors.creativecommons.org/presskit/buttons/88x31/png/by-nc.png -| Plugin | Supported Platforms | Java Req. | Free (Gratis) | Open License | Database Support | Thread-Safe Design | Stable API | Geyser Support | Multi-Instance Support | Connection Pool | Exemption | Server Scopes | Uses UUIDs | Schema Integrity | Import From | -|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|---------------|----------------|--------------------------------------------|--------------------|------------|----------------|------------------------|-----------------|-----------|---------------|------------|------------------|---------------------------------------------------------------------------------------| -| LibertyBans | ![Bukkit]Bukkit
Bungee
Sponge Velocity | 17+ | ✔️ | ✔️ ![AGPL] | HSQLDB (local), MariaDB, MySQL, PostgreSQL | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | AdvancedBan
BanManager
LiteBans
vanilla | -| AdvancedBan | ![Bukkit]Bukkit
Bungee | 8+ | ✔️ | ✔️ ![GPL] | HSQLDB (local), MariaDB, MySQL | ❌ | ❌ | ❓ | ❌ | ❌ | ✔️ | ❌ | ➖ | ❌ | | -| BanManager | ![Bukkit]Bukkit
Bungee
Sponge | 8+ | ✔️ | ➖️ ![CC-BY-NC] | H2 (local), MariaDB, MySQL | ❌️ | ❌ | ➖ | ✔️ | ✔️ | ❌ | ➖ | ✔️ | ✔️ | AdvancedBan
vanilla | -| LiteBans | ![Bukkit]Bukkit
Bungee
Velocity | 8+ | ❌ | ❌ Proprietary | H2 (local), MariaDB, MySQL, PostgreSQL | ❓ | ➖ | ❌ | ✔️ | ❓ | ✔️ | ✔️ | ✔️ | ❌ | AdvancedBan
BanManager
BungeeAdminTools
MaxBans
UltraBans
vanilla | -| vanilla | ![Bukkit]Bukkit
Sponge | 8+ | ✔️ | ❌ Proprietary | Flatfile | ✔️ | ✔️ | ✔️ | ❌ | NA | ❌ | ❌ | ✔️ | ❌ | | +| Plugin | Supported Platforms | Java Req. | Free (Gratis) | Open License | Debuggable, Modifiable | Databases Available | Thread-Safe Design | Stable API | Geyser Support | Multi-Instance Support | Connection Pool | Exemption | Server Scopes | Uses UUIDs | Schema Integrity | Switch Storage Backends | Import From | +|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|---------------|-----------------|------------------------|--------------------------------------------|--------------------|------------|----------------|------------------------|-----------------|-----------|---------------|------------|------------------|-------------------------|--------------------------------------------------------------------------------------------------------------------------------| +| LibertyBans | ![Bukkit]Bukkit
Bungee
Sponge Velocity | 17+ | ✔️ | ✔️ ![AGPL] | ✔️ | HSQLDB (local), MariaDB, MySQL, PostgreSQL | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | AdvancedBan
BanManager
LiteBans
vanilla | +| AdvancedBan | ![Bukkit]Bukkit
Bungee | 8+ | ✔️ | ✔️ ![GPL] | ✔️ | HSQLDB (local), MariaDB, MySQL | ❌ | ❌ | ❓ | ❌ | ❌ | ✔️ | ❌ | ➖️ | ❌ | ❌ | | +| BanManager | ![Bukkit]Bukkit
Bungee
Sponge | 8+ | ✔️ | ➖️️ ![CC-BY-NC] | ✔️ | H2 (local), MariaDB, MySQL | ❌️ | ❌ | ➖️️ | ✔️ | ✔️ | ❌ | ➖️ | ✔️ | ✔️ | ➖️ | AdvancedBan
vanilla | +| LiteBans | ![Bukkit]Bukkit
Bungee
Velocity | 8+ | ❌ | ❌ Proprietary | ❌ Obfuscated | H2 (local), MariaDB, MySQL, PostgreSQL | ❓ | ➖️ | ➖️️ | ✔️ | ❓ | ✔️ | ➖️ | ✔️ | ❌ | ✔️ | AdvancedBan
BanHammer**
BanManager
BungeeAdminTools**
LibertyBans ➖️
MaxBans**
UltraBans**
vanilla | +| vanilla | ![Bukkit]Bukkit
Sponge | 8+ | ✔️ | ❌ Proprietary | NA | Flatfile | ✔️ | ✔️ | ✔️ | ❌ | NA | ❌ | ❌ | ✔️ | ❌ | NA | | Legend: ✔️ – Yes -➖ – Partially +➖️ – Partially ❌ – No @@ -36,6 +36,16 @@ Legend: NA - Not applicable +** - This plugin has been abandoned for at least 3 years. + +### Why does LibertyBans check all the boxes? + +It didn't used to. This chart was created when LibertyBans didn't have multi-instance support, exemption, server scopes, or switching storage backends. With this exception of switching storage, all these features were listed in the table, but they were unimplemented and unrequested -- with no plans to add them! Back then, LibertyBans used to receive ❌ or ➖️ in many areas. + +Over time, features were requested and implemented, so LibertyBans fulfills these categories now and receives ✔️. Competitor plugins still have capabilities not found in LibertyBans, but they aren't deemed sufficiently important or central to the purpose of punishment to have a place on the table. + +How do we know the features listed in the table are sufficiently important and central to the purpose of a punishment plugin? Well, in most cases, we *predicted* which features would be most important, so we placed them in the table. The predictions turned out to be correct, since the categories in the table were the most popular feature requests. + ## Categories Some categories require further explanation because they are not self-explanatory. They are described here. @@ -60,6 +70,14 @@ BanManager's license prohibits commercial use. This makes it **illegal** to use Disclaimer: This is not legal advice and the writer is not a lawyer. +### Debuggable, Modifiable + +Whether the plugin can be debugged by users and modified by developers. +* Debugging is a critical tool because it allows administrators to fix problems deep in the software stack. This remains true regardless of whether a plugin is at fault for the bug. +* Modifying a plugin safeguards user and developer freedom. It provides an escape hatch if the original author is unwilling to implement a feature or a workaround. + +LiteBans is obfuscated, meaning its plugin jar is intentionally scrambled to prevent anyone from inspecting its operations or making any sort of modification. + ### Thread-Safe Design If a plugin is not thread-safe, it will have unreliable and buggy behavior. However, some of this unreliable behavior may only occur in exceptional and rare circumstances, making it very hard to debug and diagnose. @@ -74,6 +92,12 @@ See [semver.org](https://semver.org/) and the detailed plugin-specific compariso LiteBans' *partial* ranking: LiteBans follows the semver constraint of restricting breaking changes to major versions, but does not follow semver for additions of new API, which *should* be done in minor releases. +### Geyser Support + +Whether the plugin is compatible with Geyser/Floodgate, a plugin which allows Bedrock Edition players to join the server or proxy by changing usernames. + +LiteBans' *partial* ranking: LiteBans claims Geyser compatibility, but its documentation suggests the period character is the only supported prefix for Bedrock usernames. + ### Multi-Instance Support This feature allows synchronizing punishments across multiple instances of the given plugin. It is relevant for proxies. @@ -90,7 +114,7 @@ AdvancedBan has a connection pool, but in practice, can only use 1 connection at This feature is relevant for proxies. Whether the plugin has the ability to define "scopes" and create punishments applying to certain scopes. This allows server administrators to create punishments applying to a specific backend server or a group of backend servers. -BanManager's *partial* ranking: BanManager has the ability to create a "local" punishment, meaning it applies to one backend server. However, it does not have the ability to define punishments applying to a group of backend servers. +BanManager's and LiteBan's *partial* ranking: BanManager has the ability to create a "local" punishment, meaning it applies to one backend server. However, it does not have the ability to define punishments applying to a group of backend servers; further, local punishments cannot be created from separate servers. LiteBans has server scopes, but similarly, it cannot create scopes applying to a group of servers. ### Uses UUIDs @@ -106,3 +130,19 @@ Whether the data types defined by the plugin's database schema have appropriate * Otherwise, bugs in the plugin may corrupt user data. Data corruption, if it occurs, is not easy to recover from and may require manually interfacing with the database. + +### Switch Storage Backends + +Whether the user can switch between the supported database backends. This feature is also known as "self-importing." + +BanManager's *partial* ranking: BanManager only supports converting from H2 to MySQL/MariaDB, but it is not possible to switch back to H2. + +### Import From + +The other punishment suites this plugin can import from. + +LiteBans' *partial* ranking with respect to LibertyBans: LiteBans does not import server scopes, although scopes are a common feature between them. + +-------------------------------------------------------------------------- + +As a closing note, this information is kept up-to-date within a reasonable timeframe. We welcome PRs from those affiliated with other plugins to update this information when necessary. diff --git a/docs/Scoped-Punishments.md b/docs/Scoped-Punishments.md new file mode 100644 index 000000000..933c8c569 --- /dev/null +++ b/docs/Scoped-Punishments.md @@ -0,0 +1,56 @@ + +# Scoped Punishments + +The server scopes feature allows you to ban players from specific backend servers on a network such as BungeeCord or Velocity. + +For example, perhaps you want to ban a player from the PvP server for combat hacking; however, they beg and plead until eventually convincing you to allow them to play the Creative game mode on a different backend server. + +Scope configuration is controlled by the `scope.yml`. + +## Introduction + +There are three kinds of scopes: +1. The global scope, applying everywhere. +2. Per-server scopes. +3. Category scopes, applying to a group of servers. + +### Server Name + +The server name controls how the server itself is identified in per-server scopes. This is the same server name you configured proxy-wide: on BungeeCord, in the `config.yml`, and for Velocity, the `velocity.toml`. + +Usually, backend servers can automatically detect their server name (using a technique called plugin messaging). However, if automatic detection is insufficient, you can override the detected server name. + +On the proxy, LibertyBans will always know each backend server's name. The proxy *itself* can also be named by setting a value manually, although per-proxy punishments have limited practical use. + +### Categories + +Categories may be configured per-server. Simply write out the categories it will be included in. + +## Commands + +Scopes may be used in commands by specifying `-server=` with a server name, `-category=` with a category, or `-scope=` with *any* scope. + +With `-scope=`, the scope itself must be one of `server:`, `category:` or `*` for the global scope. For example, `-scope=category:pvp` is valid. + +### Punish commands + +If no scope is specified, punishing commands will use the configured default punishing scope. You may change this to the current server to ban players on the same server by default. + +### Listing commands + +Listing commands such as /banlist, /history, etc. will include punishments from all scopes. Specifying a scope will narrow the list of punishments to ones using that scope -- critically, this is NOT the same as punishments *applying* to that scope. For example, `/banlist -scope=*` will yield only punishments with the global scope, but will not show punishments with per-server or category scopes. + +### Examples + +* /ban A248 -server=lobby You're just too annoying in the lobby +* /mute A248 -category=clans A conversationalist is far too dangerous of a clans player +* /warn A248 -scope=* 7d It's possible to place the -server, -category, or -scope argument anywhere before the punishment reason +* /kick A248 Did you know that scoped kicks work too? The player will only be kicked if they're currently connected to the given scope + +### Permissions + +By default, permissions for scopes are disabled for compatibility. To require specific permissions, turn on `require-scope-permissions`. + +If enabled: +* Staff must have the `libertybans.scope.global`, `libertybans.scope.server.`, or `libertybans.scope.category.` permissions. +* The permission `libertybans.scope.default` must be granted to use commands without a scope argument. Not granting this permission requires staff members to specify an explicit scope. diff --git a/docs/_sidebar.md b/docs/_sidebar.md index e7ae93bf1..2f0943f4b 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -7,6 +7,7 @@ * [Lenient, Normal, and Strict punishment enforcement](Punishment-Enforcement_-Lenient,-Normal,-and-Strict-settings) * [Silent Punishments](Silent-Punishments) * [Composite Punishments](Guide-to-Composite-Punishments) + * [Scoped Punishments](Scoped-Punishments) * [Running Multiple Instances](Running-Multiple-Instances) * [Switching Storage Backends (Self-Importing)](Self-Importing) * [Addons](Addons) diff --git a/pom.xml b/pom.xml index 9e0b1a734..ab8204d94 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ 3 - 4 + 5 true