From f8f13240c3705aa6268dd9d09c98fdde7e9b301a Mon Sep 17 00:00:00 2001 From: A248 Date: Fri, 1 Sep 2023 16:18:19 -0500 Subject: [PATCH] Add API for alt detection and account management (#162) --- .../arim/libertybans/api/LibertyBans.java | 27 ++-- .../libertybans/api/user/AccountBase.java | 54 +++++++ .../api/user/AccountSupervisor.java | 68 +++++++++ .../arim/libertybans/api/user/AltAccount.java | 49 ++++++ .../libertybans/api/user/DetectionQuery.java | 103 +++++++++++++ .../libertybans/api/user/KnownAccount.java | 47 ++++++ .../arim/libertybans/core/ApiBindModule.java | 25 ++-- .../arim/libertybans/core/LibertyBansApi.java | 48 +++--- .../libertybans/core/alts/AccountHistory.java | 46 +++++- .../core/alts/AccountHistoryFormatter.java | 22 ++- .../core/alts/AltCheckFormatter.java | 69 ++++----- .../libertybans/core/alts/AltDetection.java | 139 +++++++++++------- .../libertybans/core/alts/DetectedAlt.java | 85 +++-------- .../libertybans/core/alts/KnownAccount.java | 84 ----------- .../libertybans/core/alts/ListFormat.java | 4 +- .../libertybans/core/alts/Supervisor.java | 83 +++++++++++ .../alts/AccountHistoryFormatterTest.java | 8 +- .../core/alts/AltCheckFormatterTest.java | 39 ++--- .../it/test/alts/AccountHistoryIT.java | 25 ++-- .../it/test/alts/AltDetectionIT.java | 50 ++++--- 20 files changed, 718 insertions(+), 357 deletions(-) create mode 100644 bans-api/src/main/java/space/arim/libertybans/api/user/AccountBase.java create mode 100644 bans-api/src/main/java/space/arim/libertybans/api/user/AccountSupervisor.java create mode 100644 bans-api/src/main/java/space/arim/libertybans/api/user/AltAccount.java create mode 100644 bans-api/src/main/java/space/arim/libertybans/api/user/DetectionQuery.java create mode 100644 bans-api/src/main/java/space/arim/libertybans/api/user/KnownAccount.java delete mode 100644 bans-core/src/main/java/space/arim/libertybans/core/alts/KnownAccount.java create mode 100644 bans-core/src/main/java/space/arim/libertybans/core/alts/Supervisor.java diff --git a/bans-api/src/main/java/space/arim/libertybans/api/LibertyBans.java b/bans-api/src/main/java/space/arim/libertybans/api/LibertyBans.java index d36c03800..5a1ce7052 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/LibertyBans.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/LibertyBans.java @@ -1,23 +1,25 @@ -/* - * 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; +import space.arim.libertybans.api.user.AccountSupervisor; import space.arim.omnibus.Omnibus; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; @@ -103,4 +105,11 @@ public interface LibertyBans { */ UserResolver getUserResolver(); + /** + * Gets the account supervisor for alt detection and management + * + * @return the account supervisor + */ + AccountSupervisor getAccountSupervisor(); + } diff --git a/bans-api/src/main/java/space/arim/libertybans/api/user/AccountBase.java b/bans-api/src/main/java/space/arim/libertybans/api/user/AccountBase.java new file mode 100644 index 000000000..5084abbb2 --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/user/AccountBase.java @@ -0,0 +1,54 @@ +/* + * 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.api.user; + +import space.arim.libertybans.api.NetworkAddress; + +import java.util.Optional; +import java.util.UUID; + +/** + * Base interface for accounts + * + */ +public interface AccountBase { + + /** + * The account UUID + * + * @return the UUID + */ + UUID uuid(); + + /** + * The latest username for the corresponding user, if it is known + * + * @return the latest username if there is one + */ + Optional latestUsername(); + + /** + * The account address + * + * @return the address + */ + NetworkAddress address(); + +} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/user/AccountSupervisor.java b/bans-api/src/main/java/space/arim/libertybans/api/user/AccountSupervisor.java new file mode 100644 index 000000000..f0990c6fa --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/user/AccountSupervisor.java @@ -0,0 +1,68 @@ +/* + * 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.api.user; + +import space.arim.libertybans.api.NetworkAddress; +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +import java.util.List; +import java.util.UUID; + +/** + * Enables detecting alts leveraging all the capabilities provided by the implementation + * + */ +public interface AccountSupervisor { + + /** + * Begins to detects alts for the given player + * + * @param uuid the player's UUID + * @param address the player's address + * @return a detection query builder + */ + DetectionQuery.Builder detectAlts(UUID uuid, NetworkAddress address); + + /** + * Finds accounts matching the given UUID + * + * @param uuid the uuid + * @return all matching accounts + */ + CentralisedFuture> findAccountsMatching(UUID uuid); + + /** + * Finds accounts matching the given address + * + * @param address the address + * @return all matching accounts + */ + CentralisedFuture> findAccountsMatching(NetworkAddress address); + + /** + * Finds accounts matching the given UUID or address + * + * @param uuid the uuid + * @param address the address + * @return all accounts matching the UUID OR the address + */ + CentralisedFuture> findAccountsMatching(UUID uuid, NetworkAddress address); + +} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/user/AltAccount.java b/bans-api/src/main/java/space/arim/libertybans/api/user/AltAccount.java new file mode 100644 index 000000000..03de5befb --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/user/AltAccount.java @@ -0,0 +1,49 @@ +/* + * 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.api.user; + +import space.arim.libertybans.api.PunishmentType; + +import java.time.Instant; + +/** + * A detected alt account + * + */ +public interface AltAccount extends AccountBase { + + /** + * The most recent time this alt was observed + * + * @return the last time this alt account was observed + */ + Instant lastObserved(); + + /** + * Whether the alt account has ANY active punishments of the specified type + * + * @param type the punishment type + * @return true if a punishment with the type exists and is active + * @throws IllegalArgumentException if the type was not queried for in the {@link DetectionQuery} + * (note this exception is not guaranteed to be thrown, false may be returned instead) + */ + boolean hasActivePunishment(PunishmentType type); + +} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/user/DetectionQuery.java b/bans-api/src/main/java/space/arim/libertybans/api/user/DetectionQuery.java new file mode 100644 index 000000000..8c9885756 --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/user/DetectionQuery.java @@ -0,0 +1,103 @@ +/* + * 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.api.user; + +import space.arim.libertybans.api.NetworkAddress; +import space.arim.libertybans.api.PunishmentType; +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +/** + * Query to detect alts + * + */ +public interface DetectionQuery { + + /** + * The uuid of the player whose alts to search for + * + * @return the uuid + */ + UUID uuid(); + + /** + * The network address of the player whose alts to search for + * + * @return the address + */ + NetworkAddress address(); + + /** + * The punishment types to scan alts for + * + * @return the punishment types + */ + Set punishmentTypes(); + + /** + * Builder for detection queries + * + */ + interface Builder { + + /** + * The punishment types to scan alts for.
+ *
+ * If enabled, the alt detection will attempt to look for punishment types applying to the specified + * alts on a best effort basis. + * + * @param types the punishment types to match alts with + * @return this builder + */ + default Builder punishmentTypes(PunishmentType...types) { + return punishmentTypes(Set.of(types)); + } + + /** + * The punishment types to scan alts for.
+ *
+ * If enabled, the alt detection will attempt to look for punishment types applying to the specified + * alts on a best effort basis. + * + * @param types the punishment types to match alts with + * @return this builder + */ + Builder punishmentTypes(Set types); + + /** + * Builds into a detection query + * + * @return the detection query + */ + DetectionQuery build(); + + } + + /** + * Performs the detection + * + * @return a future yielding the alt accounts + */ + CentralisedFuture> detect(); + +} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/user/KnownAccount.java b/bans-api/src/main/java/space/arim/libertybans/api/user/KnownAccount.java new file mode 100644 index 000000000..15f510a59 --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/user/KnownAccount.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.api.user; + +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +import java.time.Instant; + +/** + * A known login for a certain player + * + */ +public interface KnownAccount extends AccountBase { + + /** + * When the login took place + * + * @return the time the login was recorded + */ + Instant recorded(); + + /** + * Attempts to delete this known account + * + * @return a future yielding true if deleted, false if it does not exist + * (perhaps because it was deleted by another instance) + */ + CentralisedFuture deleteFromHistory(); + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/ApiBindModule.java b/bans-core/src/main/java/space/arim/libertybans/core/ApiBindModule.java index df71f6ab9..522df2991 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/ApiBindModule.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/ApiBindModule.java @@ -1,25 +1,28 @@ -/* - * 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; import jakarta.inject.Singleton; +import space.arim.libertybans.api.user.AccountSupervisor; +import space.arim.libertybans.core.alts.Supervisor; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import space.arim.libertybans.api.LibertyBans; @@ -79,4 +82,8 @@ public UserResolver userResolver(UUIDManager uuidManager) { return uuidManager; } + public AccountSupervisor accountSupervisor(Supervisor supervisor) { + return supervisor; + } + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/LibertyBansApi.java b/bans-core/src/main/java/space/arim/libertybans/core/LibertyBansApi.java index 60fbd5166..7fb554229 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/LibertyBansApi.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/LibertyBansApi.java @@ -1,30 +1,26 @@ -/* - * 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; import jakarta.inject.Inject; import jakarta.inject.Provider; -import jakarta.inject.Singleton; - -import space.arim.omnibus.Omnibus; -import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; - import space.arim.libertybans.api.LibertyBans; import space.arim.libertybans.api.database.PunishmentDatabase; import space.arim.libertybans.api.formatter.PunishmentFormatter; @@ -32,9 +28,11 @@ import space.arim.libertybans.api.punish.PunishmentRevoker; import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.api.select.PunishmentSelector; +import space.arim.libertybans.api.user.AccountSupervisor; import space.arim.libertybans.api.user.UserResolver; +import space.arim.omnibus.Omnibus; +import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; -@Singleton public class LibertyBansApi implements LibertyBans { private final Omnibus omnibus; @@ -42,24 +40,27 @@ public class LibertyBansApi implements LibertyBans { private final PunishmentDrafter drafter; private final PunishmentRevoker revoker; private final PunishmentSelector selector; - private final Provider databaseProvider; + private final Provider dbProvider; private final PunishmentFormatter formatter; private final ScopeManager scopeManager; private final UserResolver userResolver; + private final AccountSupervisor accountSupervisor; @Inject - public LibertyBansApi(Omnibus omnibus, FactoryOfTheFuture futuresFactory, PunishmentDrafter drafter, - PunishmentRevoker revoker, PunishmentSelector selector, Provider databaseProvider, - PunishmentFormatter formatter, ScopeManager scopeManager, UserResolver userResolver) { + public LibertyBansApi(Omnibus omnibus, FactoryOfTheFuture futuresFactory, Provider dbProvider, + PunishmentDrafter drafter, PunishmentRevoker revoker, PunishmentSelector selector, + PunishmentFormatter formatter, ScopeManager scopeManager, + UserResolver userResolver, AccountSupervisor accountSupervisor) { this.omnibus = omnibus; this.futuresFactory = futuresFactory; this.drafter = drafter; this.revoker = revoker; this.selector = selector; - this.databaseProvider = databaseProvider; + this.dbProvider = dbProvider; this.formatter = formatter; this.scopeManager = scopeManager; this.userResolver = userResolver; + this.accountSupervisor = accountSupervisor; } @Override @@ -89,7 +90,7 @@ public PunishmentSelector getSelector() { @Override public PunishmentDatabase getDatabase() { - return databaseProvider.get(); + return dbProvider.get(); } @Override @@ -107,4 +108,9 @@ public UserResolver getUserResolver() { return userResolver; } + @Override + public AccountSupervisor getAccountSupervisor() { + return accountSupervisor; + } + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/alts/AccountHistory.java b/bans-core/src/main/java/space/arim/libertybans/core/alts/AccountHistory.java index ebab4435e..cac26a1e5 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/alts/AccountHistory.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/alts/AccountHistory.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 @@ -24,8 +24,10 @@ import org.jooq.Condition; import space.arim.libertybans.api.AddressVictim; import space.arim.libertybans.api.CompositeVictim; +import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PlayerVictim; import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.user.KnownAccount; import space.arim.libertybans.core.database.execute.QueryExecutor; import space.arim.libertybans.core.database.execute.SQLFunction; import space.arim.libertybans.core.punish.MiscUtil; @@ -33,6 +35,7 @@ import java.time.Instant; import java.util.List; +import java.util.Optional; import java.util.UUID; import static space.arim.libertybans.core.schema.tables.Addresses.ADDRESSES; @@ -47,21 +50,41 @@ public AccountHistory(Provider queryExecutor) { this.queryExecutor = queryExecutor; } - private CentralisedFuture> knownAccountsWhere(Condition condition) { + public KnownAccount newAccount(UUID uuid, String username, NetworkAddress address, Instant recorded) { + return new KnownAccountImpl(uuid, username, address, recorded, this); + } + + record KnownAccountImpl(UUID uuid, String username, NetworkAddress address, Instant recorded, + AccountHistory accountHistory) implements KnownAccount { + + @Override + public Optional latestUsername() { + return Optional.ofNullable(username); + } + + @Override + public CentralisedFuture deleteFromHistory() { + return accountHistory.deleteAccount(uuid, recorded); + } + + } + + private CentralisedFuture> knownAccountsWhere(Condition condition) { return queryExecutor.get().query(SQLFunction.readOnly((context) -> { return context .select(ADDRESSES.UUID, ADDRESSES.ADDRESS, LATEST_NAMES.NAME, ADDRESSES.UPDATED) .from(ADDRESSES) - .innerJoin(LATEST_NAMES) + .leftJoin(LATEST_NAMES) .on(ADDRESSES.UUID.eq(LATEST_NAMES.UUID)) .where(condition) .orderBy(ADDRESSES.UPDATED.asc()) .fetch((record) -> { - return new KnownAccount( + return new KnownAccountImpl( record.get(ADDRESSES.UUID), record.get(LATEST_NAMES.NAME), record.get(ADDRESSES.ADDRESS), - record.get(ADDRESSES.UPDATED) + record.get(ADDRESSES.UPDATED), + this ); }); })); @@ -76,7 +99,7 @@ private CentralisedFuture> knownAccountsWhere(Condition condi * @param victim the uuid or IP address * @return the detected alts, sorted in order of oldest first */ - public CentralisedFuture> knownAccounts(Victim victim) { + public CentralisedFuture> knownAccounts(Victim victim) { if (victim instanceof PlayerVictim playerVictim) { return knownAccountsWhere(ADDRESSES.UUID.eq(playerVictim.getUUID())); @@ -103,4 +126,15 @@ public CentralisedFuture deleteAccount(UUID user, Instant recorded) { }); } + // Help out implementing equals and hashCode for KnownAccountImpl + @Override + public boolean equals(Object o) { + return o instanceof AccountHistory; + } + + @Override + public int hashCode() { + return AccountHistory.class.hashCode(); + } + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/alts/AccountHistoryFormatter.java b/bans-core/src/main/java/space/arim/libertybans/core/alts/AccountHistoryFormatter.java index 1143b5ded..6898dc2c0 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/alts/AccountHistoryFormatter.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/alts/AccountHistoryFormatter.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 @@ -23,6 +23,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentLike; import space.arim.api.jsonchat.adventure.util.ComponentText; +import space.arim.libertybans.api.user.KnownAccount; import space.arim.libertybans.core.config.Configs; import space.arim.libertybans.core.config.InternalFormatter; @@ -40,27 +41,22 @@ public AccountHistoryFormatter(Configs configs, InternalFormatter formatter) { listFormat = new ListFormat<>(formatter, new KnownAccountFormat(configs, formatter)); } - public Component formatMessage(String target, List knownAccounts) { + public Component formatMessage(String target, List knownAccounts) { ComponentText header = configs.getMessagesConfig().accountHistory().listing().header(); return listFormat.formatMessage(header, target, knownAccounts); } - private static final class KnownAccountFormat implements ListFormat.ElementFormat { - - private final Configs configs; - private final InternalFormatter formatter; - - private KnownAccountFormat(Configs configs, InternalFormatter formatter) { - this.configs = configs; - this.formatter = formatter; - } + private record KnownAccountFormat(Configs configs, + InternalFormatter formatter) implements ListFormat.ElementFormat { @Override public ComponentLike format(String target, KnownAccount knownAccount) { - Instant recorded = knownAccount.updated(); + Instant recorded = knownAccount.recorded(); return configs.getMessagesConfig().accountHistory().listing().layout() .replaceText("%TARGET%", target) - .replaceText("%USERNAME%", knownAccount.username()) + .replaceText("%USERNAME%", knownAccount.latestUsername().orElseGet( + () -> configs.getMessagesConfig().formatting().victimDisplay().playerNameUnknown() + )) .replaceText("%ADDRESS%", knownAccount.address().toString()) .replaceText("%DATE_RECORDED%", formatter.formatAbsoluteDate(recorded)) .replaceText("%DATE_RECORDED_RAW%", Long.toString(recorded.getEpochSecond())); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/alts/AltCheckFormatter.java b/bans-core/src/main/java/space/arim/libertybans/core/alts/AltCheckFormatter.java index 6c9224b47..25c9e0332 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/alts/AltCheckFormatter.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/alts/AltCheckFormatter.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 @@ -42,60 +42,51 @@ public Component formatMessage(ComponentText header, String target, List { - - private final Configs configs; - private final InternalFormatter formatter; - - private DetectedAltFormat(Configs configs, InternalFormatter formatter) { - this.configs = configs; - this.formatter = formatter; - } + private record DetectedAltFormat(Configs configs, InternalFormatter formatter) + implements ListFormat.ElementFormat { private AltsSection.Formatting formatting() { return configs.getMessagesConfig().alts().formatting(); } - private ComponentLike getKind(DetectionKind detectionKind) { - switch (detectionKind) { - case NORMAL: - return formatting().normal(); - case STRICT: - return formatting().strict(); - default: - throw new IllegalArgumentException("Unknown kind " + detectionKind); + private ComponentLike formatUsername(DetectedAlt detectedAlt) { + PunishmentType type; + if (detectedAlt.hasActivePunishment(PunishmentType.BAN)) { + type = PunishmentType.BAN; + } else if (detectedAlt.hasActivePunishment(PunishmentType.MUTE)) { + type = PunishmentType.MUTE; + } else { + type = null; } - } - - private ComponentText getUsernameFormat(PunishmentType type) { + ComponentText usernameFormat; var nameDisplay = formatting().nameDisplay(); if (type == null) { - return nameDisplay.notPunished(); - } - switch (type) { - case BAN: - return nameDisplay.banned(); - case MUTE: - return nameDisplay.muted(); - default: - throw new IllegalArgumentException("Punishment type " + type + " not found"); + usernameFormat = nameDisplay.notPunished(); + } else { + usernameFormat = switch (type) { + case BAN -> nameDisplay.banned(); + case MUTE -> nameDisplay.muted(); + default -> throw new IllegalArgumentException("Punishment type " + type + " not available"); + }; } - } - - private ComponentLike formatUsername(DetectedAlt detectedAlt) { - return getUsernameFormat(detectedAlt.punishmentType().orElse(null)) - .replaceText("%USERNAME%", detectedAlt.relevantUserName()); + return usernameFormat.replaceText("%USERNAME%", detectedAlt.latestUsername().orElseGet( + () -> configs.getMessagesConfig().formatting().victimDisplay().playerNameUnknown() + )); } @Override public ComponentLike format(String target, DetectedAlt detectedAlt) { return formatting().layout() - .replaceText("%ADDRESS%", detectedAlt.relevantAddress().toString()) - .replaceText("%RELEVANT_USERID%", detectedAlt.relevantUserId().toString()) - .replaceText("%DATE_RECORDED%", formatter.formatAbsoluteDate(detectedAlt.dateAccountRecorded())) + .replaceText("%ADDRESS%", detectedAlt.address().toString()) + .replaceText("%RELEVANT_USERID%", detectedAlt.uuid().toString()) + .replaceText("%DATE_RECORDED%", formatter.formatAbsoluteDate(detectedAlt.lastObserved())) .asComponent() .replaceText((config) -> { - config.matchLiteral("%DETECTION_KIND%").replacement(getKind(detectedAlt.detectionKind())); + Component detectionKind = switch (detectedAlt.detectionKind()) { + case NORMAL -> formatting().normal(); + case STRICT -> formatting().strict(); + }; + config.matchLiteral("%DETECTION_KIND%").replacement(detectionKind); }) .replaceText((config) -> { config.matchLiteral("%RELEVANT_USER%").replacement(formatUsername(detectedAlt)); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/alts/AltDetection.java b/bans-core/src/main/java/space/arim/libertybans/core/alts/AltDetection.java index 666db30c2..b24171649 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/alts/AltDetection.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/alts/AltDetection.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 @@ -23,28 +23,38 @@ import jakarta.inject.Provider; import org.jooq.DSLContext; import org.jooq.Field; -import org.jooq.impl.DSL; +import org.jooq.SelectField; +import org.jooq.Table; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.user.AltAccount; +import space.arim.libertybans.api.user.DetectionQuery; import space.arim.libertybans.core.config.Configs; import space.arim.libertybans.core.database.execute.QueryExecutor; import space.arim.libertybans.core.database.execute.SQLFunction; import space.arim.libertybans.core.database.sql.AccountExpirationCondition; import space.arim.libertybans.core.database.sql.EndTimeCondition; -import space.arim.libertybans.core.database.sql.SimpleViewFields; +import space.arim.libertybans.core.database.sql.TableForType; import space.arim.libertybans.core.database.sql.VictimCondition; import space.arim.libertybans.core.env.UUIDAndAddress; import space.arim.libertybans.core.service.Time; import space.arim.omnibus.util.concurrent.CentralisedFuture; import java.time.Instant; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.EnumSet; import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.UUID; +import java.util.function.Predicate; +import static org.jooq.impl.DSL.field; import static space.arim.libertybans.core.schema.tables.Addresses.ADDRESSES; import static space.arim.libertybans.core.schema.tables.LatestNames.LATEST_NAMES; -import static space.arim.libertybans.core.schema.tables.SimpleBans.SIMPLE_BANS; -import static space.arim.libertybans.core.schema.tables.SimpleMutes.SIMPLE_MUTES; public class AltDetection { @@ -70,43 +80,46 @@ public AltDetection(Configs configs, Provider queryExecutor, Time * the lack of pagination we want to show a short and linear progression from old to new. * * @param context the query source with which to contact the database - * @param uuid the user's uuid - * @param address the user's address - * @param whichAlts which alts to detect + * @param query the detection query + * @param whichAlts which alts to remove from the resulting list * @return the detected alts, sorted in order of oldest first */ - public List detectAlts(DSLContext context, UUID uuid, NetworkAddress address, - WhichAlts whichAlts) { + private List detectAlts(DSLContext context, DetectionQuery query, WhichAlts whichAlts) { // This implementation relies on strict detection including normal detection // The detection kind is inferred while processing the results final Instant currentTime = time.currentTimestamp(); var detectedAlt = ADDRESSES.as("detected_alt"); - Field hasBan = DSL.field(SIMPLE_BANS.VICTIM_TYPE.isNotNull()).as("has_ban"); - Field hasMute = DSL.field(SIMPLE_MUTES.VICTIM_TYPE.isNotNull()).as("has_mute"); - List detectedAlts = context - .select( - detectedAlt.ADDRESS, detectedAlt.UUID, - LATEST_NAMES.NAME, detectedAlt.UPDATED, - hasBan, hasMute - ) - .from(ADDRESSES) + + List> selectFields = new ArrayList<>(List.of( + detectedAlt.ADDRESS, detectedAlt.UUID, + LATEST_NAMES.NAME, detectedAlt.UPDATED + )); + Table joinedTables = ADDRESSES // Detect alts .innerJoin(detectedAlt) .on(ADDRESSES.ADDRESS.eq(detectedAlt.ADDRESS)) .and(ADDRESSES.UUID.notEqual(detectedAlt.UUID)) - // Map to names - .innerJoin(LATEST_NAMES) - .on(LATEST_NAMES.UUID.eq(detectedAlt.UUID)) - // Pair with bans - .leftJoin(SIMPLE_BANS) - .on(new VictimCondition(new SimpleViewFields(SIMPLE_BANS)).matchesUUID(detectedAlt.UUID)) - .and(new EndTimeCondition(new SimpleViewFields(SIMPLE_BANS)).isNotExpired(currentTime)) - // Pair with mutes - .leftJoin(SIMPLE_MUTES) - .on(new VictimCondition(new SimpleViewFields(SIMPLE_MUTES)).matchesUUID(detectedAlt.UUID)) - .and(new EndTimeCondition(new SimpleViewFields(SIMPLE_MUTES)).isNotExpired(currentTime)) + // Pair with latest names + .leftJoin(LATEST_NAMES) + .on(LATEST_NAMES.UUID.eq(detectedAlt.UUID)); + + Map> hasTypeFields = new EnumMap<>(PunishmentType.class); + for (PunishmentType type : query.punishmentTypes()) { + // Pair with that particular type + var simpleView = new TableForType(type).simpleView(); + joinedTables = joinedTables + .leftJoin(simpleView.table()) + .on(new VictimCondition(simpleView).matchesUUID(detectedAlt.UUID)) + .and(new EndTimeCondition(simpleView).isNotExpired(currentTime)); + Field hasTypeField = field(simpleView.victimType().isNotNull().as("has_" + type.toString().toLowerCase(Locale.ROOT))); + hasTypeFields.put(type, hasTypeField); + selectFields.add(hasTypeField); + } + List detectedAlts = context + .select(selectFields) + .from(joinedTables) // Select alts for the player in question - .where(ADDRESSES.UUID.eq(uuid)) + .where(ADDRESSES.UUID.eq(query.uuid())) // Filter non-expired alts .and(new AccountExpirationCondition(detectedAlt.UPDATED).isNotExpired(configs, currentTime)) // Order with oldest first @@ -114,40 +127,54 @@ public List detectAlts(DSLContext context, UUID uuid, NetworkAddres .fetch((record) -> { NetworkAddress detectedAddress = record.get(detectedAlt.ADDRESS); // If this alt can be detected 'normally', then the address will be the same - DetectionKind detectionKind = (address.equals(detectedAddress)) ? DetectionKind.NORMAL : DetectionKind.STRICT; - // Determine most significant punishment - PunishmentType punishmentType; - if (record.get(hasBan)) { - punishmentType = PunishmentType.BAN; - } else if (record.get(hasMute)) { - punishmentType = PunishmentType.MUTE; - } else { - punishmentType = null; + DetectionKind detectionKind = (query.address().equals(detectedAddress)) ? DetectionKind.NORMAL : DetectionKind.STRICT; + // Determine scanned scannedTypes + Set scannedTypes = EnumSet.noneOf(PunishmentType.class); + for (PunishmentType scanFor : query.punishmentTypes()) { + var hasType = hasTypeFields.get(scanFor).get(record); + Objects.requireNonNull(hasType); + if (hasType) { + scannedTypes.add(scanFor); + } } return new DetectedAlt( - detectionKind, - punishmentType, - detectedAddress, record.get(detectedAlt.UUID), record.get(LATEST_NAMES.NAME), - record.get(detectedAlt.UPDATED) + detectedAddress, + record.get(detectedAlt.UPDATED), + detectionKind, + scannedTypes ); }); - switch (whichAlts) { - case ALL_ALTS: - break; - case BANNED_OR_MUTED_ALTS: - detectedAlts.removeIf((alt) -> alt.punishmentType().isEmpty()); - break; - case BANNED_ALTS: - detectedAlts.removeIf((alt) -> alt.punishmentType().orElse(null) != PunishmentType.BAN); - break; - default: - throw new IllegalArgumentException("Unknown WhichAlts " + whichAlts); - } + Predicate removeIf = switch (whichAlts) { + case ALL_ALTS -> (alt) -> false; + case BANNED_OR_MUTED_ALTS -> (alt) -> alt.scannedTypes().isEmpty(); + case BANNED_ALTS -> (alt) -> !alt.scannedTypes().contains(PunishmentType.BAN); + }; + detectedAlts.removeIf(removeIf); return detectedAlts; } + record AltQuery(UUID uuid, NetworkAddress address, + Set punishmentTypes, AltDetection impl) implements DetectionQuery { + + @Override + public CentralisedFuture> detect() { + return impl.queryExecutor.get().query(SQLFunction.readOnly((context) -> { + return impl.detectAlts(context, this, WhichAlts.ALL_ALTS); + })); + } + + } + + public List detectAlts(DSLContext context, UUID uuid, NetworkAddress address, WhichAlts whichAlts) { + return detectAlts( + context, + new AltQuery(uuid, address, Set.of(PunishmentType.BAN, PunishmentType.MUTE), this), + whichAlts + ); + } + public CentralisedFuture> detectAlts(UUID uuid, NetworkAddress address, WhichAlts whichAlts) { return queryExecutor.get().query(SQLFunction.readOnly((context) -> { return detectAlts(context, uuid, address, whichAlts); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/alts/DetectedAlt.java b/bans-core/src/main/java/space/arim/libertybans/core/alts/DetectedAlt.java index c433d248c..680a1886c 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/alts/DetectedAlt.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/alts/DetectedAlt.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,86 +21,39 @@ import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.user.AltAccount; import java.time.Instant; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.UUID; -public final class DetectedAlt { +public record DetectedAlt(UUID uuid, String username, NetworkAddress address, Instant lastObserved, + DetectionKind detectionKind, Set scannedTypes) + implements AltAccount { - private final DetectionKind detectionKind; - private final PunishmentType punishmentType; - private final NetworkAddress relevantAddress; - private final UUID relevantUserId; - private final String relevantUserName; - private final Instant dateAccountRecorded; - - public DetectedAlt(DetectionKind detectionKind, PunishmentType punishmentType, - NetworkAddress relevantAddress, UUID relevantUserId, String relevantUserName, - Instant dateAccountRecorded) { - this.detectionKind = Objects.requireNonNull(detectionKind, "detectionKind"); - this.punishmentType = punishmentType; - this.relevantAddress = Objects.requireNonNull(relevantAddress, "relevantAddress"); - this.relevantUserId = Objects.requireNonNull(relevantUserId, "relevantUserId"); - this.relevantUserName = Objects.requireNonNull(relevantUserName, "relevantUserName"); - this.dateAccountRecorded = Objects.requireNonNull(dateAccountRecorded, "dateAccountRecorded"); - } - - public DetectionKind detectionKind() { - return detectionKind; - } - - public Optional punishmentType() { - return Optional.ofNullable(punishmentType); - } - - public NetworkAddress relevantAddress() { - return relevantAddress; + public DetectedAlt { + Objects.requireNonNull(uuid, "uuid"); + Objects.requireNonNull(address, "address"); + Objects.requireNonNull(lastObserved, "lastObserved"); + Objects.requireNonNull(detectionKind, "detectionKind"); + scannedTypes = Set.copyOf(scannedTypes); } - public UUID relevantUserId() { - return relevantUserId; - } - - public String relevantUserName() { - return relevantUserName; - } - - public Instant dateAccountRecorded() { - return dateAccountRecorded; + public DetectedAlt(UUID uuid, String username, NetworkAddress address, Instant lastObserved, + DetectionKind detectionKind, PunishmentType...scannedTypes) { + this(uuid, username, address, lastObserved, detectionKind, Set.of(scannedTypes)); } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - DetectedAlt that = (DetectedAlt) o; - return detectionKind == that.detectionKind && Objects.equals(punishmentType, that.punishmentType) - && relevantAddress.equals(that.relevantAddress) && relevantUserId.equals(that.relevantUserId) && relevantUserName.equals(that.relevantUserName) - && dateAccountRecorded.equals(that.dateAccountRecorded); + public Optional latestUsername() { + return Optional.ofNullable(username); } @Override - public int hashCode() { - int result = detectionKind.hashCode(); - result = 31 * result + Objects.hashCode(punishmentType); - result = 31 * result + relevantAddress.hashCode(); - result = 31 * result + relevantUserId.hashCode(); - result = 31 * result + relevantUserName.hashCode(); - result = 31 * result + dateAccountRecorded.hashCode(); - return result; + public boolean hasActivePunishment(PunishmentType type) { + return scannedTypes.contains(type); } - @Override - public String toString() { - return "DetectedAlt{" + - "detectionKind=" + detectionKind + - ", punishmentType=" + punishmentType + - ", relevantAddress=" + relevantAddress + - ", relevantUserId=" + relevantUserId + - ", relevantUserName='" + relevantUserName + '\'' + - ", dateAccountRecorded=" + dateAccountRecorded + - '}'; - } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/alts/KnownAccount.java b/bans-core/src/main/java/space/arim/libertybans/core/alts/KnownAccount.java deleted file mode 100644 index a3c0d1224..000000000 --- a/bans-core/src/main/java/space/arim/libertybans/core/alts/KnownAccount.java +++ /dev/null @@ -1,84 +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.core.alts; - -import space.arim.libertybans.api.NetworkAddress; - -import java.time.Instant; -import java.util.Objects; -import java.util.UUID; - -public final class KnownAccount { - - private final UUID uuid; - private final String username; - private final NetworkAddress address; - private final Instant updated; - - public KnownAccount(UUID uuid, String username, NetworkAddress address, Instant updated) { - this.uuid = Objects.requireNonNull(uuid, "uuid"); - this.username = Objects.requireNonNull(username, "username"); - this.address = Objects.requireNonNull(address, "address"); - this.updated = Objects.requireNonNull(updated, "updated"); - } - - public UUID uuid() { - return uuid; - } - - public String username() { - return username; - } - - public NetworkAddress address() { - return address; - } - - public Instant updated() { - return updated; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - KnownAccount that = (KnownAccount) o; - return uuid.equals(that.uuid) && username.equals(that.username) && address.equals(that.address) && updated.equals(that.updated); - } - - @Override - public int hashCode() { - int result = uuid.hashCode(); - result = 31 * result + username.hashCode(); - result = 31 * result + address.hashCode(); - result = 31 * result + updated.hashCode(); - return result; - } - - @Override - public String toString() { - return "KnownAccount{" + - "uuid=" + uuid + - ", username='" + username + '\'' + - ", address=" + address + - ", updated=" + updated + - '}'; - } -} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/alts/ListFormat.java b/bans-core/src/main/java/space/arim/libertybans/core/alts/ListFormat.java index 583a0321d..550f56e14 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/alts/ListFormat.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/alts/ListFormat.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 @@ -38,7 +38,7 @@ class ListFormat { this.elementFormat = elementFormat; } - Component formatMessage(ComponentText header, String target, List data) { + Component formatMessage(ComponentText header, String target, List data) { List messages = new ArrayList<>(data.size() + 1); messages.add(formatter.prefix(header.replaceText("%TARGET%", target))); for (T datum : data) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/alts/Supervisor.java b/bans-core/src/main/java/space/arim/libertybans/core/alts/Supervisor.java new file mode 100644 index 000000000..f841ab43b --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/alts/Supervisor.java @@ -0,0 +1,83 @@ +/* + * 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.alts; + +import jakarta.inject.Inject; +import space.arim.libertybans.api.AddressVictim; +import space.arim.libertybans.api.CompositeVictim; +import space.arim.libertybans.api.NetworkAddress; +import space.arim.libertybans.api.PlayerVictim; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.user.AccountSupervisor; +import space.arim.libertybans.api.user.DetectionQuery; +import space.arim.libertybans.api.user.KnownAccount; +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +public final class Supervisor implements AccountSupervisor { + + private final AltDetection altDetection; + private final AccountHistory accountHistory; + + @Inject + public Supervisor(AltDetection altDetection, AccountHistory accountHistory) { + this.altDetection = altDetection; + this.accountHistory = accountHistory; + } + + @Override + public DetectionQuery.Builder detectAlts(UUID uuid, NetworkAddress address) { + class Builder implements DetectionQuery.Builder { + + private Set types = Set.of(); + + @Override + public DetectionQuery.Builder punishmentTypes(Set types) { + this.types = Set.copyOf(types); + return this; + } + + @Override + public DetectionQuery build() { + return new AltDetection.AltQuery(uuid, address, types, altDetection); + } + } + return new Builder(); + } + + @Override + public CentralisedFuture> findAccountsMatching(UUID uuid) { + return accountHistory.knownAccounts(PlayerVictim.of(uuid)); + } + + @Override + public CentralisedFuture> findAccountsMatching(NetworkAddress address) { + return accountHistory.knownAccounts(AddressVictim.of(address)); + } + + @Override + public CentralisedFuture> findAccountsMatching(UUID uuid, NetworkAddress address) { + return accountHistory.knownAccounts(CompositeVictim.of(uuid, address)); + } + +} diff --git a/bans-core/src/test/java/space/arim/libertybans/core/alts/AccountHistoryFormatterTest.java b/bans-core/src/test/java/space/arim/libertybans/core/alts/AccountHistoryFormatterTest.java index ba40a933c..d75d25f0d 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/alts/AccountHistoryFormatterTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/alts/AccountHistoryFormatterTest.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,9 +28,11 @@ import org.mockito.junit.jupiter.MockitoExtension; import space.arim.api.jsonchat.adventure.util.ComponentText; import space.arim.libertybans.api.NetworkAddress; +import space.arim.libertybans.api.user.KnownAccount; import space.arim.libertybans.core.config.Configs; import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.config.MessagesConfig; +import space.arim.libertybans.core.database.execute.QueryExecutor; import java.net.InetAddress; import java.net.UnknownHostException; @@ -70,7 +72,9 @@ public void formatMessage() throws UnknownHostException { String username = "TargetUser"; String address = "207.144.101.102"; Instant date = Instant.parse("2021-07-23T02:15:23.000000Z"); - KnownAccount knownAccount = new KnownAccount( + + AccountHistory accountHistory = new AccountHistory(() -> mock(QueryExecutor.class)); + KnownAccount knownAccount = accountHistory.newAccount( userId, username, NetworkAddress.of(InetAddress.getByName(address)), date); { diff --git a/bans-core/src/test/java/space/arim/libertybans/core/alts/AltCheckFormatterTest.java b/bans-core/src/test/java/space/arim/libertybans/core/alts/AltCheckFormatterTest.java index e16131242..6543c9107 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/alts/AltCheckFormatterTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/alts/AltCheckFormatterTest.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 @@ -74,10 +74,9 @@ public void formatMessage() throws UnknownHostException { UUID userId = UUID.randomUUID(); String username = "AltUser"; Instant date = Instant.parse("2021-07-23T02:15:23.000000Z"); - DetectedAlt alt = new DetectedAlt(DetectionKind.NORMAL, - null, NetworkAddress.of(InetAddress.getByName(address)), - userId, username, date); - + DetectedAlt alt = new DetectedAlt( + null, username, NetworkAddress.of(InetAddress.getByName(address)), date, DetectionKind.NORMAL + ); { when(conf.layout()).thenReturn(ComponentText.create(Component.text( "detection_kind: %DETECTION_KIND%, address: %ADDRESS%, username: %RELEVANT_USER%, " + @@ -113,22 +112,26 @@ public void formatPunishedAltsWithDetectionKind() throws UnknownHostException { when(conf.nameDisplay()).thenReturn(nameDisplay); } List alts = List.of( - new DetectedAlt(DetectionKind.NORMAL, - PunishmentType.BAN, RandomUtil.randomAddress(), - UUID.randomUUID(), "BadUser", date), - new DetectedAlt(DetectionKind.STRICT, - PunishmentType.MUTE, RandomUtil.randomAddress(), - UUID.randomUUID(), "Misbehaver", date), - new DetectedAlt(DetectionKind.NORMAL, - null, RandomUtil.randomAddress(), - UUID.randomUUID(), "Saint", date) + new DetectedAlt( + UUID.randomUUID(), "BadUser", RandomUtil.randomAddress(), date, + DetectionKind.NORMAL, PunishmentType.BAN + ), + new DetectedAlt( + UUID.randomUUID(), "Misbehaver", RandomUtil.randomAddress(), date, + DetectionKind.STRICT, PunishmentType.MUTE + ), + new DetectedAlt( + UUID.randomUUID(), "Saint", RandomUtil.randomAddress(), date, + DetectionKind.NORMAL + ) ); Component formattedMessage = altCheckFormatter.formatMessage( ComponentText.create(Component.text("Header")), "MainUser", alts); - assertEquals("Header\n" + - "kind: NORMAL, username: Banned(BadUser)\n" + - "kind: STRICT, username: Muted(Misbehaver)\n" + - "kind: NORMAL, username: None(Saint)", + assertEquals(""" + Header + kind: NORMAL, username: Banned(BadUser) + kind: STRICT, username: Muted(Misbehaver) + kind: NORMAL, username: None(Saint)""", PlainComponentSerializer.plain().serialize(formattedMessage)); } } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/alts/AccountHistoryIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/alts/AccountHistoryIT.java index a6dc291a6..801586cb4 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/alts/AccountHistoryIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/alts/AccountHistoryIT.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 @@ -26,7 +26,6 @@ import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PlayerVictim; import space.arim.libertybans.core.alts.AccountHistory; -import space.arim.libertybans.core.alts.KnownAccount; import space.arim.libertybans.core.selector.Guardian; import space.arim.libertybans.core.service.SettableTime; import space.arim.libertybans.it.InjectionInvocationContextProvider; @@ -87,22 +86,22 @@ public void listAccountHistory(Guardian guardian, SettableTime time) { guardian.executeAndCheckConnection(playerTwo, playerTwoName, sharedAddress).join(); assertEquals( - List.of(new KnownAccount(playerOne, playerOneName, playerOneAddress, startTime), - new KnownAccount(playerOne, playerOneName, sharedAddress, twoDaysLater)), + List.of(accountHistory.newAccount(playerOne, playerOneName, playerOneAddress, startTime), + accountHistory.newAccount(playerOne, playerOneName, sharedAddress, twoDaysLater)), accountHistory.knownAccounts(PlayerVictim.of(playerOne)).join()); assertEquals( - List.of(new KnownAccount(playerTwo, playerTwoName, playerTwoAddress, oneDayLater), - new KnownAccount(playerTwo, playerTwoName, sharedAddress, threeDaysLater)), + List.of(accountHistory.newAccount(playerTwo, playerTwoName, playerTwoAddress, oneDayLater), + accountHistory.newAccount(playerTwo, playerTwoName, sharedAddress, threeDaysLater)), accountHistory.knownAccounts(PlayerVictim.of(playerTwo)).join()); assertEquals( - List.of(new KnownAccount(playerOne, playerOneName, playerOneAddress, startTime)), + List.of(accountHistory.newAccount(playerOne, playerOneName, playerOneAddress, startTime)), accountHistory.knownAccounts(AddressVictim.of(playerOneAddress)).join()); assertEquals( - List.of(new KnownAccount(playerTwo, playerTwoName, playerTwoAddress, oneDayLater)), + List.of(accountHistory.newAccount(playerTwo, playerTwoName, playerTwoAddress, oneDayLater)), accountHistory.knownAccounts(AddressVictim.of(playerTwoAddress)).join()); assertEquals( - List.of(new KnownAccount(playerOne, playerOneName, sharedAddress, twoDaysLater), - new KnownAccount(playerTwo, playerTwoName, sharedAddress, threeDaysLater)), + List.of(accountHistory.newAccount(playerOne, playerOneName, sharedAddress, twoDaysLater), + accountHistory.newAccount(playerTwo, playerTwoName, sharedAddress, threeDaysLater)), accountHistory.knownAccounts(AddressVictim.of(sharedAddress)).join()); } @@ -125,8 +124,8 @@ public void deleteAccount(Guardian guardian, SettableTime time) { "No account exists before startTime"); assertEquals( - List.of(new KnownAccount(player, username, firstAddress, startTime), - new KnownAccount(player, username, secondAddress, startTime.plus(ONE_DAY))), + List.of(accountHistory.newAccount(player, username, firstAddress, startTime), + accountHistory.newAccount(player, username, secondAddress, startTime.plus(ONE_DAY))), accountHistory.knownAccounts(PlayerVictim.of(player)).join()); assertTrue( @@ -134,7 +133,7 @@ public void deleteAccount(Guardian guardian, SettableTime time) { "Delete account at startTime"); assertEquals( - List.of(new KnownAccount(player, username, secondAddress, startTime.plus(ONE_DAY))), + List.of(accountHistory.newAccount(player, username, secondAddress, startTime.plus(ONE_DAY))), accountHistory.knownAccounts(PlayerVictim.of(player)).join()); } } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/alts/AltDetectionIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/alts/AltDetectionIT.java index 582b0087d..988152495 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/alts/AltDetectionIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/alts/AltDetectionIT.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 @@ -22,12 +22,14 @@ import jakarta.inject.Inject; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; import space.arim.libertybans.api.ConsoleOperator; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PlayerVictim; 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.user.DetectionQuery; import space.arim.libertybans.core.alts.AltDetection; import space.arim.libertybans.core.alts.DetectedAlt; import space.arim.libertybans.core.alts.DetectionKind; @@ -35,6 +37,7 @@ import space.arim.libertybans.core.punish.EnforcementOpts; import space.arim.libertybans.core.selector.Guardian; import space.arim.libertybans.core.service.SettableTime; +import space.arim.libertybans.it.DontInject; import space.arim.libertybans.it.InjectionInvocationContextProvider; import space.arim.libertybans.it.SetTime; @@ -61,7 +64,8 @@ public class AltDetectionIT { private final PunishmentDrafter drafter; @Inject - public AltDetectionIT(AltDetection altDetection, Guardian guardian, PunishmentDrafter drafter) { + public AltDetectionIT(AltDetection altDetection, Guardian guardian, PunishmentDrafter drafter, + @Mock @DontInject DetectionQuery query) { this.altDetection = altDetection; this.guardian = guardian; this.drafter = drafter; @@ -87,7 +91,7 @@ public void noAlts() { private static final Instant DATE_NOW = Instant.ofEpochSecond(TIME_NOW); private void testNormalAlt(WhichAlts whichAltsForFirstAltCheck, - PunishmentType expectedPunishmentTypeForFirstAltCheck, + PunishmentType expectedPunishmentForFirstCheck, Consumer operationOnAltBeforeAltCheck) { NetworkAddress commonAddress = randomAddress(); UUID uuid = UUID.randomUUID(); @@ -100,14 +104,18 @@ private void testNormalAlt(WhichAlts whichAltsForFirstAltCheck, operationOnAltBeforeAltCheck.accept(uuidTwo); - assertEquals(List.of(new DetectedAlt( - DetectionKind.NORMAL, expectedPunishmentTypeForFirstAltCheck, commonAddress, - uuidTwo, nameTwo, DATE_NOW) - ), altDetection.detectAlts(uuid, commonAddress, whichAltsForFirstAltCheck).join()); - assertEquals(List.of(new DetectedAlt( - DetectionKind.NORMAL, null, commonAddress, - uuid, name, DATE_NOW) - ), altDetection.detectAlts(uuidTwo, commonAddress, ALL_ALTS).join()); + assertEquals( + List.of(new DetectedAlt( + uuidTwo, nameTwo, commonAddress, DATE_NOW, DetectionKind.NORMAL, expectedPunishmentForFirstCheck + )), + altDetection.detectAlts(uuid, commonAddress, whichAltsForFirstAltCheck).join() + ); + assertEquals( + List.of(new DetectedAlt( + uuid, name, commonAddress, DATE_NOW, DetectionKind.NORMAL + )), + altDetection.detectAlts(uuidTwo, commonAddress, ALL_ALTS).join() + ); } @TestTemplate @@ -180,13 +188,17 @@ public void strictAlt(SettableTime time) { assumeTrue(null == guardian.executeAndCheckConnection(uuid, name, newAddress).join()); assumeTrue(null == guardian.executeAndCheckConnection(uuidTwo, nameTwo, newAddressTwo).join()); - assertEquals(List.of(new DetectedAlt( - DetectionKind.STRICT, null, commonPastAddress, - uuidTwo, nameTwo, DATE_NOW) - ), altDetection.detectAlts(uuid, newAddress, ALL_ALTS).join()); - assertEquals(List.of(new DetectedAlt( - DetectionKind.STRICT, null, commonPastAddress, - uuid, name, DATE_NOW) - ), altDetection.detectAlts(uuidTwo, newAddressTwo, ALL_ALTS).join()); + assertEquals( + List.of(new DetectedAlt( + uuidTwo, nameTwo, commonPastAddress, DATE_NOW, DetectionKind.STRICT + )), + altDetection.detectAlts(uuid, newAddress, ALL_ALTS).join() + ); + assertEquals( + List.of(new DetectedAlt( + uuid, name, commonPastAddress, DATE_NOW, DetectionKind.STRICT + )), + altDetection.detectAlts(uuidTwo, newAddressTwo, ALL_ALTS).join() + ); } }