From 92297275441c1a9048a27dfee9379f53ba13780f Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sat, 2 Oct 2021 00:12:02 +0800 Subject: [PATCH] feat(multiplayer): move token storage to glboal to prevent from token leak. --- .../org/jackhuang/hmcl/setting/Config.java | 29 ------ .../jackhuang/hmcl/setting/GlobalConfig.java | 88 +++++++++++++++++-- .../hmcl/ui/multiplayer/MultiplayerPage.java | 10 +-- .../ui/multiplayer/MultiplayerPageSkin.java | 4 +- .../MultiplayerClientServerTest.java | 7 +- 5 files changed, 94 insertions(+), 44 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index ea3a87c85..44f7a58ee 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -158,12 +158,6 @@ public final class Config implements Cloneable, Observable { @SerializedName("logLines") private IntegerProperty logLines = new SimpleIntegerProperty(100); - @SerializedName("multiplayerAgreementVersion") - private IntegerProperty multiplayerAgreementVersion = new SimpleIntegerProperty(0); - - @SerializedName("multiplayerToken") - private StringProperty multiplayerToken = new SimpleStringProperty(); - @SerializedName("authlibInjectorServers") private ObservableList authlibInjectorServers = FXCollections.observableArrayList(server -> new Observable[] { server }); @@ -587,27 +581,4 @@ public final class Config implements Cloneable, Observable { return preferredLoginType; } - public int getMultiplayerAgreementVersion() { - return multiplayerAgreementVersion.get(); - } - - public IntegerProperty multiplayerAgreementVersionProperty() { - return multiplayerAgreementVersion; - } - - public void setMultiplayerAgreementVersion(int multiplayerAgreementVersion) { - this.multiplayerAgreementVersion.set(multiplayerAgreementVersion); - } - - public String getMultiplayerToken() { - return multiplayerToken.get(); - } - - public StringProperty multiplayerTokenProperty() { - return multiplayerToken; - } - - public void setMultiplayerToken(String multiplayerToken) { - this.multiplayerToken.set(multiplayerToken); - } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java index e3a099aa7..56288bf0d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java @@ -17,14 +17,14 @@ */ package org.jackhuang.hmcl.setting; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonParseException; -import com.google.gson.annotations.SerializedName; +import com.google.gson.*; +import com.google.gson.annotations.JsonAdapter; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; import javafx.collections.ObservableSet; @@ -39,8 +39,11 @@ import org.jackhuang.hmcl.util.javafx.PropertyUtils; import org.jetbrains.annotations.Nullable; import java.io.File; +import java.lang.reflect.Type; import java.net.Proxy; +import java.util.*; +@JsonAdapter(GlobalConfig.Serializer.class) public class GlobalConfig implements Cloneable, Observable { private static final Gson CONFIG_GSON = new GsonBuilder() @@ -62,12 +65,18 @@ public class GlobalConfig implements Cloneable, Observable { } GlobalConfig instance = new GlobalConfig(); PropertyUtils.copyProperties(loaded, instance); + instance.unknownFields.putAll(loaded.unknownFields); return instance; } - @SerializedName("agreementVersion") private IntegerProperty agreementVersion = new SimpleIntegerProperty(); + private StringProperty multiplayerToken = new SimpleStringProperty(); + + private IntegerProperty multiplayerAgreementVersion = new SimpleIntegerProperty(0); + + private final Map unknownFields = new HashMap<>(); + private transient ObservableHelper helper = new ObservableHelper(this); public GlobalConfig() { @@ -104,4 +113,73 @@ public class GlobalConfig implements Cloneable, Observable { public void setAgreementVersion(int agreementVersion) { this.agreementVersion.set(agreementVersion); } + + public int getMultiplayerAgreementVersion() { + return multiplayerAgreementVersion.get(); + } + + public IntegerProperty multiplayerAgreementVersionProperty() { + return multiplayerAgreementVersion; + } + + public void setMultiplayerAgreementVersion(int multiplayerAgreementVersion) { + this.multiplayerAgreementVersion.set(multiplayerAgreementVersion); + } + + public String getMultiplayerToken() { + return multiplayerToken.get(); + } + + public StringProperty multiplayerTokenProperty() { + return multiplayerToken; + } + + public void setMultiplayerToken(String multiplayerToken) { + this.multiplayerToken.set(multiplayerToken); + } + + public static class Serializer implements JsonSerializer, JsonDeserializer { + private static final Set knownFields = new HashSet<>(Arrays.asList( + "agreementVersion", + "multiplayerToken", + "multiplayerAgreementVersion" + )); + + @Override + public JsonElement serialize(GlobalConfig src, Type typeOfSrc, JsonSerializationContext context) { + if (src == null) { + return JsonNull.INSTANCE; + } + + JsonObject jsonObject = new JsonObject(); + jsonObject.add("agreementVersion", context.serialize(src.getAgreementVersion())); + jsonObject.add("multiplayerToken", context.serialize(src.getMultiplayerToken())); + jsonObject.add("multiplayerAgreementVersion", context.serialize(src.getMultiplayerAgreementVersion())); + for (Map.Entry entry : src.unknownFields.entrySet()) { + jsonObject.add(entry.getKey(), context.serialize(entry.getValue())); + } + + return jsonObject; + } + + @Override + public GlobalConfig deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (!(json instanceof JsonObject)) return null; + + JsonObject obj = (JsonObject) json; + + GlobalConfig config = new GlobalConfig(); + config.setAgreementVersion(Optional.ofNullable(obj.get("agreementVersion")).map(JsonElement::getAsInt).orElse(0)); + config.setMultiplayerToken(Optional.ofNullable(obj.get("multiplayerToken")).map(JsonElement::getAsString).orElse(null)); + config.setMultiplayerAgreementVersion(Optional.ofNullable(obj.get("multiplayerAgreementVersion")).map(JsonElement::getAsInt).orElse(0)); + + for (Map.Entry entry : obj.entrySet()) { + if (!knownFields.contains(entry.getKey())) { + config.unknownFields.put(entry.getKey(), context.deserialize(entry.getValue(), Object.class)); + } + } + + return config; + } + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java index c3974193f..2f0f3c7ff 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java @@ -44,7 +44,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.logging.Level; -import static org.jackhuang.hmcl.setting.ConfigHolder.config; +import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -139,7 +139,7 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware } private void checkAgreement(Runnable runnable) { - if (config().getMultiplayerAgreementVersion() < MultiplayerManager.CATO_AGREEMENT_VERSION) { + if (globalConfig().getMultiplayerAgreementVersion() < MultiplayerManager.CATO_AGREEMENT_VERSION) { JFXDialogLayout agreementPane = new JFXDialogLayout(); agreementPane.setHeading(new Label(i18n("launcher.agreement"))); agreementPane.setBody(new Label(i18n("multiplayer.agreement.prompt"))); @@ -148,7 +148,7 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware JFXButton yesButton = new JFXButton(i18n("launcher.agreement.accept")); yesButton.getStyleClass().add("dialog-accept"); yesButton.setOnAction(e -> { - config().setMultiplayerAgreementVersion(MultiplayerManager.CATO_AGREEMENT_VERSION); + globalConfig().setMultiplayerAgreementVersion(MultiplayerManager.CATO_AGREEMENT_VERSION); runnable.run(); agreementPane.fireEvent(new DialogCloseEvent()); }); @@ -209,7 +209,7 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware Controllers.dialog(new CreateMultiplayerRoomDialog((result, resolve, reject) -> { int gamePort = result.getAd(); try { - MultiplayerManager.CatoSession session = MultiplayerManager.createSession(config().getMultiplayerToken(), result.getMotd(), gamePort); + MultiplayerManager.CatoSession session = MultiplayerManager.createSession(globalConfig().getMultiplayerToken(), result.getMotd(), gamePort); session.getServer().setOnClientAdding((client, resolveClient, rejectClient) -> { runInFX(() -> { Controllers.confirm(i18n("multiplayer.session.create.join.prompt", client.getUsername()), i18n("multiplayer.session.create.join"), MessageDialogPane.MessageType.INFO, @@ -264,7 +264,7 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware } try { - MultiplayerManager.joinSession(config().getMultiplayerToken(), invitation.getVersion(), invitation.getSessionName(), invitation.getId(), invitation.getChannelPort(), localPort) + MultiplayerManager.joinSession(globalConfig().getMultiplayerToken(), invitation.getVersion(), invitation.getSessionName(), invitation.getId(), invitation.getChannelPort(), localPort) .thenAcceptAsync(session -> { initCatoSession(session); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java index 4592d4245..67cf39f82 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java @@ -44,7 +44,7 @@ import org.jackhuang.hmcl.ui.versions.Versions; import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.javafx.MappedObservableList; -import static org.jackhuang.hmcl.setting.ConfigHolder.config; +import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig; import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -256,7 +256,7 @@ public class MultiplayerPageSkin extends SkinBase { gridPane.setHgap(16); JFXTextField tokenField = new JFXTextField(); - tokenField.textProperty().bindBidirectional(config().multiplayerTokenProperty()); + tokenField.textProperty().bindBidirectional(globalConfig().multiplayerTokenProperty()); tokenField.setPromptText(i18n("multiplayer.session.create.token.prompt")); JFXHyperlink applyLink = new JFXHyperlink(i18n("multiplayer.session.create.token.apply")); diff --git a/HMCL/src/test/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClientServerTest.java b/HMCL/src/test/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClientServerTest.java index e1d4022f5..e4b602ddf 100644 --- a/HMCL/src/test/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClientServerTest.java +++ b/HMCL/src/test/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClientServerTest.java @@ -18,13 +18,13 @@ package org.jackhuang.hmcl.ui.multiplayer; import org.jackhuang.hmcl.util.Logging; -import org.junit.Ignore; +import org.junit.Assert; import org.junit.Test; public class MultiplayerClientServerTest { @Test - @Ignore +// @Ignore public void startServer() throws Exception { Logging.initForTest(); MultiplayerServer server = new MultiplayerServer(1000); @@ -33,7 +33,8 @@ public class MultiplayerClientServerTest { MultiplayerClient client = new MultiplayerClient("username", 44444); client.start(); - server.onKeepAlive().register(event -> { + server.onClientAdded().register(event -> { + Assert.assertEquals("username", event.getUsername()); client.interrupt(); server.interrupt(); });