diff --git a/build-logic/src/main/kotlin/floodgate.publish-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate.publish-conventions.gradle.kts index 39e180ae..696d05ac 100644 --- a/build-logic/src/main/kotlin/floodgate.publish-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate.publish-conventions.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("floodgate.shadow-conventions") + id("floodgate.base-conventions") id("net.kyori.indra.publishing") } diff --git a/build-logic/src/main/kotlin/floodgate.shadow-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate.shadow-conventions.gradle.kts index eebd8c34..326d7f89 100644 --- a/build-logic/src/main/kotlin/floodgate.shadow-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate.shadow-conventions.gradle.kts @@ -19,7 +19,7 @@ tasks { val sJar: ShadowJar = this doFirst { -// mergeServiceFiles() + mergeServiceFiles() providedDependencies[project.name]?.forEach { (name, notation) -> sJar.dependencies { diff --git a/build.gradle.kts b/build.gradle.kts index 3ca9c913..ac7c0f99 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,6 +21,16 @@ val deployProjects = setOf( projects.universal ).map { it.dependencyProject } +val shadowProjects = setOf( + projects.api, + // for future Floodgate integration + Fabric + projects.core, + projects.bungee, + projects.spigot, + projects.velocityBase, + projects.universal +).map { it.dependencyProject } + //todo re-add checkstyle when we switch back to 2 space indention // and take a look again at spotbugs someday @@ -35,4 +45,8 @@ subprojects { in deployProjects -> plugins.apply("floodgate.publish-conventions") else -> plugins.apply("floodgate.base-conventions") } + + if (this in shadowProjects) { + plugins.apply("floodgate.shadow-conventions") + } } \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 892e6850..df03c447 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -22,23 +22,16 @@ dependencies { annotationProcessor("io.micronaut:micronaut-inject-java") api("io.micronaut", "micronaut-inject-java") api("io.micronaut", "micronaut-context") + api("io.micronaut", "micronaut-http-client") + api("io.micronaut", "micronaut-validation") + + //todo add hibernate dependency back in core, + // it's not possible to make it optional as the service files would be messed up + api(projects.database) annotationProcessor("io.micronaut.data:micronaut-data-processor") implementation("io.micronaut.data:micronaut-data-model") implementation("jakarta.persistence:jakarta.persistence-api:2.2.3") - -// compileOnlyApi("io.micronaut.data:micronaut-data-hibernate-jpa") - //todo add these as libs - //compileOnly("io.micronaut.data:micronaut-data-hibernate-jpa") - //compileOnly("io.micronaut.sql:micronaut-jdbc-hikari") - //compileOnly("com.h2database:h2") - //implementation("io.micronaut.data:micronaut-data-hibernate-jpa") - //implementation("io.micronaut.sql:micronaut-jdbc-hikari") - //runtimeOnly("com.h2database:h2") - -// annotationProcessor("io.micronaut.data:micronaut-data-document-processor") -// compileOnly("io.micronaut.data:micronaut-data-mongodb") -// runtimeOnly("org.mongodb:mongodb-driver-sync") } // present on all platforms @@ -49,7 +42,8 @@ relocate("org.bstats") tasks { templateSources { - replaceToken("floodgateVersion", fullVersion()) + replaceToken("fullVersion", fullVersion()) + replaceToken("version", version) replaceToken("branch", branchName()) replaceToken("buildNumber", buildNumber()) } diff --git a/core/src/main/java/org/geysermc/floodgate/core/FloodgatePlatform.java b/core/src/main/java/org/geysermc/floodgate/core/FloodgatePlatform.java index 04ef2fc7..8569d58a 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/FloodgatePlatform.java +++ b/core/src/main/java/org/geysermc/floodgate/core/FloodgatePlatform.java @@ -33,20 +33,15 @@ import org.geysermc.floodgate.api.InstanceHolder; import org.geysermc.floodgate.api.event.FloodgateEventBus; import org.geysermc.floodgate.api.handshake.HandshakeHandlers; import org.geysermc.floodgate.api.inject.PlatformInjector; -import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.packet.PacketHandlers; import org.geysermc.floodgate.core.config.Properties; -import org.geysermc.floodgate.core.database.PlayerLinkRepository; -import org.geysermc.floodgate.core.database.entity.LinkedPlayer; import org.geysermc.floodgate.core.event.EventBus; import org.geysermc.floodgate.core.event.lifecycle.PostEnableEvent; import org.geysermc.floodgate.core.event.lifecycle.ShutdownEvent; import org.geysermc.floodgate.core.util.EagerSingleton; -import org.geysermc.floodgate.isolation.library.Library; +import org.geysermc.floodgate.core.util.GlobalBeanCache; import org.geysermc.floodgate.isolation.library.LibraryManager; -import org.geysermc.floodgate.isolation.library.Repository; -import org.geysermc.floodgate.isolation.library.info.DependencyInfoLoader; public abstract class FloodgatePlatform { private static final UUID KEY = UUID.randomUUID(); @@ -65,32 +60,11 @@ public abstract class FloodgatePlatform { public void load() { long startTime = System.currentTimeMillis(); - var infoLoader = DependencyInfoLoader.load( - getClass().getClassLoader().getResource("dependencyInfo.txt") - ); - - manager -// .addLibrary( -// Library.builder(infoLoader) -// .id("guava") -// .repository(Repository.MAVEN_CENTRAL) -// .groupId("com.google.guava") -// .artifactId("guava") -// .build() -// ) - .addLibrary( - Library.builder() - .id("local-linking") - .repository(Repository.OPEN_COLLAB) - .groupId("org.geysermc.floodgate") - .artifactId("database") - .version("a-version") - .build() - ) - .apply(); + GlobalBeanCache.cacheIfAbsent("libraryManager", () -> manager); //noinspection unchecked context = ApplicationContext.builder(manager.classLoader()) + .singletons(manager) .properties(Map.of( "platform.proxy", isProxy() )) @@ -102,34 +76,11 @@ public abstract class FloodgatePlatform { onContextCreated(context); context.start(); - LinkedPlayer link = new LinkedPlayer() - .bedrockId(UUID.fromString("00000000-0000-0000-0009-01f64f65c7c3")) - .javaUniqueId(UUID.fromString("d34eb447-6e90-4c78-9281-600df88aef1d")) - .javaUsername("Tim203"); - System.out.println(context.getBean(PlayerLinkRepository.class).save(link)); - - System.out.println(context.getBean(PlayerLinkRepository.class) - .findByBedrockId(UUID.fromString("00000000-0000-0000-0009-01f64f65c7c3"))); - System.out.println(context.getBean(PlayerLinkRepository.class) - .findByJavaUniqueId(UUID.fromString("d34eb447-6e90-4c78-9281-600df88aef1d"))); - System.out.println(context.getBean(PlayerLinkRepository.class) - .existsByBedrockId(UUID.fromString("00000000-0000-0000-0009-01f64f65c7c3"))); - System.out.println(context.getBean(PlayerLinkRepository.class) - .existsByJavaUniqueId(UUID.fromString("d34eb447-6e90-4c78-9281-600df88aef1d"))); - -// var scopeBuilder = BeanScope.builder() -// .bean("isProxy", boolean.class, isProxy()) -// .modules(new CoreModule()) -// // temporary fix for https://github.com/avaje/avaje-inject/issues/295 -// .modules(makeModule(isProxy() ? PROXY_MODULE : SERVER_MODULE)); -// onBuildBeanScope(scopeBuilder); -// scope = scopeBuilder.build(); - injector = context.getBean(PlatformInjector.class); InstanceHolder.set( context.getBean(FloodgateApi.class), - context.getBean(PlayerLink.class), + null, // todo context.getBean(PlayerLink.class), context.getBean(FloodgateEventBus.class), injector, context.getBean(PacketHandlers.class), diff --git a/core/src/main/java/org/geysermc/floodgate/core/api/SimpleFloodgateApi.java b/core/src/main/java/org/geysermc/floodgate/core/api/SimpleFloodgateApi.java index 50b67304..6d7c7c7a 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/api/SimpleFloodgateApi.java +++ b/core/src/main/java/org/geysermc/floodgate/core/api/SimpleFloodgateApi.java @@ -28,8 +28,6 @@ package org.geysermc.floodgate.core.api; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableSet; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import io.micronaut.context.BeanProvider; import jakarta.inject.Inject; import java.util.Collection; @@ -45,12 +43,13 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.unsafe.Unsafe; import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.http.xbox.GetGamertagResult; +import org.geysermc.floodgate.core.http.xbox.GetXuidResult; +import org.geysermc.floodgate.core.http.xbox.XboxClient; import org.geysermc.floodgate.core.pluginmessage.PluginMessageManager; import org.geysermc.floodgate.core.pluginmessage.channel.FormChannel; import org.geysermc.floodgate.core.pluginmessage.channel.TransferChannel; import org.geysermc.floodgate.core.scope.ServerOnly; -import org.geysermc.floodgate.core.util.Constants; -import org.geysermc.floodgate.core.util.HttpClient; import org.geysermc.floodgate.core.util.Utils; @ServerOnly @@ -63,8 +62,8 @@ public class SimpleFloodgateApi implements FloodgateApi { @Inject BeanProvider pluginMessageManager; @Inject FloodgateConfig config; - @Inject HttpClient httpClient; @Inject FloodgateLogger logger; + @Inject XboxClient xboxClient; @Override public String getPlayerPrefix() { @@ -148,36 +147,12 @@ public class SimpleFloodgateApi implements FloodgateApi { @Override public CompletableFuture getXuidFor(String gamertag) { - if (gamertag == null || gamertag.isEmpty() || gamertag.length() > 16) { - return Utils.failedFuture(new IllegalStateException("Received an invalid gamertag")); - } - - return httpClient.asyncGet(Constants.GET_XUID_URL + gamertag) - .thenApply(result -> { - JsonObject response = result.getResponse(); - - if (!result.isCodeOk()) { - throw new IllegalStateException(response.get("message").getAsString()); - } - - JsonElement xuid = response.get("xuid"); - return xuid != null ? xuid.getAsLong() : null; - }); + return xboxClient.xuidByGamertag(gamertag).thenApply(GetXuidResult::xuid); } @Override public CompletableFuture getGamertagFor(long xuid) { - return httpClient.asyncGet(Constants.GET_GAMERTAG_URL + xuid) - .thenApply(result -> { - JsonObject response = result.getResponse(); - - if (!result.isCodeOk()) { - throw new IllegalStateException(response.get("message").getAsString()); - } - - JsonElement gamertag = response.get("gamertag"); - return gamertag != null ? gamertag.getAsString() : null; - }); + return xboxClient.gamertagByXuid(xuid).thenApply(GetGamertagResult::gamertag); } @Override diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/LinkAccountCommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/LinkAccountCommand.java index b8d7a9c3..8deb284e 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/LinkAccountCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/LinkAccountCommand.java @@ -33,14 +33,15 @@ import cloud.commandframework.context.CommandContext; import io.micronaut.context.annotation.Secondary; import jakarta.inject.Inject; import jakarta.inject.Singleton; +import java.util.concurrent.CompletableFuture; import lombok.Getter; import org.geysermc.floodgate.api.FloodgateApi; -import org.geysermc.floodgate.api.link.LinkRequestResult; -import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.command.util.Permission; import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.link.CommonPlayerLink; import org.geysermc.floodgate.core.link.GlobalPlayerLinking; +import org.geysermc.floodgate.core.link.LinkVerificationException; import org.geysermc.floodgate.core.platform.command.FloodgateCommand; import org.geysermc.floodgate.core.platform.command.TranslatableMessage; import org.geysermc.floodgate.core.player.UserAudience; @@ -48,11 +49,13 @@ import org.geysermc.floodgate.core.player.UserAudience.PlayerAudience; import org.geysermc.floodgate.core.player.audience.ProfileAudience; import org.geysermc.floodgate.core.player.audience.ProfileAudienceArgument; import org.geysermc.floodgate.core.util.Constants; +import org.geysermc.floodgate.core.util.Utils; @Singleton @Secondary public final class LinkAccountCommand implements FloodgateCommand { @Inject FloodgateApi api; + @Inject CommonPlayerLink link; @Inject FloodgateLogger logger; @Override @@ -71,11 +74,9 @@ public final class LinkAccountCommand implements FloodgateCommand { public void execute(CommandContext context) { UserAudience sender = context.getSender(); - PlayerLink link = api.getPlayerLink(); - //todo make this less hacky if (link instanceof GlobalPlayerLinking) { - if (((GlobalPlayerLinking) link).getDatabaseImpl() != null) { + if (((GlobalPlayerLinking) link).getDatabase() != null) { sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, Constants.LINK_INFO_URL); } else { @@ -85,7 +86,7 @@ public final class LinkAccountCommand implements FloodgateCommand { } } - if (!link.isEnabledAndAllowed()) { + if (!link.isActive()) { sender.sendMessage(CommonCommandMessage.LINKING_DISABLED); return; } @@ -103,33 +104,40 @@ public final class LinkAccountCommand implements FloodgateCommand { String code = context.get("code"); - link.verifyLinkRequest(sender.uuid(), targetName, sender.username(), code) - .whenComplete((result, throwable) -> { - if (throwable != null || result == LinkRequestResult.UNKNOWN_ERROR) { + link.linkRequest(targetName) + .thenApply(request -> { + if (request == null || link.isRequestedPlayer(request, sender.uuid())) { + throw LinkVerificationException.NO_LINK_REQUESTED; + } + + if (!request.linkCode().equals(code)) { + throw LinkVerificationException.INVALID_CODE; + } + + if (request.isExpired(link.getVerifyLinkTimeout())) { + throw LinkVerificationException.LINK_REQUEST_EXPIRED; + } + + return request; + }) + .thenCompose(request -> + CompletableFuture.allOf( + link.invalidateLinkRequest(request), + link.addLink( + request.javaUniqueId(), request.javaUsername(), sender.uuid() + ) + ) + ) + .whenComplete(($, throwable) -> { + if (throwable instanceof LinkVerificationException exception) { + sender.sendMessage(exception.message()); + return; + } + if (throwable != null) { sender.sendMessage(Message.LINK_REQUEST_ERROR); return; } - - switch (result) { - case ALREADY_LINKED: - sender.sendMessage(Message.ALREADY_LINKED); - break; - case NO_LINK_REQUESTED: - sender.sendMessage(Message.NO_LINK_REQUESTED); - break; - case INVALID_CODE: - sender.sendMessage(Message.INVALID_CODE); - break; - case REQUEST_EXPIRED: - sender.sendMessage(Message.LINK_REQUEST_EXPIRED); - break; - case LINK_COMPLETED: - sender.disconnect(Message.LINK_REQUEST_COMPLETED, targetName); - break; - default: - sender.disconnect("Invalid account linking result"); - break; - } + sender.disconnect(Message.LINK_REQUEST_COMPLETED, targetName); }); return; } @@ -139,21 +147,16 @@ public final class LinkAccountCommand implements FloodgateCommand { return; } - link.createLinkRequest(sender.uuid(), sender.username(), targetName) - .whenComplete((result, throwable) -> { - if (throwable != null || result == LinkRequestResult.UNKNOWN_ERROR) { + String username = sender.username(); + String code = Utils.generateCode(6); + + link.createLinkRequest(sender.uuid(), username, targetName, code) + .whenComplete(($, throwable) -> { + if (throwable != null) { sender.sendMessage(Message.LINK_REQUEST_ERROR); return; } - - if (!(result instanceof String)) { - logger.error("Expected string code, got {}", result); - sender.sendMessage(Message.LINK_REQUEST_ERROR); - return; - } - - sender.sendMessage(Message.LINK_REQUEST_CREATED, - targetName, sender.username(), result); + sender.sendMessage(Message.LINK_REQUEST_CREATED, targetName, username, code); }); } diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/UnlinkAccountCommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/UnlinkAccountCommand.java index 1ddee784..8894eef2 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/UnlinkAccountCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/UnlinkAccountCommand.java @@ -32,10 +32,9 @@ import cloud.commandframework.context.CommandContext; import jakarta.inject.Inject; import jakarta.inject.Singleton; import lombok.Getter; -import org.geysermc.floodgate.api.FloodgateApi; -import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.core.command.util.Permission; import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.link.CommonPlayerLink; import org.geysermc.floodgate.core.link.GlobalPlayerLinking; import org.geysermc.floodgate.core.platform.command.FloodgateCommand; import org.geysermc.floodgate.core.platform.command.TranslatableMessage; @@ -45,7 +44,7 @@ import org.geysermc.floodgate.core.util.Constants; @Singleton public final class UnlinkAccountCommand implements FloodgateCommand { - @Inject FloodgateApi api; + @Inject CommonPlayerLink link; @Override public Command buildCommand(CommandManager commandManager) { @@ -61,11 +60,9 @@ public final class UnlinkAccountCommand implements FloodgateCommand { public void execute(CommandContext context) { UserAudience sender = context.getSender(); - PlayerLink link = api.getPlayerLink(); - //todo make this less hacky if (link instanceof GlobalPlayerLinking) { - if (((GlobalPlayerLinking) link).getDatabaseImpl() != null) { + if (((GlobalPlayerLinking) link).getDatabase() != null) { sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, Constants.LINK_INFO_URL); } else { @@ -75,12 +72,12 @@ public final class UnlinkAccountCommand implements FloodgateCommand { } } - if (!link.isEnabledAndAllowed()) { + if (!link.isActive()) { sender.sendMessage(CommonCommandMessage.LINKING_DISABLED); return; } - link.isLinkedPlayer(sender.uuid()) + link.isLinked(sender.uuid()) .whenComplete((linked, error) -> { if (error != null) { sender.sendMessage(CommonCommandMessage.IS_LINKED_ERROR); @@ -92,7 +89,7 @@ public final class UnlinkAccountCommand implements FloodgateCommand { return; } - link.unlinkPlayer(sender.uuid()) + link.unlink(sender.uuid()) .whenComplete((unused, error1) -> { if (error1 != null) { sender.sendMessage(Message.UNLINK_ERROR); diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/WhitelistCommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/WhitelistCommand.java index 0969690a..8d0c553c 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/WhitelistCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/WhitelistCommand.java @@ -29,8 +29,7 @@ import cloud.commandframework.ArgumentDescription; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import cloud.commandframework.context.CommandContext; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; +import io.micronaut.http.client.exceptions.HttpClientResponseException; import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.util.UUID; @@ -40,6 +39,8 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.command.util.Permission; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.config.ProxyFloodgateConfig; +import org.geysermc.floodgate.core.http.UnsuccessfulResponse; +import org.geysermc.floodgate.core.http.xbox.XboxClient; import org.geysermc.floodgate.core.platform.command.CommandUtil; import org.geysermc.floodgate.core.platform.command.FloodgateCommand; import org.geysermc.floodgate.core.platform.command.TranslatableMessage; @@ -47,13 +48,12 @@ import org.geysermc.floodgate.core.platform.util.PlayerType; import org.geysermc.floodgate.core.player.UserAudience; import org.geysermc.floodgate.core.player.audience.ProfileAudience; import org.geysermc.floodgate.core.player.audience.ProfileAudienceArgument; -import org.geysermc.floodgate.core.util.Constants; -import org.geysermc.floodgate.core.util.HttpClient; @Singleton public class WhitelistCommand implements FloodgateCommand { + @Inject FloodgateApi api; @Inject FloodgateConfig config; - @Inject HttpClient httpClient; + @Inject XboxClient xboxClient; @Inject FloodgateLogger logger; @Override @@ -86,7 +86,7 @@ public class WhitelistCommand implements FloodgateCommand { } if (uuid != null) { - if (!FloodgateApi.getInstance().isFloodgateId(uuid)) { + if (!api.isFloodgateId(uuid)) { sender.sendMessage(Message.INVALID_USERNAME); return; } @@ -129,32 +129,34 @@ public class WhitelistCommand implements FloodgateCommand { final String strippedName = name; // We need to get the UUID of the player if it's not manually specified - httpClient.asyncGet(Constants.GET_XUID_URL + name) + xboxClient.xuidByGamertag(name) .whenComplete((result, error) -> { if (error != null) { - sender.sendMessage(Message.API_UNAVAILABLE); - error.printStackTrace(); + if (!(error instanceof HttpClientResponseException exception)) { + sender.sendMessage(Message.API_UNAVAILABLE); + error.printStackTrace(); + return; + } + sender.sendMessage(Message.UNEXPECTED_ERROR); + + var response = exception.getResponse().getBody(UnsuccessfulResponse.class); + var message = + response.isPresent() ? + response.get().message() : + exception.getMessage(); + logger.error( + "Got an error from requesting the xuid of a Bedrock player: {}", + message + ); return; } - JsonObject response = result.getResponse(); - - if (!result.isCodeOk()) { - sender.sendMessage(Message.UNEXPECTED_ERROR); - logger.error( - "Got an error from requesting the xuid of a Bedrock player: {}", - response.get("message").getAsString() - ); - } - - JsonElement xuidElement = response.get("xuid"); - - if (xuidElement == null) { + Long xuid = result.xuid(); + if (xuid == null) { sender.sendMessage(Message.USER_NOT_FOUND); return; } - String xuid = xuidElement.getAsString(); CommandUtil commandUtil = context.get("CommandUtil"); try { diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/main/VersionSubcommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/main/VersionSubcommand.java index 950bca4f..5ef05bd8 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/main/VersionSubcommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/main/VersionSubcommand.java @@ -69,7 +69,7 @@ public class VersionSubcommand extends FloodgateSubCommand { COLOR_CHAR + "7You're currently on " + COLOR_CHAR + "b%s" + COLOR_CHAR + "7 (branch: " + COLOR_CHAR + "b%s" + COLOR_CHAR + "7)\n" + COLOR_CHAR + "eFetching latest build info...", - Constants.VERSION, Constants.GIT_BRANCH + Constants.FULL_VERSION, Constants.GIT_BRANCH )); String baseUrl = String.format( diff --git a/core/src/main/java/org/geysermc/floodgate/core/config/ConfigAsProperties.java b/core/src/main/java/org/geysermc/floodgate/core/config/ConfigAsProperties.java new file mode 100644 index 00000000..911ad2a2 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/config/ConfigAsProperties.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.core.config; + +import io.micronaut.context.annotation.BootstrapContextCompatible; +import io.micronaut.context.env.BootstrapPropertySourceLocator; +import io.micronaut.context.env.Environment; +import io.micronaut.context.env.PropertySource; +import io.micronaut.context.exceptions.ConfigurationException; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.inject.Singleton; +import java.lang.reflect.Proxy; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.geysermc.configutils.node.codec.strategy.object.ProxyEmbodimentStrategy; +import org.geysermc.floodgate.core.database.loader.DatabaseType; +import org.geysermc.floodgate.core.util.GlobalBeanCache; +import org.geysermc.floodgate.isolation.library.LibraryManager; + +@Singleton +@BootstrapContextCompatible +public class ConfigAsProperties implements BootstrapPropertySourceLocator { + @Inject + @Named("dataDirectory") + Path dataDirectory; + + @Inject FloodgateConfig config; + + public ConfigAsProperties() { + System.out.println("test1"); + } + + @Override + public Iterable findPropertySources(Environment environment) + throws ConfigurationException { + loadDatabase(); + + var flat = flattern(Map.of("config", config)); + flat.forEach((key, value) -> System.out.println(key + ": " + value)); + + return Collections.singleton(PropertySource.of(flat)); + } + + private void loadDatabase() { + LibraryManager manager = GlobalBeanCache.get("libraryManager"); + + var databaseConfig = config.database(); + if (!databaseConfig.enabled()) { + return; + } + + var type = DatabaseType.byId(databaseConfig.type()); + if (type == null) { + throw new IllegalStateException( + "Unable to find database type that matches: " + databaseConfig.type() + ); + } + + type.libraries().forEach(manager::addLibrary); + manager.apply(); + } + + private Map flattern(Map map) { + return flattern0(Map.of("", map).get("")); + } + + @SuppressWarnings("unchecked") + private Map flattern0(Map map) { + var result = new HashMap(); + map.forEach((key, value) -> { + if (Proxy.isProxyClass(value.getClass())) { + value = new ProxyEmbodimentStrategy().disembody(value); + } + if (value instanceof Map) { + flattern0((Map) value).forEach( + (key1, value1) -> result.put(key + "." + key1, value1) + ); + return; + } + result.put(key, value); + }); + return result; + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/config/ConfigLoader.java b/core/src/main/java/org/geysermc/floodgate/core/config/ConfigLoader.java index 3d0005f8..f54f9b28 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/config/ConfigLoader.java +++ b/core/src/main/java/org/geysermc/floodgate/core/config/ConfigLoader.java @@ -25,8 +25,8 @@ package org.geysermc.floodgate.core.config; -import io.micronaut.context.ApplicationContext; import io.micronaut.context.annotation.Bean; +import io.micronaut.context.annotation.BootstrapContextCompatible; import io.micronaut.context.annotation.Factory; import jakarta.inject.Inject; import jakarta.inject.Named; @@ -39,49 +39,43 @@ import lombok.Getter; import org.geysermc.configutils.ConfigUtilities; import org.geysermc.configutils.file.codec.PathFileCodec; import org.geysermc.configutils.updater.change.Changes; -import org.geysermc.floodgate.core.event.lifecycle.ConfigLoadedEvent; import org.geysermc.floodgate.core.scope.ProxyOnly; import org.geysermc.floodgate.core.scope.ServerOnly; -import org.geysermc.floodgate.core.util.LanguageManager; +import org.geysermc.floodgate.core.util.GlobalBeanCache; import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.crypto.KeyProducer; @Factory @Getter +@BootstrapContextCompatible public final class ConfigLoader { private final Path dataDirectory; private final KeyProducer keyProducer; private final FloodgateCipher cipher; - private final LanguageManager languageManager; - private final ApplicationContext context; @Inject ConfigLoader( @Named("dataDirectory") Path dataDirectory, KeyProducer keyProducer, - FloodgateCipher cipher, - LanguageManager languageManager, - ApplicationContext context + FloodgateCipher cipher ) { this.dataDirectory = dataDirectory; this.keyProducer = keyProducer; this.cipher = cipher; - this.languageManager = languageManager; - this.context = context; } @Bean @ServerOnly @Singleton FloodgateConfig config() { - return load(FloodgateConfig.class); + return GlobalBeanCache.cacheIfAbsent("config", () -> load(FloodgateConfig.class)); } @Bean @ProxyOnly @Singleton ProxyFloodgateConfig proxyConfig() { - return load(ProxyFloodgateConfig.class); + return GlobalBeanCache.cacheIfAbsent("config", () -> load(ProxyFloodgateConfig.class)); } @SuppressWarnings("unchecked") @@ -99,25 +93,20 @@ public final class ConfigLoader { .keyRenamed("playerLink.allowLinking", "playerLink.allowed")) .version(2, Changes.versionBuilder() .keyRenamed("playerLink.useGlobalLinking", "playerLink.enableGlobalLinking")) + .version(3, Changes.versionBuilder() + .keyRenamed("playerLink.type", "database.type")) .build()) .definePlaceholder("metrics.uuid", UUID::randomUUID) .postInitializeCallbackArgument(this) - .commentTranslator((key) -> languageManager.getLogString("floodgate.config." + key)) .build(); - - T config; try { - config = (T) utilities.executeOn(configClass); + return (T) utilities.executeOn(configClass); } catch (Throwable throwable) { throw new RuntimeException( "Failed to load the config! Try to delete the config file if this error persists", throwable ); } - - context.getEventPublisher(ConfigLoadedEvent.class) - .publishEvent(new ConfigLoadedEvent(config)); - return config; } public void generateKey(Path keyPath) { diff --git a/core/src/main/java/org/geysermc/floodgate/core/config/FloodgateConfig.java b/core/src/main/java/org/geysermc/floodgate/core/config/FloodgateConfig.java index d8c7f7ff..ebb5b77e 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/config/FloodgateConfig.java +++ b/core/src/main/java/org/geysermc/floodgate/core/config/FloodgateConfig.java @@ -75,10 +75,6 @@ public interface FloodgateConfig extends GenericPostInitializeCallback { + CompletableFuture findByJavaUsername(String javaUsername); +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/database/PlayerLinkRepository.java b/core/src/main/java/org/geysermc/floodgate/core/database/PlayerLinkRepository.java index 2c95a9ab..95c7a550 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/database/PlayerLinkRepository.java +++ b/core/src/main/java/org/geysermc/floodgate/core/database/PlayerLinkRepository.java @@ -25,18 +25,31 @@ package org.geysermc.floodgate.core.database; +import io.micronaut.context.annotation.Requires; import io.micronaut.data.annotation.Repository; -import io.micronaut.data.repository.CrudRepository; +import io.micronaut.data.repository.async.AsyncCrudRepository; import java.util.Optional; import java.util.UUID; import javax.validation.constraints.NotNull; import org.geysermc.floodgate.core.database.entity.LinkedPlayer; @Repository -public interface PlayerLinkRepository extends CrudRepository { +@Requires(property = "config.database.enabled", value = "true") +public interface PlayerLinkRepository extends AsyncCrudRepository { Optional findByBedrockId(@NotNull UUID bedrockId); + Optional findByJavaUniqueId(@NotNull UUID javaUniqueId); + Optional findByBedrockIdOrJavaUniqueId( + UUID bedrockId, + UUID javaUniqueId + ); + Boolean existsByBedrockId(@NotNull UUID bedrockId); + Boolean existsByJavaUniqueId(@NotNull UUID javaUniqueId); + + Boolean existsByBedrockIdOrJavaUniqueId(UUID bedrockId, UUID javaUniqueId); + + void deleteByBedrockIdOrJavaUniqueId(UUID bedrockId, UUID javaUniqueId); } diff --git a/core/src/main/java/org/geysermc/floodgate/core/database/config/DatabaseConfigLoader.java b/core/src/main/java/org/geysermc/floodgate/core/database/config/DatabaseConfigLoader.java deleted file mode 100644 index 388dedcb..00000000 --- a/core/src/main/java/org/geysermc/floodgate/core/database/config/DatabaseConfigLoader.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Floodgate - */ - -package org.geysermc.floodgate.core.database.config; - -import com.google.gson.JsonObject; -import io.micronaut.context.annotation.Requires; -import jakarta.inject.Inject; -import jakarta.inject.Named; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import org.yaml.snakeyaml.Yaml; - -@Requires(env = "no") -public class DatabaseConfigLoader { - private Yaml yaml; - - @Inject - @Named("dataDirectory") - Path dataDirectory; - - @Inject - @Named("database.name") - String name; - - @Inject - @Named("database.classloader") - ClassLoader classLoader; - - @Inject - @Named("database.init.data") - JsonObject initData; - - @Inject - public void init() { -// yaml = new Yaml(new CustomClassLoaderConstructor(classLoader)); -// yaml.setBeanAccess(BeanAccess.FIELD); - } - - /** - * This will load the config if it already exists or will create the config from the default - * config file if it doesn't exist. - * - * @param configType the class to parse the config into - * @param type that extends the base DatabaseConfig class - * @return the config if successful or null if not. It'll return null if the database didn't - * provide a config or if there is no config present nor default config available or if an error - * occurred while executing this method. - */ - public T loadAs(Class configType) { - if (!initData.has("config")) { - return null; - } - - String configFile = initData.get("config").getAsString(); - Path configPath = dataDirectory.resolve(name).resolve(configFile); - - // return the existing config - if (Files.exists(configPath)) { - try (BufferedReader reader = Files.newBufferedReader(configPath)) { - return yaml.loadAs(reader, configType); - } catch (IOException exception) { - exception.printStackTrace(); - return null; - } - } - - // make directories - try { - Files.createDirectories(configPath.getParent()); - } catch (IOException exception) { - exception.printStackTrace(); - return null; - } - - // load default config resource - try (InputStream configStream = classLoader.getResourceAsStream(configFile)) { - if (configStream == null) { - return null; - } - - // copy resource and load config - if (!configStream.markSupported()) { - Files.copy(configStream, configPath); - try (InputStream configStream1 = classLoader.getResourceAsStream(configFile)) { - return yaml.loadAs(configStream1, configType); - } - } - - configStream.mark(Integer.MAX_VALUE); - Files.copy(configStream, configPath); - configStream.reset(); - return yaml.loadAs(configStream, configType); - } catch (IOException exception) { - exception.printStackTrace(); - } - return null; - } -} diff --git a/core/src/main/java/org/geysermc/floodgate/core/database/entity/LinkRequest.java b/core/src/main/java/org/geysermc/floodgate/core/database/entity/LinkRequest.java new file mode 100644 index 00000000..6e349b63 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/database/entity/LinkRequest.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.core.database.entity; + +import io.micronaut.core.annotation.AccessorsStyle; +import java.time.Instant; +import java.util.Objects; +import java.util.UUID; +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +@AccessorsStyle(readPrefixes = "", writePrefixes = "") +public class LinkRequest { + @Id + private UUID javaUniqueId; + private String javaUsername; + private String bedrockUsername; + private String linkCode; + private long requestTime = Instant.now().getEpochSecond(); + + public UUID javaUniqueId() { + return javaUniqueId; + } + + public LinkRequest javaUniqueId(UUID javaUniqueId) { + this.javaUniqueId = javaUniqueId; + return this; + } + + public String javaUsername() { + return javaUsername; + } + + public LinkRequest javaUsername(String javaUsername) { + this.javaUsername = javaUsername; + return this; + } + + public String bedrockUsername() { + return bedrockUsername; + } + + public LinkRequest bedrockUsername(String bedrockUsername) { + this.bedrockUsername = bedrockUsername; + return this; + } + + public String linkCode() { + return linkCode; + } + + public LinkRequest linkCode(String linkCode) { + this.linkCode = linkCode; + return this; + } + + public long requestTime() { + return requestTime; + } + + public LinkRequest requestTime(long requestTime) { + this.requestTime = requestTime; + return this; + } + + public boolean isExpired(long linkTimeout) { + long timePassed = Instant.now().getEpochSecond() - requestTime; + return timePassed > linkTimeout; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LinkRequest that = (LinkRequest) o; + return Objects.equals(javaUniqueId, that.javaUniqueId); + } + + @Override + public int hashCode() { + return Objects.hash(javaUniqueId); + } + + @Override + public String toString() { + return "LinkRequest{" + + "javaUniqueId=" + javaUniqueId + + ", javaUsername='" + javaUsername + '\'' + + ", bedrockUsername='" + bedrockUsername + '\'' + + ", linkCode='" + linkCode + '\'' + + ", requestTime=" + requestTime + + '}'; + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/database/entity/LinkedPlayer.java b/core/src/main/java/org/geysermc/floodgate/core/database/entity/LinkedPlayer.java index cfe8d6fa..6e55adfc 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/database/entity/LinkedPlayer.java +++ b/core/src/main/java/org/geysermc/floodgate/core/database/entity/LinkedPlayer.java @@ -25,19 +25,21 @@ package org.geysermc.floodgate.core.database.entity; +import io.micronaut.core.annotation.AccessorsStyle; import java.util.Objects; import java.util.UUID; import javax.persistence.Entity; import javax.persistence.Id; @Entity +@AccessorsStyle(readPrefixes = "", writePrefixes = "") public class LinkedPlayer { @Id private UUID bedrockId; private UUID javaUniqueId; private String javaUsername; - public UUID getBedrockId() { + public UUID bedrockId() { return bedrockId; } @@ -46,7 +48,7 @@ public class LinkedPlayer { return this; } - public UUID getJavaUniqueId() { + public UUID javaUniqueId() { return javaUniqueId; } @@ -55,7 +57,7 @@ public class LinkedPlayer { return this; } - public String getJavaUsername() { + public String javaUsername() { return javaUsername; } @@ -74,13 +76,12 @@ public class LinkedPlayer { } var that = (LinkedPlayer) obj; return Objects.equals(this.bedrockId, that.bedrockId) && - Objects.equals(this.javaUniqueId, that.javaUniqueId) && - Objects.equals(this.javaUsername, that.javaUsername); + Objects.equals(this.javaUniqueId, that.javaUniqueId); } @Override public int hashCode() { - return Objects.hash(bedrockId, javaUniqueId, javaUsername); + return Objects.hash(bedrockId, javaUniqueId); } @Override diff --git a/core/src/main/java/org/geysermc/floodgate/core/database/loader/DatabaseType.java b/core/src/main/java/org/geysermc/floodgate/core/database/loader/DatabaseType.java new file mode 100644 index 00000000..e8727e49 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/database/loader/DatabaseType.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.core.database.loader; + +import java.util.Locale; +import java.util.Objects; +import java.util.Set; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.core.util.Constants; +import org.geysermc.floodgate.isolation.library.Library; +import org.geysermc.floodgate.isolation.library.Repository; + +public enum DatabaseType { + H2( + DriverCategory.HIBERNATE, + databaseModule(), + library("h2", Repository.MAVEN_CENTRAL, "com.h2database", "h2", "1.4.200") + ); + + private static final DatabaseType[] VALUES = values(); + private final DriverCategory category; + private final Set libraries; + + DatabaseType(@NonNull DriverCategory category, @NonNull Library... libraries) { + this.category = Objects.requireNonNull(category); + this.libraries = Set.of(Objects.requireNonNull(libraries)); + } + + static Library databaseModule() { + return library( + "database", + Repository.OPEN_COLLAB, + "org.geysermc.floodgate", + "database", + Constants.VERSION + ); + } + + static Library library( + String id, + Repository repo, + String groupId, + String artifactId, + String version + ) { + return Library.builder() + .id(id) + .repository(repo) + .groupId(groupId) + .artifactId(artifactId) + .version(version) + .build(); + } + + public static DatabaseType byId(String id) { + id = id.toUpperCase(Locale.ROOT); + for (DatabaseType value : VALUES) { + if (value.name().equals(id)) { + return value; + } + } + return null; + } + + public DriverCategory category() { + return category; + } + + public Set libraries() { + return libraries; + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/database/config/DatabaseConfig.java b/core/src/main/java/org/geysermc/floodgate/core/database/loader/DriverCategory.java similarity index 88% rename from core/src/main/java/org/geysermc/floodgate/core/database/config/DatabaseConfig.java rename to core/src/main/java/org/geysermc/floodgate/core/database/loader/DriverCategory.java index 4a7cf18f..326667bf 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/database/config/DatabaseConfig.java +++ b/core/src/main/java/org/geysermc/floodgate/core/database/loader/DriverCategory.java @@ -23,10 +23,8 @@ * @link https://github.com/GeyserMC/Floodgate */ -package org.geysermc.floodgate.core.database.config; +package org.geysermc.floodgate.core.database.loader; -/** - * Base class for every database related configuration. - */ -public interface DatabaseConfig { +public enum DriverCategory { + HIBERNATE } diff --git a/core/src/main/java/org/geysermc/floodgate/core/http/link/LinkedPlayer.java b/core/src/main/java/org/geysermc/floodgate/core/http/link/LinkedPlayer.java new file mode 100644 index 00000000..cd5cdb27 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/http/link/LinkedPlayer.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.core.http.link; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.UUID; +import org.geysermc.floodgate.core.util.Utils; + +public record LinkedPlayer( + @JsonProperty("bedrock_id") + long xuid, + String gamertag, + @JsonProperty("java_id") + UUID uuid, + @JsonProperty("java_name") + String username +) { + public boolean isLinked() { + // everything will be null when the player is not linked, since we return an empty object. + // but it's sufficient to check if one of them is null + return uuid != null; + } + + public org.geysermc.floodgate.core.database.entity.LinkedPlayer toDatabase() { + if (!isLinked()) { + return null; + } + + return new org.geysermc.floodgate.core.database.entity.LinkedPlayer() + .bedrockId(Utils.getJavaUuid(xuid)) + .javaUniqueId(uuid) + .javaUsername(username); + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/http/xbox/GetGamertagResult.java b/core/src/main/java/org/geysermc/floodgate/core/http/xbox/GetGamertagResult.java new file mode 100644 index 00000000..84bd4797 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/http/xbox/GetGamertagResult.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.core.http.xbox; + +public record GetGamertagResult(String gamertag) { +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/http/xbox/GetXuidResult.java b/core/src/main/java/org/geysermc/floodgate/core/http/xbox/GetXuidResult.java new file mode 100644 index 00000000..f9670f53 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/http/xbox/GetXuidResult.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.core.http.xbox; + +public record GetXuidResult(Long xuid) { +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/http/xbox/XboxClient.java b/core/src/main/java/org/geysermc/floodgate/core/http/xbox/XboxClient.java new file mode 100644 index 00000000..07c79fa8 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/http/xbox/XboxClient.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.core.http.xbox; + +import static io.micronaut.http.HttpHeaders.ACCEPT; +import static io.micronaut.http.HttpHeaders.USER_AGENT; + +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.Header; +import io.micronaut.http.client.annotation.Client; +import java.util.concurrent.CompletableFuture; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +@Client("${http.baseUrl}/v2/xbox") +@Header(name = USER_AGENT, value = "${http.userAgent}") +@Header(name = ACCEPT, value = "application/json") +public interface XboxClient { + @Get("/xuid/{gamertag}") + CompletableFuture xuidByGamertag( + @NotNull @Size(min = 1, max = 16) String gamertag + ); + + @Get("/gamertag/{xuid}") + CompletableFuture gamertagByXuid(long xuid); +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/link/CommonPlayerLink.java b/core/src/main/java/org/geysermc/floodgate/core/link/CommonPlayerLink.java index 51f2b19f..48d10dff 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/link/CommonPlayerLink.java +++ b/core/src/main/java/org/geysermc/floodgate/core/link/CommonPlayerLink.java @@ -25,30 +25,20 @@ package org.geysermc.floodgate.core.link; -import io.micronaut.context.ApplicationContext; -import io.micronaut.context.annotation.Requires; -import jakarta.annotation.PreDestroy; import jakarta.inject.Inject; -import java.util.Random; import java.util.UUID; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.CompletableFuture; import lombok.AccessLevel; import lombok.Getter; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.api.FloodgateApi; -import org.geysermc.floodgate.api.link.LinkRequest; -import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.core.config.FloodgateConfig; -import org.geysermc.floodgate.core.database.config.DatabaseConfig; -import org.geysermc.floodgate.core.database.config.DatabaseConfigLoader; - -//@Listener -@Requires(env = "no") -public abstract class CommonPlayerLink implements PlayerLink { - @Getter(AccessLevel.PROTECTED) - private final ExecutorService executorService = Executors.newCachedThreadPool(); +import org.geysermc.floodgate.core.database.entity.LinkRequest; +import org.geysermc.floodgate.core.database.entity.LinkedPlayer; +public abstract class CommonPlayerLink { @Getter private boolean enabled; @Getter private boolean allowLinking; @Getter private long verifyLinkTimeout; @@ -61,9 +51,6 @@ public abstract class CommonPlayerLink implements PlayerLink { @Getter(AccessLevel.PROTECTED) FloodgateApi api; - @Inject - ApplicationContext context; - @Inject void init(FloodgateConfig config) { FloodgateConfig.PlayerLinkConfig linkConfig = config.playerLink(); @@ -72,37 +59,45 @@ public abstract class CommonPlayerLink implements PlayerLink { verifyLinkTimeout = linkConfig.linkCodeTimeout(); } - public String createCode() { - return String.format("%04d", new Random().nextInt(10000)); - } - - public boolean isRequestedPlayer(LinkRequest request, UUID bedrockId) { - return request.isRequestedPlayer(api.getPlayer(bedrockId)); - } - /** - * Get the config present in init.json and turn it into the given config class. This method will - * automatically copy and save the default config if the config doesn't exist. + * Checks if the given FloodgatePlayer is the player requested in this LinkRequest. This method + * will check both the real bedrock username {@link FloodgatePlayer#getUsername()} and the + * edited username {@link FloodgatePlayer#getJavaUsername()} and returns true if one of the two + * matches. * - * @param configClass the class to convert the config to - * @param the config type - * @return the loaded config or null if something went wrong. - * @see DatabaseConfigLoader#loadAs(Class) + * @return true if the given player is the player requested */ - public T getConfig(Class configClass) { - // this method is not intended to be used more than once. It'll make a new instance of - // DatabaseConfigLoader and DatabaseConfig every time you run this method. - return context.getBean(DatabaseConfigLoader.class).loadAs(configClass); + public boolean isRequestedPlayer(LinkRequest request, UUID bedrockId) { + // Java starts the process, Bedrock finishes it. So player can't be null + var player = api.getPlayer(bedrockId); + return request.bedrockUsername().equals(player.getUsername()) || + request.bedrockUsername().equals(player.getJavaUsername()); } - @Override - public String getName() { - return context.get("database.name", String.class).get(); - } + public abstract CompletableFuture addLink( + @NonNull UUID javaUniqueId, + @NonNull String javaUsername, + @NonNull UUID bedrockId + ); - @Override - @PreDestroy - public void stop() { - executorService.shutdown(); + public abstract CompletableFuture fetchLink(@NonNull UUID uuid); + + public abstract CompletableFuture isLinked(@NonNull UUID uuid); + + public abstract CompletableFuture unlink(@NonNull UUID uuid); + + public abstract CompletableFuture createLinkRequest( + @NonNull UUID javaUniqueId, + @NonNull String javaUsername, + @NonNull String bedrockUsername, + @NonNull String code + ); + + public abstract CompletableFuture linkRequest(@NonNull String javaUsername); + + public abstract CompletableFuture invalidateLinkRequest(@NonNull LinkRequest request); + + public boolean isActive() { + return enabled && allowLinking; } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/link/DisabledPlayerLink.java b/core/src/main/java/org/geysermc/floodgate/core/link/DisabledPlayerLink.java index fada2a6a..e52431da 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/link/DisabledPlayerLink.java +++ b/core/src/main/java/org/geysermc/floodgate/core/link/DisabledPlayerLink.java @@ -25,76 +25,18 @@ package org.geysermc.floodgate.core.link; +import jakarta.inject.Singleton; import java.util.UUID; import java.util.concurrent.CompletableFuture; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.api.FloodgateApi; -import org.geysermc.floodgate.api.link.LinkRequestResult; -import org.geysermc.floodgate.api.link.PlayerLink; -import org.geysermc.floodgate.core.util.Utils; -import org.geysermc.floodgate.util.LinkedPlayer; +import org.geysermc.floodgate.core.database.entity.LinkRequest; +import org.geysermc.floodgate.core.database.entity.LinkedPlayer; /** - * Simple class used when PlayerLinking is disabled. This class has been made because Floodgate - * doesn't have a default PlayerLink implementation anymore and {@link FloodgateApi#getPlayerLink()} - * returning null} is also not an option. + * Simple class used when PlayerLinking is disabled */ -final class DisabledPlayerLink implements PlayerLink { - @Override - public void load() { - } - - @Override - @NonNull - public CompletableFuture getLinkedPlayer(@NonNull UUID bedrockId) { - return failedFuture(); - } - - @Override - @NonNull - public CompletableFuture isLinkedPlayer(@NonNull UUID bedrockId) { - return failedFuture(); - } - - @Override - @NonNull - public CompletableFuture linkPlayer( - @NonNull UUID bedrockId, - @NonNull UUID javaId, - @NonNull String username) { - return failedFuture(); - } - - @Override - @NonNull - public CompletableFuture unlinkPlayer(@NonNull UUID javaId) { - return failedFuture(); - } - - @Override - @NonNull - public CompletableFuture createLinkRequest( - @NonNull UUID javaId, - @NonNull String javaUsername, - @NonNull String bedrockUsername) { - return failedFuture(); - } - - @Override - @NonNull - public CompletableFuture verifyLinkRequest( - @NonNull UUID bedrockId, - @NonNull String javaUsername, - @NonNull String bedrockUsername, - @NonNull String code) { - return failedFuture(); - } - - @Override - public String getName() { - return null; - } - +@Singleton +final class DisabledPlayerLink extends CommonPlayerLink { @Override public boolean isEnabled() { return false; @@ -111,11 +53,52 @@ final class DisabledPlayerLink implements PlayerLink { } @Override - public void stop() { + public CompletableFuture addLink( + @NonNull UUID javaUniqueId, + @NonNull String javaUsername, + @NonNull UUID bedrockId + ) { + return failedFuture(); + } + + @Override + public CompletableFuture fetchLink(@NonNull UUID uuid) { + return failedFuture(); + } + + @Override + public CompletableFuture isLinked(@NonNull UUID uuid) { + return failedFuture(); + } + + @Override + public CompletableFuture unlink(@NonNull UUID uuid) { + return failedFuture(); + } + + @Override + public CompletableFuture createLinkRequest( + @NonNull UUID javaUniqueId, + @NonNull String javaUsername, + @NonNull String bedrockUsername, + @NonNull String code + ) { + return failedFuture(); + } + + @Override + public CompletableFuture linkRequest(@NonNull String javaUsername) { + return failedFuture(); + } + + @Override + public CompletableFuture invalidateLinkRequest(@NonNull LinkRequest request) { + return failedFuture(); } private CompletableFuture failedFuture() { - return Utils.failedFuture(new IllegalStateException( - "Cannot perform this action when PlayerLinking is disabled")); + return CompletableFuture.failedFuture(new IllegalStateException( + "Cannot perform this action when PlayerLinking is disabled" + )); } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/link/GlobalPlayerLinking.java b/core/src/main/java/org/geysermc/floodgate/core/link/GlobalPlayerLinking.java index 83fbe597..6131ada3 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/link/GlobalPlayerLinking.java +++ b/core/src/main/java/org/geysermc/floodgate/core/link/GlobalPlayerLinking.java @@ -25,66 +25,43 @@ package org.geysermc.floodgate.core.link; -import static org.geysermc.floodgate.core.util.Constants.GET_BEDROCK_LINK; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; +import io.micronaut.context.BeanProvider; +import io.micronaut.context.annotation.Primary; +import io.micronaut.context.annotation.Requires; import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.inject.Singleton; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; import lombok.Getter; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.api.link.LinkRequestResult; -import org.geysermc.floodgate.api.link.PlayerLink; -import org.geysermc.floodgate.core.util.HttpClient; -import org.geysermc.floodgate.core.util.HttpClient.DefaultHttpResponse; -import org.geysermc.floodgate.core.util.Utils; -import org.geysermc.floodgate.util.LinkedPlayer; +import org.geysermc.floodgate.core.database.entity.LinkRequest; +import org.geysermc.floodgate.core.database.entity.LinkedPlayer; +import org.geysermc.floodgate.core.http.link.GlobalLinkClient; +@Requires(property = "config.link.enableGlobalLinking", value = "true") +@Primary +@Singleton @Getter public class GlobalPlayerLinking extends CommonPlayerLink { - @Inject HttpClient httpClient; + @Inject GlobalLinkClient linkClient; - private PlayerLink databaseImpl; + private CommonPlayerLink database; - public void setDatabaseImpl(PlayerLink databaseImpl) { - if (this.databaseImpl == null) { - this.databaseImpl = databaseImpl; - } - } - - @Override - public void load() { - if (databaseImpl != null) { - databaseImpl.load(); - } - } - - @Override - public String getName() { - if (databaseImpl != null) { - return databaseImpl.getName(); - } - // Global Linking is integrated - return null; - } - - @Override - public void stop() { - super.stop(); - if (databaseImpl != null) { - databaseImpl.stop(); - } + @Inject + void init(@Named("localLinking") BeanProvider databaseImpl) { + this.database = databaseImpl.orElse(null); } @Override @NonNull - public CompletableFuture getLinkedPlayer(@NonNull UUID bedrockId) { - if (databaseImpl == null) { + public CompletableFuture fetchLink(@NonNull UUID bedrockId) { + if (database == null) { return getLinkedPlayer0(bedrockId); } - return databaseImpl.getLinkedPlayer(bedrockId).thenComposeAsync(result -> { + return database.fetchLink(bedrockId).thenComposeAsync(result -> { if (result != null) { return CompletableFuture.completedFuture(result); } @@ -94,47 +71,18 @@ public class GlobalPlayerLinking extends CommonPlayerLink { @NonNull private CompletableFuture getLinkedPlayer0(@NonNull UUID bedrockId) { - return CompletableFuture.supplyAsync( - () -> { - DefaultHttpResponse response = - httpClient.get(GET_BEDROCK_LINK + bedrockId.getLeastSignificantBits()); - - // either the global api is down or it failed to return link - if (!response.isCodeOk()) { - if (response.getResponse() != null) { - getLogger().error( - "Failed to request link for {}: {}", - bedrockId.getLeastSignificantBits(), - response.getResponse().get("message").getAsString() - ); - } - return null; - } - - JsonObject data = response.getResponse(); - - JsonElement javaName = data.get("java_name"); - // javaName will be null when the player isn't linked - if (javaName == null) { - return null; - } - - return LinkedPlayer.of( - javaName.getAsString(), - UUID.fromString(data.get("java_id").getAsString()), - Utils.getJavaUuid(data.get("bedrock_id").getAsLong())); - }, - getExecutorService()); + return linkClient.bedrockLink(bedrockId.getLeastSignificantBits()) + .thenApply(org.geysermc.floodgate.core.http.link.LinkedPlayer::toDatabase); } @Override @NonNull - public CompletableFuture isLinkedPlayer(@NonNull UUID bedrockId) { - if (databaseImpl == null) { + public CompletableFuture isLinked(@NonNull UUID bedrockId) { + if (database == null) { return isLinkedPlayer0(bedrockId); } - return databaseImpl.isLinkedPlayer(bedrockId).thenComposeAsync(result -> { + return database.isLinked(bedrockId).thenComposeAsync(result -> { if (result != null) { return CompletableFuture.completedFuture(result); } @@ -144,23 +92,7 @@ public class GlobalPlayerLinking extends CommonPlayerLink { @NonNull private CompletableFuture isLinkedPlayer0(@NonNull UUID bedrockId) { - return CompletableFuture.supplyAsync( - () -> { - DefaultHttpResponse response = - httpClient.get(GET_BEDROCK_LINK + bedrockId.getLeastSignificantBits()); - - if (!response.isCodeOk()) { - getLogger().error( - "Failed to request link for {}: {}", - bedrockId.getLeastSignificantBits(), - response.getResponse().get("message").getAsString()); - return false; - } - - // no link if data is empty, otherwise the player is linked - return response.getResponse().entrySet().size() != 0; - }, - getExecutorService()); + return getLinkedPlayer0(bedrockId).thenApply(Objects::nonNull); } // player linking and unlinking now goes through the global player linking server. @@ -168,57 +100,64 @@ public class GlobalPlayerLinking extends CommonPlayerLink { @Override @NonNull - public CompletableFuture linkPlayer( - @NonNull UUID bedrockId, - @NonNull UUID javaId, - @NonNull String username) { - if (databaseImpl != null) { - return databaseImpl.linkPlayer(bedrockId, javaId, username); - } - return failedFuture(); - } - - @Override - @NonNull - public CompletableFuture unlinkPlayer(@NonNull UUID javaId) { - if (databaseImpl != null) { - return databaseImpl.unlinkPlayer(javaId); - } - return failedFuture(); - } - - @Override - @NonNull - public CompletableFuture createLinkRequest( - @NonNull UUID javaId, + public CompletableFuture addLink( + @NonNull UUID javaUniqueId, @NonNull String javaUsername, - @NonNull String bedrockUsername) { - if (databaseImpl != null) { - return databaseImpl.createLinkRequest(javaId, javaUsername, bedrockUsername); + @NonNull UUID bedrockId + ) { + if (database != null) { + return database.addLink(javaUniqueId, javaUsername, bedrockId); } return failedFuture(); } @Override @NonNull - public CompletableFuture verifyLinkRequest( - @NonNull UUID bedrockId, + public CompletableFuture unlink(@NonNull UUID javaUniqueId) { + if (database != null) { + return database.unlink(javaUniqueId); + } + return failedFuture(); + } + + @Override + @NonNull + public CompletableFuture createLinkRequest( + @NonNull UUID javaUniqueId, @NonNull String javaUsername, @NonNull String bedrockUsername, - @NonNull String code) { - if (databaseImpl != null) { - return databaseImpl.verifyLinkRequest(bedrockId, javaUsername, bedrockUsername, code); + @NonNull String code + ) { + if (database != null) { + return database.createLinkRequest(javaUniqueId, javaUsername, bedrockUsername, code); } return failedFuture(); } @Override - public boolean isEnabledAndAllowed() { - return databaseImpl != null && databaseImpl.isEnabledAndAllowed(); + public CompletableFuture linkRequest(@NonNull String javaUsername) { + if (database != null) { + return database.linkRequest(javaUsername); + } + return failedFuture(); + } + + @Override + public CompletableFuture invalidateLinkRequest(@NonNull LinkRequest request) { + if (database != null) { + return database.invalidateLinkRequest(request); + } + return failedFuture(); + } + + @Override + public boolean isActive() { + return database != null && database.isActive(); } private CompletableFuture failedFuture() { - return Utils.failedFuture(new IllegalStateException( - "Cannot perform this action when Global Linking is enabled")); + return CompletableFuture.failedFuture(new IllegalStateException( + "Cannot perform this action when Global Linking is enabled" + )); } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/link/LinkVerificationException.java b/core/src/main/java/org/geysermc/floodgate/core/link/LinkVerificationException.java new file mode 100644 index 00000000..d1ed6e5e --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/link/LinkVerificationException.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.core.link; + +import org.geysermc.floodgate.core.command.LinkAccountCommand.Message; + +public class LinkVerificationException extends RuntimeException { + public static final LinkVerificationException NO_LINK_REQUESTED = + new LinkVerificationException(Message.NO_LINK_REQUESTED); + public static final LinkVerificationException INVALID_CODE = + new LinkVerificationException(Message.INVALID_CODE); + public static final LinkVerificationException LINK_REQUEST_EXPIRED = + new LinkVerificationException(Message.LINK_REQUEST_EXPIRED); + + private final Message message; + + private LinkVerificationException(Message message) { + super(null, null, true, false); + this.message = message; + } + + public Message message() { + return message; + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/link/PlayerLinkHibernate.java b/core/src/main/java/org/geysermc/floodgate/core/link/PlayerLinkHibernate.java new file mode 100644 index 00000000..a6fcc077 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/link/PlayerLinkHibernate.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.core.link; + +import io.micronaut.context.annotation.Replaces; +import io.micronaut.context.annotation.Requires; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.inject.Singleton; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.core.database.PendingLinkRepository; +import org.geysermc.floodgate.core.database.PlayerLinkRepository; +import org.geysermc.floodgate.core.database.entity.LinkRequest; +import org.geysermc.floodgate.core.database.entity.LinkedPlayer; + +@Requires(property = "config.playerLink.enableOwnLinking", value = "true") +@Replaces(DisabledPlayerLink.class) +@Named("localLinking") +@Singleton +public class PlayerLinkHibernate extends CommonPlayerLink { + @Inject PlayerLinkRepository linkRepository; + @Inject PendingLinkRepository pendingLinkRepository; + + @Override + public CompletableFuture addLink( + @NonNull UUID javaUniqueId, + @NonNull String javaUsername, + @NonNull UUID bedrockId + ) { + return linkRepository.save( + new LinkedPlayer() + .javaUniqueId(javaUniqueId) + .javaUsername(javaUsername) + .bedrockId(bedrockId) + ); + } + + @Override + public CompletableFuture fetchLink(@NonNull UUID uuid) { + return CompletableFuture.supplyAsync(() -> + linkRepository.findByBedrockIdOrJavaUniqueId(uuid, uuid).orElse(null) + ); + } + + @Override + public CompletableFuture isLinked(@NonNull UUID uuid) { + return CompletableFuture.supplyAsync(() -> + linkRepository.existsByBedrockIdOrJavaUniqueId(uuid, uuid) + ); + } + + @Override + public CompletableFuture unlink(@NonNull UUID uuid) { + return CompletableFuture.supplyAsync(() -> { + linkRepository.deleteByBedrockIdOrJavaUniqueId(uuid, uuid); + return null; + }); + } + + @Override + public CompletableFuture createLinkRequest( + @NonNull UUID javaUniqueId, + @NonNull String javaUsername, + @NonNull String bedrockUsername, + @NonNull String code + ) { + return pendingLinkRepository.save( + new LinkRequest() + .javaUniqueId(javaUniqueId) + .javaUsername(javaUsername) + .bedrockUsername(bedrockUsername) + .linkCode(code) + ); + } + + @Override + public CompletableFuture linkRequest(@NonNull String javaUsername) { + return pendingLinkRepository.findByJavaUsername(javaUsername); + } + + @Override + public CompletableFuture invalidateLinkRequest(@NonNull LinkRequest request) { + return pendingLinkRepository.delete(request); + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/link/PlayerLinkHolder.java b/core/src/main/java/org/geysermc/floodgate/core/link/PlayerLinkHolder.java deleted file mode 100644 index 15a1e46f..00000000 --- a/core/src/main/java/org/geysermc/floodgate/core/link/PlayerLinkHolder.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Floodgate - */ - -package org.geysermc.floodgate.core.link; - -import static java.util.Objects.requireNonNull; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import io.micronaut.context.ApplicationContext; -import io.micronaut.context.annotation.Bean; -import io.micronaut.context.annotation.Factory; -import jakarta.annotation.PreDestroy; -import jakarta.inject.Inject; -import jakarta.inject.Named; -import jakarta.inject.Singleton; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.geysermc.event.Listener; -import org.geysermc.floodgate.api.link.PlayerLink; -import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.core.config.FloodgateConfig; -import org.geysermc.floodgate.core.config.FloodgateConfig.PlayerLinkConfig; -import org.geysermc.floodgate.core.util.Constants; -import org.geysermc.floodgate.core.util.Utils; - -@Listener -@Factory -@SuppressWarnings("unchecked") -public final class PlayerLinkHolder { - private final ApplicationContext currentContext; - - private URLClassLoader classLoader; - private ApplicationContext childContext; - private PlayerLink instance; - - @Inject - PlayerLinkHolder(ApplicationContext context) { - this.currentContext = context; - } - - @Bean - @Singleton - PlayerLink load( - FloodgateConfig config, - FloodgateLogger logger, - @Named("dataDirectory") Path dataDirectory - ) { - if (instance != null) { - return instance; - } - - if (config == null) { - throw new IllegalStateException("Config cannot be null!"); - } - - PlayerLinkConfig linkConfig = config.playerLink(); - if (!linkConfig.enabled()) { - return new DisabledPlayerLink(); - } - - List files; - try (Stream list = Files.list(dataDirectory)) { - files = list - .filter(path -> Files.isRegularFile(path) && path.toString().endsWith(".jar")) - .collect(Collectors.toList()); - } catch (IOException exception) { - logger.error("Failed to list possible database implementations", exception); - return new DisabledPlayerLink(); - } - - // we can skip the rest if global linking is enabled and no database implementations has - // been found, or when global linking is enabled and own player linking is disabled. - if (linkConfig.enableGlobalLinking() && - (files.isEmpty() || !linkConfig.enableOwnLinking())) { - return currentContext.getBean(GlobalPlayerLinking.class); - } - - if (files.isEmpty()) { - logger.error("Failed to find a database implementation"); - return new DisabledPlayerLink(); - } - - Path implementationPath = files.get(0); - final String databaseName; - - // We only want to load one database implementation - if (files.size() > 1) { - boolean found = false; - databaseName = linkConfig.type(); - - String expectedName = "floodgate-" + databaseName + "-database.jar"; - for (Path path : files) { - if (expectedName.equalsIgnoreCase(path.getFileName().toString())) { - implementationPath = path; - found = true; - } - } - - if (!found) { - logger.error( - "Failed to find an implementation for type: {}", linkConfig.type() - ); - return new DisabledPlayerLink(); - } - } else { - String name = implementationPath.getFileName().toString(); - if (!Utils.isValidDatabaseName(name)) { - logger.error( - "Found database {} but the name doesn't match {}", - name, Constants.DATABASE_NAME_FORMAT - ); - return new DisabledPlayerLink(); - } - int firstSplit = name.indexOf('-') + 1; - databaseName = name.substring(firstSplit, name.indexOf('-', firstSplit)); - } - - boolean init = true; - - try { - URL pluginUrl = implementationPath.toUri().toURL(); - - // we don't have a way to close this properly since we have no stop method and we have - // to be able to load classes on the fly, but that doesn't matter anyway since Floodgate - // doesn't support reloading - classLoader = new URLClassLoader( - new URL[]{pluginUrl}, - PlayerLinkHolder.class.getClassLoader() - ); - - String mainClassName; - JsonObject dbInitConfig; - - try (InputStream linkConfigStream = classLoader.getResourceAsStream("init.json")) { - requireNonNull(linkConfigStream, "Implementation should have an init file"); - - dbInitConfig = new Gson().fromJson( - new InputStreamReader(linkConfigStream), JsonObject.class - ); - - mainClassName = dbInitConfig.get("mainClass").getAsString(); - } - - Class mainClass = - (Class) classLoader.loadClass(mainClassName); - - init = false; - -// childContext = ApplicationContext.builder() -// .singletons() -// .classLoader(classLoader) -// .packages() -// .properties(Map.of( -// "database.name", databaseName, -// "database.classloader", classLoader, -// "database.init.data", dbInitConfig -// )) -// .build(); -// -// childContext.registerBeanDefinition(RuntimeBeanDefinition.builder(null).) -// -// childContext.environment(helo -> helo.); - - instance = childContext.getBean(mainClass); - - // we use our own internal PlayerLinking when global linking is enabled - if (linkConfig.enableGlobalLinking()) { - GlobalPlayerLinking linking = childContext.getBean(GlobalPlayerLinking.class); - linking.setDatabaseImpl(instance); - linking.load(); - return linking; - } else { - instance.load(); - return instance; - } - } catch (ClassCastException exception) { - logger.error( - "The database implementation ({}) doesn't extend the PlayerLink class!", - implementationPath.getFileName().toString(), exception - ); - return new DisabledPlayerLink(); - } catch (Exception exception) { - if (init) { - logger.error("Error while initialising database jar", exception); - } else { - logger.error("Error while loading database jar", exception); - } - return new DisabledPlayerLink(); - } - } - - @PreDestroy - void close() throws Exception { - instance.stop(); - childContext.close(); - classLoader.close(); - } -} diff --git a/core/src/main/java/org/geysermc/floodgate/core/module/CommonModule.java b/core/src/main/java/org/geysermc/floodgate/core/module/CommonModule.java index 88e5fa65..c783004b 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/module/CommonModule.java +++ b/core/src/main/java/org/geysermc/floodgate/core/module/CommonModule.java @@ -26,10 +26,13 @@ package org.geysermc.floodgate.core.module; import io.micronaut.context.annotation.Bean; +import io.micronaut.context.annotation.BootstrapContextCompatible; import io.micronaut.context.annotation.Factory; import io.netty.util.AttributeKey; import jakarta.inject.Named; import jakarta.inject.Singleton; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -39,6 +42,7 @@ import java.util.concurrent.TimeUnit; import org.geysermc.event.Listener; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.core.util.Constants; +import org.geysermc.floodgate.core.util.GlobalBeanCache; import org.geysermc.floodgate.crypto.AesCipher; import org.geysermc.floodgate.crypto.AesKeyProducer; import org.geysermc.floodgate.crypto.Base64Topping; @@ -46,6 +50,7 @@ import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.crypto.KeyProducer; @Factory +@BootstrapContextCompatible @Listener public class CommonModule { @Bean(preDestroy = "shutdown") @@ -63,15 +68,26 @@ public class CommonModule { } @Bean + @BootstrapContextCompatible @Singleton - public KeyProducer keyProducer() { - return new AesKeyProducer(); + @Named("dataDirectory") + public Path dataDirectory() { + // todo open discussion asking how you can register bootstrap context beans + return Paths.get("plugins", "floodgate"); } @Bean + @BootstrapContextCompatible + @Singleton + public KeyProducer keyProducer() { + return GlobalBeanCache.cacheIfAbsent("keyProducer", AesKeyProducer::new); + } + + @Bean + @BootstrapContextCompatible @Singleton public FloodgateCipher cipher() { - return new AesCipher(new Base64Topping()); + return GlobalBeanCache.cacheIfAbsent("cipher", () -> new AesCipher(new Base64Topping())); } @Bean diff --git a/core/src/main/java/org/geysermc/floodgate/core/platform/command/CommandUtil.java b/core/src/main/java/org/geysermc/floodgate/core/platform/command/CommandUtil.java index 36245cf0..55c0eb6e 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/platform/command/CommandUtil.java +++ b/core/src/main/java/org/geysermc/floodgate/core/platform/command/CommandUtil.java @@ -192,7 +192,7 @@ public abstract class CommandUtil { * @return true if the player has been whitelisted, false if the player was already whitelisted. * Defaults to false when this platform doesn't support whitelisting. */ - public boolean whitelistPlayer(String xuid, String username) { + public boolean whitelistPlayer(long xuid, String username) { UUID uuid = Utils.getJavaUuid(xuid); return whitelistPlayer(uuid, username); } @@ -217,7 +217,7 @@ public abstract class CommandUtil { * @return true if the player has been removed from the whitelist, false if the player wasn't * whitelisted. Defaults to false when this platform doesn't support whitelisting. */ - public boolean removePlayerFromWhitelist(String xuid, String username) { + public boolean removePlayerFromWhitelist(long xuid, String username) { UUID uuid = Utils.getJavaUuid(xuid); return removePlayerFromWhitelist(uuid, username); } diff --git a/core/src/main/java/org/geysermc/floodgate/core/player/FloodgateHandshakeHandler.java b/core/src/main/java/org/geysermc/floodgate/core/player/FloodgateHandshakeHandler.java index f45f78e6..595da9cb 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/player/FloodgateHandshakeHandler.java +++ b/core/src/main/java/org/geysermc/floodgate/core/player/FloodgateHandshakeHandler.java @@ -51,6 +51,7 @@ import org.geysermc.floodgate.core.addon.data.HandshakeDataImpl; import org.geysermc.floodgate.core.addon.data.HandshakeHandlersImpl; import org.geysermc.floodgate.core.api.SimpleFloodgateApi; import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.link.CommonPlayerLink; import org.geysermc.floodgate.core.skin.SkinUploadManager; import org.geysermc.floodgate.core.util.Constants; import org.geysermc.floodgate.core.util.LanguageManager; @@ -63,6 +64,7 @@ import org.geysermc.floodgate.util.LinkedPlayer; public final class FloodgateHandshakeHandler { @Inject HandshakeHandlersImpl handshakeHandlers; @Inject SimpleFloodgateApi api; + @Inject CommonPlayerLink link; @Inject FloodgateCipher cipher; @Inject FloodgateConfig config; @Inject SkinUploadManager skinUploadManager; @@ -246,10 +248,16 @@ public final class FloodgateHandshakeHandler { } private CompletableFuture> fetchLinkedPlayer(BedrockData data) { - if (!api.getPlayerLink().isEnabled()) { + if (!link.isEnabled()) { return CompletableFuture.completedFuture(new ObjectObjectImmutablePair<>(data, null)); } - return api.getPlayerLink().getLinkedPlayer(Utils.getJavaUuid(data.getXuid())) + return link.fetchLink(Utils.getJavaUuid(data.getXuid())) + .thenApply(link -> { + if (link == null) { + return null; + } + return LinkedPlayer.of(link.javaUsername(), link.javaUniqueId(), link.bedrockId()); + }) .thenApply(link -> new ObjectObjectImmutablePair<>(data, link)) .handle((result, error) -> { if (error != null) { diff --git a/core/src/main/java/org/geysermc/floodgate/core/util/GlobalBeanCache.java b/core/src/main/java/org/geysermc/floodgate/core/util/GlobalBeanCache.java new file mode 100644 index 00000000..894533f6 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/util/GlobalBeanCache.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.core.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class GlobalBeanCache { + private static final Map cache = new HashMap<>(); + + @SuppressWarnings("unchecked") + public static T cacheIfAbsent(String id, Supplier object) { + return (T) cache.computeIfAbsent(id, $ -> object.get()); + } + + @SuppressWarnings("unchecked") + public static T get(String id) { + return (T) cache.get(id); + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/util/LanguageManager.java b/core/src/main/java/org/geysermc/floodgate/core/util/LanguageManager.java index 07f8ef6e..947ec975 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/util/LanguageManager.java +++ b/core/src/main/java/org/geysermc/floodgate/core/util/LanguageManager.java @@ -26,8 +26,6 @@ package org.geysermc.floodgate.core.util; import com.google.common.base.Joiner; -import io.micronaut.context.BeanProvider; -import jakarta.annotation.PostConstruct; import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.net.URL; @@ -47,7 +45,7 @@ import org.geysermc.floodgate.core.config.FloodgateConfig; public final class LanguageManager { private final Map localeMappings = new HashMap<>(); - @Inject BeanProvider logger; + @Inject FloodgateLogger logger; /** * The locale used in console and as a fallback @@ -72,16 +70,11 @@ public final class LanguageManager { /** * Tries to load the log's locale file once a string has been requested */ - @PostConstruct - public void init() { + @Inject + public void init(FloodgateConfig config) { if (!loadLocale("en_US")) { - logger.get().error("Failed to load the fallback language. This will likely cause errors!"); + logger.error("Failed to load the fallback language. This will likely cause errors!"); } - defaultLocale = "en_US"; - } - - public void loadConfiguredDefaultLocale(FloodgateConfig config) { - FloodgateLogger logger = this.logger.get(); defaultLocale = formatLocale(config.defaultLocale()); if (!"system".equals(defaultLocale) && isValidLanguage(defaultLocale)) { @@ -126,7 +119,7 @@ public final class LanguageManager { return true; } - logger.get().warn("Missing locale file: " + formatLocale); + logger.warn("Missing locale file: " + formatLocale); return false; } @@ -187,7 +180,7 @@ public final class LanguageManager { .getResource("/languages/texts/" + locale + ".properties"); if (languageFile == null) { - logger.get().warn(locale + " is not a supported Floodgate language."); + logger.warn(locale + " is not a supported Floodgate language."); return false; } return true; diff --git a/core/src/main/java/org/geysermc/floodgate/core/util/Metrics.java b/core/src/main/java/org/geysermc/floodgate/core/util/Metrics.java index 6008d80e..a0f6c6f7 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/util/Metrics.java +++ b/core/src/main/java/org/geysermc/floodgate/core/util/Metrics.java @@ -99,7 +99,7 @@ public final class Metrics { ); metricsBase.addCustomChart( - new SimplePie("floodgate_version", () -> Constants.VERSION) + new SimplePie("floodgate_version", () -> Constants.FULL_VERSION) ); metricsBase.addCustomChart( diff --git a/core/src/main/java/org/geysermc/floodgate/core/util/Utils.java b/core/src/main/java/org/geysermc/floodgate/core/util/Utils.java index d19be8e0..5e97338e 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/util/Utils.java +++ b/core/src/main/java/org/geysermc/floodgate/core/util/Utils.java @@ -34,15 +34,17 @@ import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; import java.util.Locale; import java.util.Properties; +import java.util.Random; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import java.util.regex.Pattern; public class Utils { private static final Pattern NON_UNIQUE_PREFIX = Pattern.compile("^\\w{0,16}$"); private static final Pattern DATABASE_NAME = Pattern.compile(Constants.DATABASE_NAME_FORMAT); + private static final Random random = new SecureRandom(); /** * This method is used in Addons.
Most addons can be removed once the player associated to @@ -99,6 +101,22 @@ public class Utils { return DATABASE_NAME.matcher(databaseName).matches(); } + public static String generateCode(int length) { + StringBuilder code = new StringBuilder(length); + for (int i = 0; i < length; i++) { + code.append(generateCodeChar()); + } + return code.toString(); + } + + public static char generateCodeChar() { + var codeChar = random.nextInt() % (10 + 26); + if (codeChar < 10) { + return (char) ('0' + codeChar); + } + return (char) ('A' + codeChar); + } + public static int readVarInt(ByteBuf buffer) { int out = 0; int count = 0; @@ -119,18 +137,4 @@ public class Utils { throwable.printStackTrace(new PrintWriter(writer)); return writer.toString(); } - - /** - * Returns a new CompletableFuture that is already completed exceptionally with the given - * exception. - * - * @param ex the exception - * @param the type of the value - * @return the exceptionally completed CompletableFuture - */ - public static CompletableFuture failedFuture(Throwable ex) { - CompletableFuture future = new CompletableFuture<>(); - future.completeExceptionally(ex); - return future; - } } diff --git a/core/src/main/resources/application.yaml b/core/src/main/resources/application.yaml new file mode 100644 index 00000000..9a9c389b --- /dev/null +++ b/core/src/main/resources/application.yaml @@ -0,0 +1,3 @@ +http: + baseUrl: https://api.geysermc.org + userAgent: GeyserMC/Floodgate \ No newline at end of file diff --git a/core/src/main/templates/org/geysermc/floodgate/core/util/Constants.java b/core/src/main/templates/org/geysermc/floodgate/core/util/Constants.java index 111e5252..fad6381a 100644 --- a/core/src/main/templates/org/geysermc/floodgate/core/util/Constants.java +++ b/core/src/main/templates/org/geysermc/floodgate/core/util/Constants.java @@ -26,7 +26,8 @@ package org.geysermc.floodgate.core.util; public final class Constants { - public static final String VERSION = "@floodgateVersion@"; + public static final String FULL_VERSION = "@fullVersion@"; + public static final String VERSION = "@version@"; public static final int BUILD_NUMBER = Integer.parseInt("@buildNumber@"); public static final String GIT_BRANCH = "@branch@"; public static final int METRICS_ID = 14649; diff --git a/database/build.gradle.kts b/database/build.gradle.kts index 865bd515..2dd6d63e 100644 --- a/database/build.gradle.kts +++ b/database/build.gradle.kts @@ -16,5 +16,5 @@ configurations.runtimeClasspath.get() dependencies { implementation("io.micronaut.data:micronaut-data-hibernate-jpa") implementation("io.micronaut.sql:micronaut-jdbc-hikari") - runtimeOnly("com.h2database:h2") + //runtimeOnly("com.h2database:h2") } diff --git a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/logger/Slf4jFloodgateLogger.java b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/logger/Slf4jFloodgateLogger.java index b9111356..d9c3aae6 100644 --- a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/logger/Slf4jFloodgateLogger.java +++ b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/logger/Slf4jFloodgateLogger.java @@ -28,13 +28,12 @@ package org.geysermc.floodgate.velocity.logger; import static org.geysermc.floodgate.core.util.MessageFormatter.format; import io.micronaut.context.BeanProvider; -import io.micronaut.runtime.event.annotation.EventListener; import jakarta.inject.Inject; import jakarta.inject.Singleton; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.Configurator; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.core.event.lifecycle.ConfigLoadedEvent; +import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.util.LanguageManager; import org.slf4j.Logger; @@ -43,9 +42,9 @@ public final class Slf4jFloodgateLogger implements FloodgateLogger { @Inject BeanProvider languageManager; @Inject Logger logger; - @EventListener - void onConfigLoaded(ConfigLoadedEvent event) { - if (event.config().debug() && !logger.isDebugEnabled()) { + @Inject + void init(FloodgateConfig config) { + if (config.debug() && !logger.isDebugEnabled()) { Configurator.setLevel(logger.getName(), Level.DEBUG); } } diff --git a/velocity/isolated/build.gradle.kts b/velocity/isolated/build.gradle.kts index a46356c6..dfeb4507 100644 --- a/velocity/isolated/build.gradle.kts +++ b/velocity/isolated/build.gradle.kts @@ -12,7 +12,7 @@ tasks { dependsOn(":velocity-base:build", configurations.runtimeClasspath) duplicatesStrategy = DuplicatesStrategy.EXCLUDE - from (configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) + from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) archiveBaseName = "floodgate-${project.name}" archiveVersion = ""