From fdb51c4d012ecd03d0a32261094a0b0c89a6381f Mon Sep 17 00:00:00 2001 From: HypherionMC Date: Fri, 20 Oct 2023 20:44:21 +0200 Subject: [PATCH] [FEAT] New Access control system to replace whitelisting and account linking --- build.gradle | 5 +- gradle.properties | 3 +- .../core/accounts/MinecraftAccount.java | 184 +++++++++++++++++- .../sdlink/core/config/SDLinkConfig.java | 4 +- .../core/config/impl/AccessControl.java | 47 +++++ .../sdlink/core/database/SDLinkAccount.java | 27 +++ .../sdlink/core/discord/BotController.java | 5 +- .../core/discord/commands/CommandManager.java | 7 +- .../slash/general/PlayerListSlashCommand.java | 4 +- .../verification/StaffUnverifyCommand.java | 58 ++++++ .../StaffVerifyAccountCommand.java | 48 +++++ .../UnverifyAccountSlashCommand.java | 43 ++++ .../verification/VerifyAccountCommand.java | 55 ++++++ .../verification/ViewVerifiedAccounts.java | 74 +++++++ .../discord/events/DiscordEventHandler.java | 31 ++- .../discord/hooks/MinecraftCommandHook.java | 13 +- .../sdlink/core/managers/DatabaseManager.java | 11 +- .../sdlink/core/managers/RoleManager.java | 26 ++- .../discord/DiscordMessageBuilder.java | 6 +- .../services/helpers/IMinecraftHelper.java | 4 +- .../sdlink/core/util/SDLinkUtils.java | 9 + 21 files changed, 645 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/hypherionmc/sdlink/core/config/impl/AccessControl.java create mode 100644 src/main/java/com/hypherionmc/sdlink/core/database/SDLinkAccount.java create mode 100644 src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/StaffUnverifyCommand.java create mode 100644 src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/StaffVerifyAccountCommand.java create mode 100644 src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/UnverifyAccountSlashCommand.java create mode 100644 src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/VerifyAccountCommand.java create mode 100644 src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/ViewVerifiedAccounts.java create mode 100644 src/main/java/com/hypherionmc/sdlink/core/util/SDLinkUtils.java diff --git a/build.gradle b/build.gradle index de35aaf..9802ca3 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,9 @@ dependencies { implementation("com.google.code.gson:gson:${gson}") implementation("com.google.guava:guava:31.1-jre") implementation 'com.mojang:authlib:4.0.43' + + compileOnly "org.projectlombok:lombok:${lombok}" + annotationProcessor "org.projectlombok:lombok:${lombok}" } shadowJar { @@ -114,7 +117,7 @@ shadowJar { relocate 'org.reflections', shade_group + 'org.reflections' } exclude 'META-INF/**' - setArchiveClassifier('') + setArchiveClassifier('verification') } publishing { diff --git a/gradle.properties b/gradle.properties index f47e19d..3380661 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,4 +19,5 @@ log4j=2.17.2 commons=3.12.0 commonsio=2.11.0 gson=2.10.1 -craterlib=1.0.0 \ No newline at end of file +craterlib=1.0.0 +lombok=1.18.30 \ No newline at end of file diff --git a/src/main/java/com/hypherionmc/sdlink/core/accounts/MinecraftAccount.java b/src/main/java/com/hypherionmc/sdlink/core/accounts/MinecraftAccount.java index f45e343..e0ac9ce 100644 --- a/src/main/java/com/hypherionmc/sdlink/core/accounts/MinecraftAccount.java +++ b/src/main/java/com/hypherionmc/sdlink/core/accounts/MinecraftAccount.java @@ -4,8 +4,20 @@ */ package com.hypherionmc.sdlink.core.accounts; +import com.hypherionmc.sdlink.core.config.SDLinkConfig; +import com.hypherionmc.sdlink.core.database.SDLinkAccount; +import com.hypherionmc.sdlink.core.discord.BotController; +import com.hypherionmc.sdlink.core.managers.RoleManager; +import com.hypherionmc.sdlink.core.messaging.Result; +import com.hypherionmc.sdlink.core.util.SDLinkUtils; import com.mojang.authlib.GameProfile; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.UserSnowflake; import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; @@ -18,6 +30,9 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase; /** * @author HypherionSA @@ -85,7 +100,174 @@ public class MinecraftAccount { return standard(profile.getName()); } - // TODO Verification + public static SDLinkAccount getStoredFromUUID(String uuid) { + sdlinkDatabase.reloadCollection("verifiedaccounts"); + return sdlinkDatabase.findById(uuid, SDLinkAccount.class); + } + + public boolean isAccountVerified() { + SDLinkAccount account = getStoredAccount(); + + if (account == null) + return false; + + return !SDLinkUtils.isNullOrEmpty(account.getDiscordID()); + } + + public SDLinkAccount getStoredAccount() { + sdlinkDatabase.reloadCollection("verifiedaccounts"); + SDLinkAccount account = sdlinkDatabase.findById(this.uuid.toString(), SDLinkAccount.class); + + return account == null ? newDBEntry() : account; + } + + @NotNull + public SDLinkAccount newDBEntry() { + SDLinkAccount account = new SDLinkAccount(); + account.setUsername(this.username); + account.setUuid(this.uuid.toString()); + account.setDiscordID(null); + account.setVerifyCode(null); + account.setOffline(this.isOffline); + + sdlinkDatabase.upsert(account); + sdlinkDatabase.reloadCollection("verifiedaccounts"); + + return account; + } + + @NotNull + public String getDiscordName() { + SDLinkAccount account = getStoredAccount(); + if (account == null || SDLinkUtils.isNullOrEmpty(account.getDiscordID())) + return "Unlinked"; + + User discordUser = BotController.INSTANCE.getJDA().getUserById(account.getDiscordID()); + return discordUser == null ? "Unlinked" : discordUser.getEffectiveName(); + } + + @Nullable + public User getDiscordUser() { + SDLinkAccount storedAccount = getStoredAccount(); + if (storedAccount == null || SDLinkUtils.isNullOrEmpty(storedAccount.getDiscordID())) + return null; + + return BotController.INSTANCE.getJDA().getUserById(storedAccount.getDiscordID()); + } + + public Result verifyAccount(Member member, Guild guild) { + SDLinkAccount account = getStoredAccount(); + + if (account == null) + return Result.error("We couldn't find your Minecraft account. Please ask the staff for assistance"); + + account.setDiscordID(member.getId()); + account.setVerifyCode(null); + + try { + sdlinkDatabase.upsert(account); + sdlinkDatabase.reloadCollection("verifiedaccounts"); + } catch (Exception e) { + if (SDLinkConfig.INSTANCE.generalConfig.debugging) { + e.printStackTrace(); + } + } + + if (RoleManager.getVerifiedRole() != null) { + try { + guild.addRoleToMember(UserSnowflake.fromId(member.getId()), RoleManager.getVerifiedRole()).queue(); + } catch (Exception e) { + if (SDLinkConfig.INSTANCE.generalConfig.debugging) { + e.printStackTrace(); + } + } + } + + return Result.success("Your account has been verified"); + } + + public Result unverifyAccount(Member member, Guild guild) { + SDLinkAccount account = getStoredAccount(); + + if (account == null) + return Result.error("We couldn't find your Minecraft account. Please ask the staff for assistance"); + + account.setDiscordID(null); + account.setVerifyCode(null); + + try { + sdlinkDatabase.upsert(account); + sdlinkDatabase.reloadCollection("verifiedaccounts"); + } catch (Exception e) { + if (SDLinkConfig.INSTANCE.generalConfig.debugging) { + e.printStackTrace(); + } + } + + if (RoleManager.getVerifiedRole() != null) { + try { + guild.removeRoleFromMember(UserSnowflake.fromId(member.getId()), RoleManager.getVerifiedRole()).queue(); + } catch (Exception e) { + if (SDLinkConfig.INSTANCE.generalConfig.debugging) { + e.printStackTrace(); + } + } + } + + return Result.success("Your account has been un-verified"); + } + + public Result checkAccessControl() { + if (!SDLinkConfig.INSTANCE.accessControl.enabled) { + return Result.success("pass"); + } + + SDLinkAccount account = getStoredAccount(); + if (account == null) + return Result.error("notFound"); + + if (SDLinkUtils.isNullOrEmpty(account.getDiscordID())) + return Result.error("notVerified"); + + if (SDLinkConfig.INSTANCE.accessControl.requireDiscordMembership) { + Guild guild = BotController.INSTANCE.getJDA().getGuilds().get(0); + + if (guild == null) + return Result.error("noGuildFound"); + + Member member = guild.getMemberById(account.getDiscordID()); + + if (member == null) + return Result.error("memberNotFound"); + } + + if (!SDLinkConfig.INSTANCE.accessControl.requiredRoles.isEmpty() && !RoleManager.getVerificationRoles().isEmpty()) { + AtomicBoolean anyFound = new AtomicBoolean(false); + + Guild guild = BotController.INSTANCE.getJDA().getGuilds().get(0); + + if (guild == null) + return Result.error("noGuildFound"); + + Member member = guild.getMemberById(account.getDiscordID()); + if (member != null) { + member.getRoles().forEach(r -> { + if (RoleManager.getVerificationRoles().stream().anyMatch(role -> role.getIdLong() == r.getIdLong())) { + if (!anyFound.get()) { + anyFound.set(true); + } + } + }); + + if (!anyFound.get()) + return Result.error("rolesNotFound"); + } else { + return Result.error("memberNotFound"); + } + } + + return Result.success("pass"); + } public String getUsername() { return username; diff --git a/src/main/java/com/hypherionmc/sdlink/core/config/SDLinkConfig.java b/src/main/java/com/hypherionmc/sdlink/core/config/SDLinkConfig.java index 3721fd2..b7aa03c 100644 --- a/src/main/java/com/hypherionmc/sdlink/core/config/SDLinkConfig.java +++ b/src/main/java/com/hypherionmc/sdlink/core/config/SDLinkConfig.java @@ -50,7 +50,9 @@ public class SDLinkConfig extends ModuleConfig { @SpecComment("Change in which channel messages appear") public MessageChannelConfig messageDestinations = new MessageChannelConfig(); - // TODO Verification Config + @Path("accessControl") + @SpecComment("Manage access to your server, similar to whitelisting") + public AccessControl accessControl = new AccessControl(); @Path("minecraftCommands") @SpecComment("Execute Minecraft commands in Discord") diff --git a/src/main/java/com/hypherionmc/sdlink/core/config/impl/AccessControl.java b/src/main/java/com/hypherionmc/sdlink/core/config/impl/AccessControl.java new file mode 100644 index 0000000..624c5f0 --- /dev/null +++ b/src/main/java/com/hypherionmc/sdlink/core/config/impl/AccessControl.java @@ -0,0 +1,47 @@ +package com.hypherionmc.sdlink.core.config.impl; + +import me.hypherionmc.moonconfig.core.conversion.Path; +import me.hypherionmc.moonconfig.core.conversion.SpecComment; + +import java.util.ArrayList; +import java.util.List; + +public class AccessControl { + + @Path("enabled") + @SpecComment("Enable Access Control") + public boolean enabled = false; + + @Path("requireDiscordMembership") + @SpecComment("Does the player need to be a member of your discord to join") + public boolean requireDiscordMembership = false; + + @Path("requiredRoles") + @SpecComment("Optional: The player requires any of these roles to be able to join your server") + public List requiredRoles = new ArrayList<>(); + + @Path("verifiedRole") + @SpecComment("Optional: Role name or ID to assign to verified player accounts") + public String verifiedRole = ""; + + @Path("verificationMessages") + @SpecComment("Configure messages shown to players when they don't meet verification requirements") + public AccessMessages verificationMessages = new AccessMessages(); + + public static class AccessMessages { + + @Path("accountVerification") + @SpecComment("The message shown to players that are not verified") + public String accountVerify = "This server requires account verification. Your verification code is: {code}. Please visit our discord server for instructions on how to verify your account."; + + @Path("nonMember") + @SpecComment("Message to show to players that are not a member of your discord") + public String nonMember = "Sorry, you need to be a member of our discord server to join this server"; + + @Path("requireRoles") + @SpecComment("Message to show when player doesn't have one of the required roles. Use {roles} to display the names of configured roles") + public String requireRoles = "Sorry, but you require any of the following roles: {roles}"; + + } + +} diff --git a/src/main/java/com/hypherionmc/sdlink/core/database/SDLinkAccount.java b/src/main/java/com/hypherionmc/sdlink/core/database/SDLinkAccount.java new file mode 100644 index 0000000..21bd096 --- /dev/null +++ b/src/main/java/com/hypherionmc/sdlink/core/database/SDLinkAccount.java @@ -0,0 +1,27 @@ +package com.hypherionmc.sdlink.core.database; + +import io.jsondb.annotation.Document; +import io.jsondb.annotation.Id; +import lombok.Getter; +import lombok.Setter; + +@Document(collection = "verifiedaccounts", schemaVersion = "1.0") +public class SDLinkAccount { + + @Id + @Getter @Setter + private String uuid; + + @Getter @Setter + private String username; + + @Getter @Setter + private String discordID; + + @Getter @Setter + private String verifyCode; + + @Getter @Setter + private boolean isOffline; + +} diff --git a/src/main/java/com/hypherionmc/sdlink/core/discord/BotController.java b/src/main/java/com/hypherionmc/sdlink/core/discord/BotController.java index 8ffa4af..a503cf5 100644 --- a/src/main/java/com/hypherionmc/sdlink/core/discord/BotController.java +++ b/src/main/java/com/hypherionmc/sdlink/core/discord/BotController.java @@ -10,6 +10,7 @@ import com.hypherionmc.sdlink.core.discord.events.DiscordEventHandler; import com.hypherionmc.sdlink.core.managers.DatabaseManager; import com.hypherionmc.sdlink.core.managers.EmbedManager; import com.hypherionmc.sdlink.core.managers.WebhookManager; +import com.hypherionmc.sdlink.core.services.SDLinkPlatform; import com.hypherionmc.sdlink.core.util.EncryptionUtil; import com.hypherionmc.sdlink.core.util.ThreadedEventManager; import com.jagrosh.jdautilities.command.CommandClient; @@ -192,7 +193,9 @@ public class BotController { * Ensure that whitelisting is set up properly, so the bot can use the feature */ public void checkWhiteListing() { - // TODO Verification + if (SDLinkConfig.INSTANCE.accessControl.enabled && !SDLinkPlatform.minecraftHelper.checkWhitelisting().isError()) { + getLogger().error("SDLink Access Control is enabled, but so is whitelisting on your server. You need to disable whitelisting to use the AccessControl feature"); + } } public Logger getLogger() { diff --git a/src/main/java/com/hypherionmc/sdlink/core/discord/commands/CommandManager.java b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/CommandManager.java index 55b0ac1..35d6231 100644 --- a/src/main/java/com/hypherionmc/sdlink/core/discord/commands/CommandManager.java +++ b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/CommandManager.java @@ -7,6 +7,7 @@ package com.hypherionmc.sdlink.core.discord.commands; import com.hypherionmc.sdlink.core.discord.commands.slash.general.HelpSlashCommand; import com.hypherionmc.sdlink.core.discord.commands.slash.general.PlayerListSlashCommand; import com.hypherionmc.sdlink.core.discord.commands.slash.general.ServerStatusSlashCommand; +import com.hypherionmc.sdlink.core.discord.commands.slash.verification.*; import com.jagrosh.jdautilities.command.CommandClient; import com.jagrosh.jdautilities.command.SlashCommand; @@ -28,7 +29,11 @@ public class CommandManager { } private void addCommands() { - // TODO Verification + commands.add(new VerifyAccountCommand()); + commands.add(new UnverifyAccountSlashCommand()); + commands.add(new StaffUnverifyCommand()); + commands.add(new StaffVerifyAccountCommand()); + commands.add(new ViewVerifiedAccounts()); // Enable the Server Status command commands.add(new ServerStatusSlashCommand()); diff --git a/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/general/PlayerListSlashCommand.java b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/general/PlayerListSlashCommand.java index 04657c8..d0ff652 100644 --- a/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/general/PlayerListSlashCommand.java +++ b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/general/PlayerListSlashCommand.java @@ -63,7 +63,9 @@ public class PlayerListSlashCommand extends SDLinkSlashCommand { p.forEach(account -> { sb.append("`").append(account.getUsername()).append("`"); - // TODO Verification + if (account.getDiscordUser() != null) { + sb.append(" - ").append(account.getDiscordUser().getAsMention()); + } sb.append("\r\n"); }); diff --git a/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/StaffUnverifyCommand.java b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/StaffUnverifyCommand.java new file mode 100644 index 0000000..16e7655 --- /dev/null +++ b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/StaffUnverifyCommand.java @@ -0,0 +1,58 @@ +package com.hypherionmc.sdlink.core.discord.commands.slash.verification; + +import com.hypherionmc.sdlink.core.accounts.MinecraftAccount; +import com.hypherionmc.sdlink.core.database.SDLinkAccount; +import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand; +import com.hypherionmc.sdlink.core.messaging.Result; +import com.jagrosh.jdautilities.command.SlashCommandEvent; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; + +import java.util.ArrayList; +import java.util.List; + +import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase; + +public class StaffUnverifyCommand extends SDLinkSlashCommand { + + public StaffUnverifyCommand() { + super(false); + this.name = "staffunverify"; + this.help = "Unverify another player's Minecraft account"; + + List options = new ArrayList<>() {{ + add(new OptionData(OptionType.USER, "discorduser", "The discord user the minecraft account belongs to").setRequired(true)); + add(new OptionData(OptionType.STRING, "mcname", "The minecraft account of the linked user").setRequired(true)); + }}; + + this.options = options; + } + + @Override + protected void execute(SlashCommandEvent event) { + sdlinkDatabase.reloadCollection("verifiedaccounts"); + List accounts = sdlinkDatabase.findAll(SDLinkAccount.class); + + if (accounts.isEmpty()) { + event.reply("Sorry, but this server does not contain any stored players in its database").setEphemeral(true).queue(); + return; + } + + String mcname = event.getOption("mcname").getAsString(); + User user = event.getOption("discorduser").getAsUser(); + + Member member = event.getGuild().getMemberById(user.getId()); + + if (member == null) { + event.reply(user.getEffectiveName() + " is not a member of this discord server").setEphemeral(true).queue(); + return; + } + + MinecraftAccount minecraftAccount = MinecraftAccount.standard(mcname); + Result result = minecraftAccount.unverifyAccount(member, event.getGuild()); + event.reply(result.getMessage()).setEphemeral(true).queue(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/StaffVerifyAccountCommand.java b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/StaffVerifyAccountCommand.java new file mode 100644 index 0000000..091efe7 --- /dev/null +++ b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/StaffVerifyAccountCommand.java @@ -0,0 +1,48 @@ +package com.hypherionmc.sdlink.core.discord.commands.slash.verification; + +import com.hypherionmc.sdlink.core.accounts.MinecraftAccount; +import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand; +import com.hypherionmc.sdlink.core.messaging.Result; +import com.jagrosh.jdautilities.command.SlashCommandEvent; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; + +import java.util.ArrayList; +import java.util.List; + +public class StaffVerifyAccountCommand extends SDLinkSlashCommand { + + public StaffVerifyAccountCommand() { + super(true); + this.name = "staffverify"; + this.help = "Allow staff members verify minecraft players, without verification"; + + List options = new ArrayList<>() {{ + add(new OptionData(OptionType.USER, "discorduser", "The discord user the minecraft account belongs to").setRequired(true)); + add(new OptionData(OptionType.STRING, "mcname", "The minecraft account to link to the user").setRequired(true)); + }}; + + this.options = options; + } + + @Override + protected void execute(SlashCommandEvent event) { + String mcname = event.getOption("mcname").getAsString(); + User user = event.getOption("discorduser").getAsUser(); + + Member member = event.getGuild().getMemberById(user.getId()); + + if (member == null) { + event.reply(user.getEffectiveName() + " is not a member of this discord server").setEphemeral(true).queue(); + return; + } + + MinecraftAccount minecraftAccount = MinecraftAccount.standard(mcname); + + Result result = minecraftAccount.verifyAccount(member, event.getGuild()); + event.reply(result.getMessage()).setEphemeral(true).queue(); + } + +} diff --git a/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/UnverifyAccountSlashCommand.java b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/UnverifyAccountSlashCommand.java new file mode 100644 index 0000000..abe7c30 --- /dev/null +++ b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/UnverifyAccountSlashCommand.java @@ -0,0 +1,43 @@ +package com.hypherionmc.sdlink.core.discord.commands.slash.verification; + +import com.hypherionmc.sdlink.core.accounts.MinecraftAccount; +import com.hypherionmc.sdlink.core.database.SDLinkAccount; +import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand; +import com.hypherionmc.sdlink.core.messaging.Result; +import com.jagrosh.jdautilities.command.SlashCommandEvent; + +import java.util.List; + +import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase; + +public class UnverifyAccountSlashCommand extends SDLinkSlashCommand { + + public UnverifyAccountSlashCommand() { + super(false); + this.name = "unverify"; + this.help = "Unverify your previously verified Minecraft account"; + } + + @Override + protected void execute(SlashCommandEvent event) { + sdlinkDatabase.reloadCollection("verifiedaccounts"); + List accounts = sdlinkDatabase.findAll(SDLinkAccount.class); + + if (accounts.isEmpty()) { + event.reply("Sorry, but this server does not contain any stored players in its database").setEphemeral(true).queue(); + return; + } + + for (SDLinkAccount account : accounts) { + if (account.getDiscordID() != null && account.getDiscordID().equalsIgnoreCase(event.getMember().getId())) { + MinecraftAccount minecraftAccount = MinecraftAccount.standard(account.getUsername()); + Result result = minecraftAccount.unverifyAccount(event.getMember(), event.getGuild()); + event.reply(result.getMessage()).setEphemeral(true).queue(); + break; + } + } + + event.reply("Sorry, we could not un-verify your Minecraft account. Please try again").setEphemeral(true).queue(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/VerifyAccountCommand.java b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/VerifyAccountCommand.java new file mode 100644 index 0000000..cffe596 --- /dev/null +++ b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/VerifyAccountCommand.java @@ -0,0 +1,55 @@ +package com.hypherionmc.sdlink.core.discord.commands.slash.verification; + +import com.hypherionmc.sdlink.core.accounts.MinecraftAccount; +import com.hypherionmc.sdlink.core.database.SDLinkAccount; +import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand; +import com.hypherionmc.sdlink.core.messaging.Result; +import com.jagrosh.jdautilities.command.SlashCommandEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; + +import java.util.Collections; +import java.util.List; + +import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase; + +public class VerifyAccountCommand extends SDLinkSlashCommand { + + public VerifyAccountCommand() { + super(false); + this.name = "verify"; + this.help = "Verify your Minecraft account to access the server"; + + this.options = Collections.singletonList(new OptionData(OptionType.INTEGER, "code", "The verification code from the Minecraft Kick Message").setRequired(true)); + } + + @Override + protected void execute(SlashCommandEvent event) { + int mcCode = event.getOption("code") != null ? event.getOption("code").getAsInt() : 0; + + if (mcCode == 0) { + event.reply("You need to provide a verification code").setEphemeral(true).queue(); + return; + } + + sdlinkDatabase.reloadCollection("verifiedaccounts"); + List accounts = sdlinkDatabase.findAll(SDLinkAccount.class); + + if (accounts.isEmpty()) { + event.reply("Sorry, but this server does not contain any stored players in its database").setEphemeral(true).queue(); + return; + } + + for (SDLinkAccount account : accounts) { + if (account.getVerifyCode().equalsIgnoreCase(String.valueOf(mcCode))) { + MinecraftAccount minecraftAccount = MinecraftAccount.standard(account.getUsername()); + Result result = minecraftAccount.verifyAccount(event.getMember(), event.getGuild()); + event.reply(result.getMessage()).setEphemeral(true).queue(); + return; + } + } + + event.reply("Sorry, we could not verify your Minecraft account. Please try again").setEphemeral(true).queue(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/ViewVerifiedAccounts.java b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/ViewVerifiedAccounts.java new file mode 100644 index 0000000..7898931 --- /dev/null +++ b/src/main/java/com/hypherionmc/sdlink/core/discord/commands/slash/verification/ViewVerifiedAccounts.java @@ -0,0 +1,74 @@ +package com.hypherionmc.sdlink.core.discord.commands.slash.verification; + +import com.hypherionmc.sdlink.core.database.SDLinkAccount; +import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand; +import com.hypherionmc.sdlink.core.util.MessageUtil; +import com.jagrosh.jdautilities.command.SlashCommandEvent; +import com.jagrosh.jdautilities.menu.EmbedPaginator; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase; + +/** + * @author HypherionSA + * Staff Command to view a list of Linked Minecraft and Discord accounts + */ +public class ViewVerifiedAccounts extends SDLinkSlashCommand { + + public ViewVerifiedAccounts() { + super(true); + + this.name = "verifiedaccounts"; + this.help = "View a list of verified Minecraft Accounts"; + } + + @Override + protected void execute(SlashCommandEvent event) { + EmbedPaginator.Builder paginator = MessageUtil.defaultPaginator(event); + + sdlinkDatabase.reloadCollection("verifiedaccounts"); + List accounts = sdlinkDatabase.findAll(SDLinkAccount.class); + + EmbedBuilder builder = new EmbedBuilder(); + ArrayList pages = new ArrayList<>(); + AtomicInteger count = new AtomicInteger(); + + if (accounts.isEmpty()) { + event.reply("There are no verified accounts for this discord").setEphemeral(true).queue(); + return; + } + + MessageUtil.listBatches(accounts, 10).forEach(itm -> { + count.getAndIncrement(); + builder.clear(); + builder.setTitle("Verified Accounts - Page " + count + "/" + (int)Math.ceil(((float)accounts.size() / 10))); + builder.setColor(Color.GREEN); + StringBuilder sBuilder = new StringBuilder(); + + itm.forEach(v -> { + Member member = null; + + if (v.getDiscordID() != null && !v.getDiscordID().isEmpty()) { + member = event.getGuild().getMemberById(v.getDiscordID()); + } + + sBuilder.append(v.getUsername()).append(" -> ").append(member == null ? "Unlinked" : member.getAsMention()).append("\r\n"); + }); + builder.setDescription(sBuilder); + pages.add(builder.build()); + }); + + paginator.setItems(pages); + EmbedPaginator embedPaginator = paginator.build(); + + event.replyEmbeds(pages.get(0)).setEphemeral(false).queue(success -> success.retrieveOriginal().queue(msg -> embedPaginator.paginate(msg, 1))); + } + +} \ No newline at end of file diff --git a/src/main/java/com/hypherionmc/sdlink/core/discord/events/DiscordEventHandler.java b/src/main/java/com/hypherionmc/sdlink/core/discord/events/DiscordEventHandler.java index 0146b36..e52162e 100644 --- a/src/main/java/com/hypherionmc/sdlink/core/discord/events/DiscordEventHandler.java +++ b/src/main/java/com/hypherionmc/sdlink/core/discord/events/DiscordEventHandler.java @@ -5,6 +5,9 @@ package com.hypherionmc.sdlink.core.discord.events; import com.hypherionmc.craterlib.core.event.CraterEventBus; +import com.hypherionmc.sdlink.core.accounts.MinecraftAccount; +import com.hypherionmc.sdlink.core.config.SDLinkConfig; +import com.hypherionmc.sdlink.core.database.SDLinkAccount; import com.hypherionmc.sdlink.core.discord.BotController; import com.hypherionmc.sdlink.core.discord.commands.slash.general.ServerStatusSlashCommand; import com.hypherionmc.sdlink.core.discord.hooks.BotReadyHooks; @@ -14,6 +17,7 @@ import com.hypherionmc.sdlink.core.events.SDLinkReadyEvent; import com.hypherionmc.sdlink.core.managers.CacheManager; import com.hypherionmc.sdlink.core.managers.ChannelManager; import com.hypherionmc.sdlink.core.managers.PermissionChecker; +import com.hypherionmc.sdlink.core.services.SDLinkPlatform; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.events.channel.ChannelCreateEvent; import net.dv8tion.jda.api.events.channel.ChannelDeleteEvent; @@ -28,6 +32,11 @@ import net.dv8tion.jda.api.events.session.ReadyEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.jetbrains.annotations.NotNull; +import java.util.List; +import java.util.Optional; + +import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase; + /** * @author HypherionSA * Class to provide Hooks for Discord Events, such as message received, and login @@ -129,6 +138,26 @@ public class DiscordEventHandler extends ListenerAdapter { if (event.getUser().isBot()) return; - // TODO Verification + if (!SDLinkConfig.INSTANCE.accessControl.enabled) + return; + + try { + List accounts = sdlinkDatabase.getCollection(SDLinkAccount.class); + Optional account = accounts.stream().filter(a -> a.getDiscordID().equalsIgnoreCase(event.getUser().getId())).findFirst(); + + account.ifPresent(a -> { + MinecraftAccount acc = MinecraftAccount.standard(a.getUsername()); + + if (acc != null) { + sdlinkDatabase.remove(a, SDLinkAccount.class); + SDLinkPlatform.minecraftHelper.banPlayer(acc); + sdlinkDatabase.reloadCollection("verifiedaccounts"); + } + }); + } catch (Exception e) { + if (SDLinkConfig.INSTANCE.generalConfig.debugging) { + e.printStackTrace(); + } + } } } diff --git a/src/main/java/com/hypherionmc/sdlink/core/discord/hooks/MinecraftCommandHook.java b/src/main/java/com/hypherionmc/sdlink/core/discord/hooks/MinecraftCommandHook.java index 2672914..1c867bc 100644 --- a/src/main/java/com/hypherionmc/sdlink/core/discord/hooks/MinecraftCommandHook.java +++ b/src/main/java/com/hypherionmc/sdlink/core/discord/hooks/MinecraftCommandHook.java @@ -5,14 +5,18 @@ package com.hypherionmc.sdlink.core.discord.hooks; import com.hypherionmc.sdlink.core.config.SDLinkConfig; +import com.hypherionmc.sdlink.core.database.SDLinkAccount; import com.hypherionmc.sdlink.core.services.SDLinkPlatform; import net.dv8tion.jda.api.entities.ISnowflake; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase; + public class MinecraftCommandHook { public static void discordMessageEvent(MessageReceivedEvent event) { @@ -26,18 +30,19 @@ public class MinecraftCommandHook { roles.add(event.getMember().getIdLong()); roles.add(0L); - // TODO Verification + sdlinkDatabase.reloadCollection("verifiedaccounts"); + List accounts = sdlinkDatabase.findAll(SDLinkAccount.class); + Optional account = accounts.stream().filter(u -> u.getDiscordID().equals(event.getMember().getId())).findFirst(); Integer permLevel = SDLinkConfig.INSTANCE.linkedCommands.permissions.stream().filter(r -> roles.contains(Long.parseLong(r.role))).map(r -> r.permissionLevel).max(Integer::compareTo).orElse(-1); List commands = SDLinkConfig.INSTANCE.linkedCommands.permissions.stream().filter(c -> roles.contains(Long.parseLong(c.role))).flatMap(c -> c.commands.stream()).filter(s -> !s.isEmpty()).toList(); String raw = event.getMessage().getContentRaw().substring(SDLinkConfig.INSTANCE.linkedCommands.prefix.length()); - // TODO Verification if (commands.stream().anyMatch(raw::startsWith)) { - SDLinkPlatform.minecraftHelper.executeMinecraftCommand(raw, Integer.MAX_VALUE, event, null); + SDLinkPlatform.minecraftHelper.executeMinecraftCommand(raw, Integer.MAX_VALUE, event, account.orElse(null)); } else { - SDLinkPlatform.minecraftHelper.executeMinecraftCommand(raw, permLevel, event, null); + SDLinkPlatform.minecraftHelper.executeMinecraftCommand(raw, permLevel, event, account.orElse(null)); } } } diff --git a/src/main/java/com/hypherionmc/sdlink/core/managers/DatabaseManager.java b/src/main/java/com/hypherionmc/sdlink/core/managers/DatabaseManager.java index b22606a..5d03fc0 100644 --- a/src/main/java/com/hypherionmc/sdlink/core/managers/DatabaseManager.java +++ b/src/main/java/com/hypherionmc/sdlink/core/managers/DatabaseManager.java @@ -4,8 +4,11 @@ */ package com.hypherionmc.sdlink.core.managers; +import com.hypherionmc.sdlink.core.database.SDLinkAccount; import io.jsondb.JsonDBTemplate; +import java.util.Collections; + /** * @author HypherionSA * Helper class to initialize the JSON database @@ -15,10 +18,14 @@ public class DatabaseManager { public static final JsonDBTemplate sdlinkDatabase = new JsonDBTemplate("sdlinkstorage", "com.hypherionmc.sdlink.core.database"); static { - + sdlinkDatabase.setupDB(Collections.singleton(SDLinkAccount.class)); } public static void initialize() { - // Todo Verification + if (!sdlinkDatabase.collectionExists(SDLinkAccount.class)) { + sdlinkDatabase.createCollection(SDLinkAccount.class); + } + + sdlinkDatabase.reloadCollection("verifiedaccounts"); } } diff --git a/src/main/java/com/hypherionmc/sdlink/core/managers/RoleManager.java b/src/main/java/com/hypherionmc/sdlink/core/managers/RoleManager.java index d8a06d8..d8818f2 100644 --- a/src/main/java/com/hypherionmc/sdlink/core/managers/RoleManager.java +++ b/src/main/java/com/hypherionmc/sdlink/core/managers/RoleManager.java @@ -4,7 +4,9 @@ */ package com.hypherionmc.sdlink.core.managers; +import com.hypherionmc.sdlink.core.config.SDLinkConfig; import com.hypherionmc.sdlink.core.discord.BotController; +import com.hypherionmc.sdlink.core.util.SDLinkUtils; import com.hypherionmc.sdlink.core.util.SystemUtils; import net.dv8tion.jda.api.entities.Role; @@ -19,7 +21,8 @@ import java.util.concurrent.atomic.AtomicInteger; */ public class RoleManager { - private static final Set autoWhitelistRoles = new HashSet<>(); + private static final Set verificationRoles = new HashSet<>(); + private static Role verifiedRole = null; /** * Check and load the roles required by the bot @@ -27,7 +30,18 @@ public class RoleManager { * @param builder */ public static void loadRequiredRoles(AtomicInteger errCount, StringBuilder builder) { - // TODO Verification Roles + if (SDLinkConfig.INSTANCE.accessControl.enabled) { + SDLinkConfig.INSTANCE.accessControl.requiredRoles.forEach(r -> { + Role role = getRole(errCount, builder, "Access Control Role", r); + + if (role != null) + verificationRoles.add(role); + }); + + if (!SDLinkUtils.isNullOrEmpty(SDLinkConfig.INSTANCE.accessControl.verifiedRole)) { + verifiedRole = getRole(errCount, builder, "Verified Player Role", SDLinkConfig.INSTANCE.accessControl.verifiedRole); + } + } } /** @@ -65,4 +79,12 @@ public class RoleManager { return null; } + + public static Set getVerificationRoles() { + return verificationRoles; + } + + public static Role getVerifiedRole() { + return verifiedRole; + } } diff --git a/src/main/java/com/hypherionmc/sdlink/core/messaging/discord/DiscordMessageBuilder.java b/src/main/java/com/hypherionmc/sdlink/core/messaging/discord/DiscordMessageBuilder.java index 2dbe250..e2247c1 100644 --- a/src/main/java/com/hypherionmc/sdlink/core/messaging/discord/DiscordMessageBuilder.java +++ b/src/main/java/com/hypherionmc/sdlink/core/messaging/discord/DiscordMessageBuilder.java @@ -42,7 +42,11 @@ public final class DiscordMessageBuilder { if (SDLinkConfig.INSTANCE.chatConfig.useLinkedNames && this.author != DiscordAuthor.SERVER) { MinecraftAccount account = MinecraftAccount.standard(author.getRawUsername()); - // TODO Verification + User discordUser = account.getDiscordUser(); + + if (account != null && discordUser != null) { + this.author = DiscordAuthor.of(discordUser.getEffectiveName(), author.getUuid(), author.getRawUsername()); + } } return this; diff --git a/src/main/java/com/hypherionmc/sdlink/core/services/helpers/IMinecraftHelper.java b/src/main/java/com/hypherionmc/sdlink/core/services/helpers/IMinecraftHelper.java index a1afa87..51e1ed7 100644 --- a/src/main/java/com/hypherionmc/sdlink/core/services/helpers/IMinecraftHelper.java +++ b/src/main/java/com/hypherionmc/sdlink/core/services/helpers/IMinecraftHelper.java @@ -5,6 +5,7 @@ package com.hypherionmc.sdlink.core.services.helpers; import com.hypherionmc.sdlink.core.accounts.MinecraftAccount; +import com.hypherionmc.sdlink.core.database.SDLinkAccount; import com.hypherionmc.sdlink.core.messaging.Result; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; @@ -26,8 +27,7 @@ public interface IMinecraftHelper { List getOnlinePlayers(); long getServerUptime(); String getServerVersion(); - // TODO Verification - void executeMinecraftCommand(String command, int permLevel, MessageReceivedEvent event, @Nullable Object account); + void executeMinecraftCommand(String command, int permLevel, MessageReceivedEvent event, @Nullable SDLinkAccount account); boolean isOnlineMode(); void banPlayer(MinecraftAccount acc); } diff --git a/src/main/java/com/hypherionmc/sdlink/core/util/SDLinkUtils.java b/src/main/java/com/hypherionmc/sdlink/core/util/SDLinkUtils.java new file mode 100644 index 0000000..cb5ebcb --- /dev/null +++ b/src/main/java/com/hypherionmc/sdlink/core/util/SDLinkUtils.java @@ -0,0 +1,9 @@ +package com.hypherionmc.sdlink.core.util; + +public class SDLinkUtils { + + public static boolean isNullOrEmpty(String inString) { + return inString == null || inString.isEmpty(); + } + +}