From 9adb18681ab2611e5b7c8d655808a4ba84ec1ea1 Mon Sep 17 00:00:00 2001 From: Bixilon Date: Tue, 22 Dec 2020 23:01:37 +0100 Subject: [PATCH] Improvements, WIP microsoft accounts (1): multiple login endpoints --- .../java/de/bixilon/minosoft/Minosoft.java | 33 ++-- .../minosoft/config/Configuration.java | 38 +++-- .../java/de/bixilon/minosoft/data/Player.java | 10 +- .../minosoft/data/accounts/Account.java | 68 ++++++++ .../minosoft/data/accounts/MojangAccount.java | 138 ++++++++++++++++ .../data/accounts/OfflineAccount.java | 71 ++++++++ .../minosoft/data/assets/AssetsManager.java | 4 +- .../bixilon/minosoft/data/locale/Strings.java | 1 - .../minosoft/gui/main/AccountListCell.java | 30 ++-- .../minosoft/gui/main/AccountWindow.java | 5 +- .../bixilon/minosoft/gui/main/Launcher.java | 2 +- .../bixilon/minosoft/gui/main/MainWindow.java | 22 ++- .../minosoft/gui/main/ServerListCell.java | 4 +- .../minosoft/gui/main/SessionListCell.java | 2 +- .../main/dialogs/MojangLoginController.java | 52 +++--- .../minosoft/protocol/network/Network.java | 4 +- .../network/socket/BlockingSocketNetwork.java | 1 - .../login/PacketEncryptionRequest.java | 10 +- .../java/de/bixilon/minosoft/util/HTTP.java | 26 +-- .../util/mojang/api/MojangAccount.java | 156 ------------------ .../util/mojang/api/MojangAuthentication.java | 49 ++++-- .../util/mojang/api/MojangBlockedServers.java | 51 ------ .../exceptions/AuthenticationException.java | 21 +++ .../MojangJoinServerErrorException.java | 24 +++ .../NoNetworkConnectionException.java} | 26 +-- src/main/resources/assets/locale/de_DE.json | 1 - src/main/resources/assets/locale/en_US.json | 1 - src/main/resources/layout/cells/account.fxml | 8 +- 28 files changed, 490 insertions(+), 368 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/data/accounts/Account.java create mode 100644 src/main/java/de/bixilon/minosoft/data/accounts/MojangAccount.java create mode 100644 src/main/java/de/bixilon/minosoft/data/accounts/OfflineAccount.java delete mode 100644 src/main/java/de/bixilon/minosoft/util/mojang/api/MojangAccount.java delete mode 100644 src/main/java/de/bixilon/minosoft/util/mojang/api/MojangBlockedServers.java create mode 100644 src/main/java/de/bixilon/minosoft/util/mojang/api/exceptions/AuthenticationException.java create mode 100644 src/main/java/de/bixilon/minosoft/util/mojang/api/exceptions/MojangJoinServerErrorException.java rename src/main/java/de/bixilon/minosoft/util/mojang/api/{MojangAccountAuthenticationAttempt.java => exceptions/NoNetworkConnectionException.java} (56%) diff --git a/src/main/java/de/bixilon/minosoft/Minosoft.java b/src/main/java/de/bixilon/minosoft/Minosoft.java index 95b6e085c..aaac11079 100644 --- a/src/main/java/de/bixilon/minosoft/Minosoft.java +++ b/src/main/java/de/bixilon/minosoft/Minosoft.java @@ -18,6 +18,7 @@ import com.jfoenix.controls.JFXDialogLayout; import de.bixilon.minosoft.config.Configuration; import de.bixilon.minosoft.config.ConfigurationPaths; import de.bixilon.minosoft.config.StaticConfiguration; +import de.bixilon.minosoft.data.accounts.Account; import de.bixilon.minosoft.data.assets.AssetsManager; import de.bixilon.minosoft.data.locale.LocaleManager; import de.bixilon.minosoft.data.locale.minecraft.MinecraftLocaleManager; @@ -35,7 +36,6 @@ import de.bixilon.minosoft.protocol.protocol.LANServerListener; import de.bixilon.minosoft.util.CountUpAndDownLatch; import de.bixilon.minosoft.util.MinosoftCommandLineArguments; import de.bixilon.minosoft.util.Util; -import de.bixilon.minosoft.util.mojang.api.MojangAccount; import de.bixilon.minosoft.util.task.AsyncTaskWorker; import de.bixilon.minosoft.util.task.Task; import de.bixilon.minosoft.util.task.TaskImportance; @@ -51,7 +51,6 @@ import java.util.UUID; public final class Minosoft { public static final HashSet EVENT_MANAGERS = new HashSet<>(); private static final CountUpAndDownLatch START_STATUS_LATCH = new CountUpAndDownLatch(1); - public static MojangAccount selectedAccount; public static Configuration config; public static void main(String[] args) { @@ -136,7 +135,7 @@ public final class Minosoft { taskWorker.addTask(new Task(progress -> { Log.debug("Refreshing account token..."); checkClientToken(); - selectAccount(config.getAccountList().get(config.getString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED))); + selectAccount(config.getSccounts().get(config.getString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED))); }, "Token refresh", "Refresh selected account token", Priorities.LOW, TaskImportance.OPTIONAL, "Configuration")); taskWorker.addTask(new Task(progress -> { @@ -191,37 +190,31 @@ public final class Minosoft { } } - public static void selectAccount(MojangAccount account) { + public static void selectAccount(Account account) { if (account == null) { - selectedAccount = null; config.putString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED, ""); config.saveToFile(); return; } - if (account.needsRefresh()) { - MojangAccount.RefreshStates refreshState = account.refreshToken(); - if (refreshState == MojangAccount.RefreshStates.ERROR) { - account.delete(); - AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.getItems().remove(account); - selectedAccount = null; - return; - } + if (!account.select()) { + account.logout(); + AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.getItems().remove(account); + config.removeAccount(account); + config.saveToFile(); + return; } - config.putString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED, account.getUserId()); + config.putAccount(account); + config.selectAccount(account); if (Launcher.getMainWindow() != null) { - Launcher.getMainWindow().selectAccount(selectedAccount); + Launcher.getMainWindow().selectAccount(account); } - account.saveToConfig(); - selectedAccount = account; + config.saveToFile(); } public static Configuration getConfig() { return config; } - public static MojangAccount getSelectedAccount() { - return selectedAccount; - } /** * Waits until all critical components are started diff --git a/src/main/java/de/bixilon/minosoft/config/Configuration.java b/src/main/java/de/bixilon/minosoft/config/Configuration.java index 4e4a7d62e..17eea5b5d 100644 --- a/src/main/java/de/bixilon/minosoft/config/Configuration.java +++ b/src/main/java/de/bixilon/minosoft/config/Configuration.java @@ -15,10 +15,12 @@ package de.bixilon.minosoft.config; import com.google.common.collect.HashBiMap; import com.google.gson.*; +import de.bixilon.minosoft.data.accounts.Account; +import de.bixilon.minosoft.data.accounts.MojangAccount; +import de.bixilon.minosoft.data.accounts.OfflineAccount; import de.bixilon.minosoft.gui.main.Server; import de.bixilon.minosoft.logging.Log; import de.bixilon.minosoft.util.Util; -import de.bixilon.minosoft.util.mojang.api.MojangAccount; import java.io.*; import java.nio.file.Files; @@ -30,7 +32,7 @@ public class Configuration { public static final int LATEST_CONFIG_VERSION = 1; private static final JsonObject DEFAULT_CONFIGURATION = JsonParser.parseReader(new InputStreamReader(Configuration.class.getResourceAsStream("/config/" + StaticConfiguration.CONFIG_FILENAME))).getAsJsonObject(); private final HashMap config = new HashMap<>(); - private final HashBiMap accountList = HashBiMap.create(); + private final HashBiMap accountList = HashBiMap.create(); private final HashBiMap serverList = HashBiMap.create(); private final Object lock = new Object(); @@ -71,8 +73,13 @@ public class Configuration { // accounts for (Map.Entry entry : json.getAsJsonObject("accounts").getAsJsonObject("entries").entrySet()) { - MojangAccount account = MojangAccount.deserialize(entry.getValue().getAsJsonObject()); - this.accountList.put(account.getUserId(), account); + JsonObject data = entry.getValue().getAsJsonObject(); + Account account = switch (data.get("type").getAsString()) { + case "mojang" -> MojangAccount.deserialize(data); + case "offline" -> OfflineAccount.deserialize(data); + default -> throw new IllegalStateException("Unexpected value: " + data.get("type").getAsString()); + }; + this.accountList.put(account.getId(), account); } final File finalFile = file; @@ -106,7 +113,7 @@ public class Configuration { // servers JsonObject serversEntriesJson = jsonObject.getAsJsonObject("accounts").getAsJsonObject("entries"); - for (Map.Entry entry : this.accountList.entrySet()) { + for (Map.Entry entry : this.accountList.entrySet()) { serversEntriesJson.add(entry.getKey(), entry.getValue().serialize()); } @@ -159,8 +166,8 @@ public class Configuration { this.config.put(path, value); } - public void putMojangAccount(MojangAccount account) { - this.accountList.put(account.getUserId(), account); + public void putAccount(Account account) { + this.accountList.put(account.getId(), account); } public void putServer(Server server) { @@ -177,15 +184,26 @@ public class Configuration { } } - public void removeAccount(MojangAccount account) { - this.accountList.remove(account.getUserId()); + public Account getSelectedAccount() { + return this.accountList.get(getString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED)); + } + + public void selectAccount(Account account) { + putString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED, account.getId()); + } + + public void removeAccount(Account account) { + this.accountList.remove(account.getId()); + if (getString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED).equals(account.getId())) { + putString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED, ""); + } } public HashBiMap getServerList() { return this.serverList; } - public HashBiMap getAccountList() { + public HashBiMap getSccounts() { return this.accountList; } diff --git a/src/main/java/de/bixilon/minosoft/data/Player.java b/src/main/java/de/bixilon/minosoft/data/Player.java index 75ed6cfc8..d49bff548 100644 --- a/src/main/java/de/bixilon/minosoft/data/Player.java +++ b/src/main/java/de/bixilon/minosoft/data/Player.java @@ -13,6 +13,7 @@ package de.bixilon.minosoft.data; +import de.bixilon.minosoft.data.accounts.Account; import de.bixilon.minosoft.data.entities.entities.player.PlayerEntity; import de.bixilon.minosoft.data.inventory.Inventory; import de.bixilon.minosoft.data.inventory.InventoryProperties; @@ -23,7 +24,6 @@ import de.bixilon.minosoft.data.scoreboard.ScoreboardManager; import de.bixilon.minosoft.data.text.ChatComponent; import de.bixilon.minosoft.data.world.BlockPosition; import de.bixilon.minosoft.data.world.World; -import de.bixilon.minosoft.util.mojang.api.MojangAccount; import java.util.HashMap; import java.util.UUID; @@ -32,7 +32,7 @@ import static de.bixilon.minosoft.protocol.protocol.ProtocolDefinition.PLAYER_IN public class Player { public final HashMap playerList = new HashMap<>(); - final MojangAccount account; + final Account account; final ScoreboardManager scoreboardManager = new ScoreboardManager(); final World world = new World(); final HashMap inventories = new HashMap<>(); @@ -50,21 +50,21 @@ public class Player { ChatComponent tabHeader; ChatComponent tabFooter; - public Player(MojangAccount account) { + public Player(Account account) { this.account = account; // create our own inventory without any properties this.inventories.put(PLAYER_INVENTORY_ID, new Inventory(null)); } public String getPlayerName() { - return this.account.getPlayerName(); + return this.account.getUsername(); } public UUID getPlayerUUID() { return this.account.getUUID(); } - public MojangAccount getAccount() { + public Account getAccount() { return this.account; } diff --git a/src/main/java/de/bixilon/minosoft/data/accounts/Account.java b/src/main/java/de/bixilon/minosoft/data/accounts/Account.java new file mode 100644 index 000000000..5d74fec4f --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/accounts/Account.java @@ -0,0 +1,68 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.accounts; + +import com.google.gson.JsonObject; +import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException; +import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException; + +import java.util.UUID; + +public abstract class Account { + private final String username; + private final UUID uuid; + + protected Account(String username, UUID uuid) { + this.username = username; + this.uuid = uuid; + } + + public String getUsername() { + return this.username; + } + + public UUID getUUID() { + return this.uuid; + } + + public abstract JsonObject serialize(); + + public abstract void join(String serverId) throws MojangJoinServerErrorException, NoNetworkConnectionException; + + public abstract boolean select(); + + public abstract void logout(); + + public abstract String getId(); + + @Override + public int hashCode() { + return getId().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + return true; + } + if (obj == null) { + return false; + } + if (hashCode() != obj.hashCode()) { + return false; + } + Account account = (Account) obj; + return getId().equals(account.getId()); + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/accounts/MojangAccount.java b/src/main/java/de/bixilon/minosoft/data/accounts/MojangAccount.java new file mode 100644 index 000000000..feceea552 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/accounts/MojangAccount.java @@ -0,0 +1,138 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.accounts; + +import com.google.gson.JsonObject; +import de.bixilon.minosoft.Minosoft; +import de.bixilon.minosoft.util.Util; +import de.bixilon.minosoft.util.mojang.api.MojangAuthentication; +import de.bixilon.minosoft.util.mojang.api.exceptions.AuthenticationException; +import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException; +import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException; + +import java.util.UUID; + +public class MojangAccount extends Account { + private final String id; + private final String email; + private String accessToken; + private RefreshStates lastRefreshStatus; + private boolean needsRefresh = true; + + public MojangAccount(String username, JsonObject json) { + super(json.getAsJsonObject("selectedProfile").get("name").getAsString(), Util.getUUIDFromString(json.getAsJsonObject("selectedProfile").get("id").getAsString())); + this.accessToken = json.get("accessToken").getAsString(); + this.id = json.getAsJsonObject("user").get("id").getAsString(); + this.email = username; + } + + public MojangAccount(String accessToken, String id, UUID uuid, String username, String email) { + super(username, uuid); + this.accessToken = accessToken; + this.id = id; + this.email = email; + } + + public static MojangAccount deserialize(JsonObject json) { + return new MojangAccount(json.get("accessToken").getAsString(), json.get("id").getAsString(), Util.getUUIDFromString(json.get("uuid").getAsString()), json.get("username").getAsString(), json.get("email").getAsString()); + } + + public JsonObject serialize() { + JsonObject json = new JsonObject(); + json.addProperty("id", this.id); + json.addProperty("accessToken", this.accessToken); + json.addProperty("uuid", getUUID().toString()); + json.addProperty("username", getUsername()); + json.addProperty("email", this.email); + json.addProperty("type", "mojang"); + return json; + } + + public void join(String serverId) throws MojangJoinServerErrorException, NoNetworkConnectionException { + MojangAuthentication.joinServer(this, serverId); + } + + @Override + public boolean select() { + if (this.needsRefresh) { + return refreshToken() != RefreshStates.ERROR; + } + return true; + } + + @Override + public void logout() { + } + + @Override + public String getId() { + return this.id; + } + + public RefreshStates refreshToken() { + try { + this.accessToken = MojangAuthentication.refresh(this.accessToken); + this.lastRefreshStatus = RefreshStates.SUCCESSFUL; + } catch (NoNetworkConnectionException e) { + e.printStackTrace(); + this.lastRefreshStatus = RefreshStates.FAILED; + } catch (AuthenticationException e) { + e.printStackTrace(); + this.lastRefreshStatus = RefreshStates.ERROR; + } + return this.lastRefreshStatus; + } + + public String getAccessToken() { + return this.accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + this.needsRefresh = false; + } + + public String getEmail() { + return this.email; + } + + public void saveToConfig() { + Minosoft.getConfig().putAccount(this); + Minosoft.getConfig().saveToFile(); + } + + public void delete() { + Minosoft.getConfig().removeAccount(this); + Minosoft.getConfig().saveToFile(); + } + + @Override + public String toString() { + return getId(); + } + + public boolean needsRefresh() { + return this.needsRefresh; + } + + public void setNeedRefresh(boolean needsRefresh) { + this.needsRefresh = needsRefresh; + } + + public enum RefreshStates { + SUCCESSFUL, + ERROR, // account not valid anymore + FAILED // error occurred while checking -> Unknown state + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/accounts/OfflineAccount.java b/src/main/java/de/bixilon/minosoft/data/accounts/OfflineAccount.java new file mode 100644 index 000000000..2b0130186 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/accounts/OfflineAccount.java @@ -0,0 +1,71 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.accounts; + +import com.google.gson.JsonObject; +import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition; +import de.bixilon.minosoft.util.Util; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; +import java.util.regex.Matcher; + +public class OfflineAccount extends Account { + public OfflineAccount(String username) { + super(username, UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8))); + Matcher matcher = ProtocolDefinition.MINECRAFT_NAME_VALIDATOR.matcher(username); + if (!matcher.find() || !matcher.group().equals(username)) { + throw new IllegalArgumentException(String.format("%s is not valid Minecraft username", username)); + } + } + + public OfflineAccount(String username, UUID uuid) { + super(username, uuid); + Matcher matcher = ProtocolDefinition.MINECRAFT_NAME_VALIDATOR.matcher(username); + if (!matcher.find() || !matcher.group().equals(username)) { + throw new IllegalArgumentException(String.format("%s is not valid Minecraft username", username)); + } + } + + public static OfflineAccount deserialize(JsonObject json) { + return new OfflineAccount(json.get("username").getAsString(), Util.getUUIDFromString(json.get("uuid").getAsString())); + } + + @Override + public JsonObject serialize() { + JsonObject json = new JsonObject(); + json.addProperty("username", getUsername()); + json.addProperty("uuid", getUUID().toString()); + json.addProperty("type", "offline"); + return json; + } + + @Override + public void join(String serverId) { + } + + @Override + public boolean select() { + return true; + } + + @Override + public void logout() { + } + + @Override + public String getId() { + return getUsername(); + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/assets/AssetsManager.java b/src/main/java/de/bixilon/minosoft/data/assets/AssetsManager.java index cf5546d12..5b2a7d82b 100644 --- a/src/main/java/de/bixilon/minosoft/data/assets/AssetsManager.java +++ b/src/main/java/de/bixilon/minosoft/data/assets/AssetsManager.java @@ -70,7 +70,7 @@ public class AssetsManager { return ret; } - public static void downloadAllAssets(CountUpAndDownLatch latch) throws IOException { + public static void downloadAllAssets(CountUpAndDownLatch latch) throws IOException, InterruptedException { if (!ASSETS_MAP.isEmpty()) { return; } @@ -184,7 +184,7 @@ public class AssetsManager { return false; } - public static void generateJarAssets() throws IOException { + public static void generateJarAssets() throws IOException, InterruptedException { long startTime = System.currentTimeMillis(); Log.verbose("Generating client.jar assets..."); if (verifyAssetHash(ASSETS_CLIENT_JAR_HASH)) { diff --git a/src/main/java/de/bixilon/minosoft/data/locale/Strings.java b/src/main/java/de/bixilon/minosoft/data/locale/Strings.java index e6c9a52f3..bd370c541 100644 --- a/src/main/java/de/bixilon/minosoft/data/locale/Strings.java +++ b/src/main/java/de/bixilon/minosoft/data/locale/Strings.java @@ -80,7 +80,6 @@ public enum Strings { LOGIN_DIALOG_TITLE, LOGIN_DIALOG_HEADER, - LOGIN_ACCOUNT_ALREADY_PRESENT, MINOSOFT_STILL_STARTING_TITLE, MINOSOFT_STILL_STARTING_HEADER, diff --git a/src/main/java/de/bixilon/minosoft/gui/main/AccountListCell.java b/src/main/java/de/bixilon/minosoft/gui/main/AccountListCell.java index 947b67df0..76eff9d5d 100644 --- a/src/main/java/de/bixilon/minosoft/gui/main/AccountListCell.java +++ b/src/main/java/de/bixilon/minosoft/gui/main/AccountListCell.java @@ -14,10 +14,10 @@ package de.bixilon.minosoft.gui.main; import de.bixilon.minosoft.Minosoft; +import de.bixilon.minosoft.data.accounts.Account; import de.bixilon.minosoft.data.locale.LocaleManager; import de.bixilon.minosoft.data.locale.Strings; import de.bixilon.minosoft.logging.Log; -import de.bixilon.minosoft.util.mojang.api.MojangAccount; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.control.*; @@ -28,8 +28,8 @@ import java.io.IOException; import java.net.URL; import java.util.ResourceBundle; -public class AccountListCell extends ListCell implements Initializable { - public static final ListView MOJANG_ACCOUNT_LIST_VIEW = new ListView<>(); +public class AccountListCell extends ListCell implements Initializable { + public static final ListView MOJANG_ACCOUNT_LIST_VIEW = new ListView<>(); public MenuButton optionsMenu; public Label playerName; @@ -38,7 +38,7 @@ public class AccountListCell extends ListCell implements Initiali public MenuItem optionsDelete; public AnchorPane root; - private MojangAccount account; + private Account account; public static AccountListCell newInstance() { FXMLLoader loader = new FXMLLoader(AccountListCell.class.getResource("/layout/cells/account.fxml")); @@ -67,7 +67,7 @@ public class AccountListCell extends ListCell implements Initiali } @Override - protected void updateItem(MojangAccount account, boolean empty) { + protected void updateItem(Account account, boolean empty) { super.updateItem(account, empty); this.root.setVisible(!empty); @@ -85,9 +85,9 @@ public class AccountListCell extends ListCell implements Initiali resetCell(); this.account = account; - this.playerName.setText(account.getPlayerName()); - this.email.setText(account.getMojangUserName()); - if (Minosoft.getSelectedAccount() == account) { + this.playerName.setText(account.getUsername()); + // this.email.setText(account.getEmail()); + if (Minosoft.getConfig().getSelectedAccount() == account) { setStyle("-fx-background-color: darkseagreen;"); this.optionsSelect.setDisable(true); } @@ -99,17 +99,19 @@ public class AccountListCell extends ListCell implements Initiali this.optionsSelect.setDisable(false); } - public void delete() { - this.account.delete(); - if (Minosoft.getSelectedAccount() == this.account) { - if (Minosoft.getConfig().getAccountList().isEmpty()) { + public void logout() { + this.account.logout(); + Minosoft.getConfig().removeAccount(this.account); + Minosoft.getConfig().saveToFile(); + if (Minosoft.getConfig().getSelectedAccount() == this.account) { + if (Minosoft.getConfig().getSccounts().isEmpty()) { Minosoft.selectAccount(null); } else { - Minosoft.selectAccount(Minosoft.getConfig().getAccountList().values().iterator().next()); + Minosoft.selectAccount(Minosoft.getConfig().getSccounts().values().iterator().next()); } MOJANG_ACCOUNT_LIST_VIEW.refresh(); } - Log.info(String.format("Deleted account (email=\"%s\", playerName=\"%s\")", this.account.getMojangUserName(), this.account.getPlayerName())); + Log.info(String.format("Deleted account (id=%s, username=%s)", this.account.getId(), this.account.getUsername())); MOJANG_ACCOUNT_LIST_VIEW.getItems().remove(this.account); } diff --git a/src/main/java/de/bixilon/minosoft/gui/main/AccountWindow.java b/src/main/java/de/bixilon/minosoft/gui/main/AccountWindow.java index 67c20ffe5..76ab371e0 100644 --- a/src/main/java/de/bixilon/minosoft/gui/main/AccountWindow.java +++ b/src/main/java/de/bixilon/minosoft/gui/main/AccountWindow.java @@ -14,9 +14,9 @@ package de.bixilon.minosoft.gui.main; import de.bixilon.minosoft.Minosoft; +import de.bixilon.minosoft.data.accounts.Account; import de.bixilon.minosoft.data.locale.LocaleManager; import de.bixilon.minosoft.data.locale.Strings; -import de.bixilon.minosoft.util.mojang.api.MojangAccount; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; @@ -30,7 +30,6 @@ import java.net.URL; import java.util.ResourceBundle; public class AccountWindow implements Initializable { - public BorderPane accountPane; public MenuItem menuAddAccount; @@ -38,7 +37,7 @@ public class AccountWindow implements Initializable { public void initialize(URL url, ResourceBundle resourceBundle) { AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.setCellFactory((lv) -> AccountListCell.newInstance()); - ObservableList accounts = FXCollections.observableArrayList(Minosoft.getConfig().getAccountList().values()); + ObservableList accounts = FXCollections.observableArrayList(Minosoft.getConfig().getSccounts().values()); AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.setItems(accounts); this.accountPane.setCenter(AccountListCell.MOJANG_ACCOUNT_LIST_VIEW); diff --git a/src/main/java/de/bixilon/minosoft/gui/main/Launcher.java b/src/main/java/de/bixilon/minosoft/gui/main/Launcher.java index 47c8418bc..9c5d8025e 100644 --- a/src/main/java/de/bixilon/minosoft/gui/main/Launcher.java +++ b/src/main/java/de/bixilon/minosoft/gui/main/Launcher.java @@ -92,7 +92,7 @@ public class Launcher { stage.show(); - if (Minosoft.getSelectedAccount() == null) { + if (Minosoft.getConfig().getSelectedAccount() == null) { MainWindow.manageAccounts(); } latch.countDown(); diff --git a/src/main/java/de/bixilon/minosoft/gui/main/MainWindow.java b/src/main/java/de/bixilon/minosoft/gui/main/MainWindow.java index 358a712b0..c40250ac5 100644 --- a/src/main/java/de/bixilon/minosoft/gui/main/MainWindow.java +++ b/src/main/java/de/bixilon/minosoft/gui/main/MainWindow.java @@ -19,6 +19,7 @@ import com.jfoenix.controls.JFXDialogLayout; import com.jfoenix.controls.JFXTextField; import com.jfoenix.validation.RequiredFieldValidator; import de.bixilon.minosoft.Minosoft; +import de.bixilon.minosoft.data.accounts.Account; import de.bixilon.minosoft.data.locale.LocaleManager; import de.bixilon.minosoft.data.locale.Strings; import de.bixilon.minosoft.data.mappings.versions.Versions; @@ -26,7 +27,6 @@ import de.bixilon.minosoft.data.text.BaseComponent; import de.bixilon.minosoft.logging.Log; import de.bixilon.minosoft.protocol.protocol.LANServerListener; import de.bixilon.minosoft.util.DNSUtil; -import de.bixilon.minosoft.util.mojang.api.MojangAccount; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; @@ -71,7 +71,7 @@ public class MainWindow implements Initializable { GUITools.initializeScene(stage.getScene()); Platform.setImplicitExit(false); stage.setOnCloseRequest(event -> { - if (Minosoft.getSelectedAccount() == null) { + if (Minosoft.getConfig().getSelectedAccount() == null) { event.consume(); JFXAlert alert = new JFXAlert<>(); GUITools.initializePane(alert.getDialogPane()); @@ -212,10 +212,22 @@ public class MainWindow implements Initializable { dialog.showAndWait(); } - public void selectAccount(MojangAccount account) { + public void selectAccount(Account account) { + Runnable runnable = () -> { + if (account != null) { + MainWindow.this.menuAccount.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS_SELECTED, account.getUsername())); + } else { + MainWindow.this.menuAccount.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS)); + } + }; + if (Platform.isFxApplicationThread()) { + runnable.run(); + } else { + Platform.runLater(runnable); + } Platform.runLater(() -> { if (account != null) { - this.menuAccount.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS_SELECTED, account.getPlayerName())); + this.menuAccount.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS_SELECTED, account.getUsername())); } else { this.menuAccount.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS)); } @@ -235,7 +247,7 @@ public class MainWindow implements Initializable { this.menuHelp.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_HELP)); this.menuHelpAbout.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_HELP_ABOUT)); this.menuAccountManage.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS_MANAGE)); - selectAccount(Minosoft.getSelectedAccount()); + selectAccount(Minosoft.getConfig().getSelectedAccount()); } @FXML diff --git a/src/main/java/de/bixilon/minosoft/gui/main/ServerListCell.java b/src/main/java/de/bixilon/minosoft/gui/main/ServerListCell.java index 71a56779d..8b6434738 100644 --- a/src/main/java/de/bixilon/minosoft/gui/main/ServerListCell.java +++ b/src/main/java/de/bixilon/minosoft/gui/main/ServerListCell.java @@ -281,7 +281,7 @@ public class ServerListCell extends ListCell implements Initializable { if (!this.canConnect || this.server.getLastPing() == null) { return; } - Connection connection = new Connection(Connection.lastConnectionId++, this.server.getAddress(), new Player(Minosoft.getSelectedAccount())); + Connection connection = new Connection(Connection.lastConnectionId++, this.server.getAddress(), new Player(Minosoft.getConfig().getSelectedAccount())); Version version; if (this.server.getDesiredVersionId() == ProtocolDefinition.QUERY_PROTOCOL_VERSION_ID) { version = this.server.getLastPing().getVersion(); @@ -317,7 +317,7 @@ public class ServerListCell extends ListCell implements Initializable { } } - this.optionsConnect.setDisable(Minosoft.getSelectedAccount() == connection.getPlayer().getAccount()); + this.optionsConnect.setDisable(Minosoft.getConfig().getSelectedAccount() == connection.getPlayer().getAccount()); getStyleClass().add("list-cell-connected"); this.optionsSessions.setDisable(false); }); diff --git a/src/main/java/de/bixilon/minosoft/gui/main/SessionListCell.java b/src/main/java/de/bixilon/minosoft/gui/main/SessionListCell.java index 8a5d4003b..9c5712e56 100644 --- a/src/main/java/de/bixilon/minosoft/gui/main/SessionListCell.java +++ b/src/main/java/de/bixilon/minosoft/gui/main/SessionListCell.java @@ -85,7 +85,7 @@ public class SessionListCell extends ListCell implements Initializab this.connection = connection; connection.registerEvent(new EventInvokerCallback<>(ConnectionStateChangeEvent.class, this::handleConnectionCallback)); this.connectionId.setText(String.format("#%d", connection.getConnectionId())); - this.account.setText(connection.getPlayer().getAccount().getPlayerName()); + this.account.setText(connection.getPlayer().getAccount().getUsername()); } private void handleConnectionCallback(ConnectionStateChangeEvent event) { diff --git a/src/main/java/de/bixilon/minosoft/gui/main/dialogs/MojangLoginController.java b/src/main/java/de/bixilon/minosoft/gui/main/dialogs/MojangLoginController.java index aaa3726a9..9835a2aac 100644 --- a/src/main/java/de/bixilon/minosoft/gui/main/dialogs/MojangLoginController.java +++ b/src/main/java/de/bixilon/minosoft/gui/main/dialogs/MojangLoginController.java @@ -17,13 +17,14 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXPasswordField; import com.jfoenix.controls.JFXTextField; import de.bixilon.minosoft.Minosoft; +import de.bixilon.minosoft.data.accounts.MojangAccount; import de.bixilon.minosoft.data.locale.LocaleManager; import de.bixilon.minosoft.data.locale.Strings; import de.bixilon.minosoft.gui.main.AccountListCell; import de.bixilon.minosoft.logging.Log; -import de.bixilon.minosoft.util.mojang.api.MojangAccount; -import de.bixilon.minosoft.util.mojang.api.MojangAccountAuthenticationAttempt; import de.bixilon.minosoft.util.mojang.api.MojangAuthentication; +import de.bixilon.minosoft.util.mojang.api.exceptions.AuthenticationException; +import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException; import javafx.application.Platform; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; @@ -49,7 +50,6 @@ public class MojangLoginController implements Initializable { @Override public void initialize(URL url, ResourceBundle resourceBundle) { // translate - this.header.setText(LocaleManager.translate(Strings.LOGIN_DIALOG_HEADER)); this.emailLabel.setText(LocaleManager.translate(Strings.EMAIL)); this.passwordLabel.setText(LocaleManager.translate(Strings.PASSWORD)); @@ -83,45 +83,31 @@ public class MojangLoginController implements Initializable { new Thread(() -> { // ToDo: recycle thread - MojangAccountAuthenticationAttempt attempt = MojangAuthentication.login(this.email.getText(), this.password.getText()); - if (attempt.succeeded()) { - // login okay - final MojangAccount account = attempt.getAccount(); - if (Minosoft.getConfig().getAccountList().containsValue(account)) { - // account already present - // replace access token - MojangAccount existingAccount = Minosoft.getConfig().getAccountList().get(account.getUserId()); - existingAccount.setAccessToken(attempt.getAccount().getAccessToken()); - existingAccount.saveToConfig(); - Platform.runLater(() -> { - this.errorMessage.setText(LocaleManager.translate(Strings.LOGIN_ACCOUNT_ALREADY_PRESENT)); - this.errorMessage.setVisible(true); - this.email.setDisable(false); - this.password.setDisable(false); - this.loginButton.setDisable(true); - }); - return; - } - Minosoft.getConfig().putMojangAccount(account); + try { + MojangAccount account = MojangAuthentication.login(this.email.getText(), this.password.getText()); + + account.setNeedRefresh(false); + Minosoft.getConfig().putAccount(account); account.saveToConfig(); - Log.info(String.format("Added and saved account (playerName=%s, email=%s, uuid=%s)", account.getPlayerName(), account.getMojangUserName(), account.getUUID())); + Log.info(String.format("Added and saved account (username=%s, email=%s, uuid=%s)", account.getUsername(), account.getEmail(), account.getUUID())); Platform.runLater(() -> { AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.getItems().add(account); close(); }); - if (Minosoft.getSelectedAccount() == null) { + if (Minosoft.getConfig().getSelectedAccount() == null) { // select account Minosoft.selectAccount(account); } - return; + } catch (AuthenticationException | NoNetworkConnectionException e) { + e.printStackTrace(); + Platform.runLater(() -> { + this.errorMessage.setText(e.getMessage()); + this.errorMessage.setVisible(true); + this.email.setDisable(false); + this.password.setDisable(false); + this.loginButton.setDisable(true); + }); } - Platform.runLater(() -> { - this.errorMessage.setText(attempt.getError()); - this.errorMessage.setVisible(true); - this.email.setDisable(false); - this.password.setDisable(false); - this.loginButton.setDisable(true); - }); }, "AccountLoginThread").start(); } diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/Network.java b/src/main/java/de/bixilon/minosoft/protocol/network/Network.java index 141bf494b..b921e3703 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/network/Network.java +++ b/src/main/java/de/bixilon/minosoft/protocol/network/Network.java @@ -17,7 +17,7 @@ import de.bixilon.minosoft.logging.Log; import de.bixilon.minosoft.protocol.exceptions.PacketNotImplementedException; import de.bixilon.minosoft.protocol.exceptions.PacketParseException; import de.bixilon.minosoft.protocol.exceptions.UnknownPacketException; -import de.bixilon.minosoft.protocol.network.socket.NonBlockingSocketNetwork; +import de.bixilon.minosoft.protocol.network.socket.BlockingSocketNetwork; import de.bixilon.minosoft.protocol.packets.ClientboundPacket; import de.bixilon.minosoft.protocol.packets.ServerboundPacket; import de.bixilon.minosoft.protocol.packets.clientbound.interfaces.CompressionThresholdChange; @@ -36,7 +36,7 @@ public abstract class Network { } public static Network getNetworkInstance(Connection connection) { - return new NonBlockingSocketNetwork(connection); + return new BlockingSocketNetwork(connection); } public abstract void connect(ServerAddress address); diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/socket/BlockingSocketNetwork.java b/src/main/java/de/bixilon/minosoft/protocol/network/socket/BlockingSocketNetwork.java index 78a713d58..81e3f1068 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/network/socket/BlockingSocketNetwork.java +++ b/src/main/java/de/bixilon/minosoft/protocol/network/socket/BlockingSocketNetwork.java @@ -40,7 +40,6 @@ import java.net.Socket; import java.net.SocketException; import java.util.concurrent.LinkedBlockingQueue; -@Deprecated public class BlockingSocketNetwork extends Network { private final LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); private Thread socketReceiveThread; diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/login/PacketEncryptionRequest.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/login/PacketEncryptionRequest.java index cc7399adf..006967c62 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/login/PacketEncryptionRequest.java +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/login/PacketEncryptionRequest.java @@ -19,6 +19,8 @@ import de.bixilon.minosoft.protocol.packets.ClientboundPacket; import de.bixilon.minosoft.protocol.packets.serverbound.login.PacketEncryptionResponse; import de.bixilon.minosoft.protocol.protocol.CryptManager; import de.bixilon.minosoft.protocol.protocol.InByteBuffer; +import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException; +import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException; import javax.crypto.SecretKey; import java.math.BigInteger; @@ -43,7 +45,13 @@ public class PacketEncryptionRequest extends ClientboundPacket { SecretKey secretKey = CryptManager.createNewSharedKey(); PublicKey publicKey = CryptManager.decodePublicKey(getPublicKey()); String serverHash = new BigInteger(CryptManager.getServerHash(getServerId(), publicKey, secretKey)).toString(16); - connection.getPlayer().getAccount().join(serverHash); + try { + connection.getPlayer().getAccount().join(serverHash); + } catch (MojangJoinServerErrorException | NoNetworkConnectionException e) { + e.printStackTrace(); + connection.disconnect(); + return; + } connection.sendPacket(new PacketEncryptionResponse(secretKey, getVerifyToken(), publicKey)); } diff --git a/src/main/java/de/bixilon/minosoft/util/HTTP.java b/src/main/java/de/bixilon/minosoft/util/HTTP.java index c214f8201..36c67d01f 100644 --- a/src/main/java/de/bixilon/minosoft/util/HTTP.java +++ b/src/main/java/de/bixilon/minosoft/util/HTTP.java @@ -16,8 +16,6 @@ package de.bixilon.minosoft.util; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import de.bixilon.minosoft.logging.Log; -import de.bixilon.minosoft.logging.LogLevels; import java.io.IOException; import java.net.URI; @@ -27,32 +25,22 @@ import java.net.http.HttpResponse; public final class HTTP { - public static HttpResponse postJson(String url, JsonObject json) { + public static HttpResponse postJson(String url, JsonObject json) throws IOException, InterruptedException { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).POST(HttpRequest.BodyPublishers.ofString(json.toString())).header("Content-Type", "application/json").build(); - try { - return client.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (Exception e) { - Log.printException(e, LogLevels.DEBUG); - } - return null; + return client.send(request, HttpResponse.BodyHandlers.ofString()); } - public static HttpResponse get(String url) { + public static HttpResponse get(String url) throws IOException, InterruptedException { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build(); - try { - return client.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException | InterruptedException e) { - e.printStackTrace(); - } - return null; + return client.send(request, HttpResponse.BodyHandlers.ofString()); } - public static JsonElement getJson(String url) { + public static JsonElement getJson(String url) throws IOException, InterruptedException { HttpResponse response = get(url); - if (response == null || response.statusCode() != 200) { - return null; + if (response.statusCode() != 200) { + throw new IOException(); } return JsonParser.parseString(response.body()); } diff --git a/src/main/java/de/bixilon/minosoft/util/mojang/api/MojangAccount.java b/src/main/java/de/bixilon/minosoft/util/mojang/api/MojangAccount.java deleted file mode 100644 index e7fe5761a..000000000 --- a/src/main/java/de/bixilon/minosoft/util/mojang/api/MojangAccount.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Minosoft - * Copyright (C) 2020 Moritz Zwerger - * - * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program.If not, see . - * - * This software is not affiliated with Mojang AB, the original developer of Minecraft. - */ - -package de.bixilon.minosoft.util.mojang.api; - -import com.google.gson.JsonObject; -import de.bixilon.minosoft.Minosoft; -import de.bixilon.minosoft.util.Util; - -import java.util.UUID; - -public class MojangAccount { - final String userId; - final UUID uuid; - final String playerName; - final String mojangUserName; - String accessToken; - RefreshStates lastRefreshStatus; - private boolean needsRefresh = true; - - public MojangAccount(String username, JsonObject json) { - this.accessToken = json.get("accessToken").getAsString(); - JsonObject profile = json.get("selectedProfile").getAsJsonObject(); - this.uuid = Util.getUUIDFromString(profile.get("id").getAsString()); - this.playerName = profile.get("name").getAsString(); - - JsonObject mojang = json.get("user").getAsJsonObject(); - this.userId = mojang.get("id").getAsString(); - this.mojangUserName = username; - } - - public MojangAccount(String accessToken, String userId, UUID uuid, String playerName, String mojangUserName) { - this.accessToken = accessToken; - this.userId = userId; - this.uuid = uuid; - this.playerName = playerName; - this.mojangUserName = mojangUserName; - } - - public static MojangAccount deserialize(JsonObject json) { - return new MojangAccount(json.get("accessToken").getAsString(), json.get("userId").getAsString(), Util.getUUIDFromString(json.get("uuid").getAsString()), json.get("playerName").getAsString(), json.get("userName").getAsString()); - } - - public JsonObject serialize() { - JsonObject json = new JsonObject(); - json.addProperty("accessToken", this.accessToken); - json.addProperty("userId", this.userId); - json.addProperty("uuid", this.uuid.toString()); - json.addProperty("playerName", this.playerName); - json.addProperty("userName", this.mojangUserName); - return json; - } - - public void join(String serverId) { - MojangAuthentication.joinServer(this, serverId); - } - - public RefreshStates refreshToken() { - if (this.lastRefreshStatus != null) { - return this.lastRefreshStatus; - } - String accessToken = MojangAuthentication.refresh(this.accessToken); - if (accessToken == null) { - this.lastRefreshStatus = RefreshStates.FAILED; - return this.lastRefreshStatus; - } - if (accessToken.isBlank()) { - this.lastRefreshStatus = RefreshStates.ERROR; - return this.lastRefreshStatus; - } - this.accessToken = accessToken; - this.lastRefreshStatus = RefreshStates.SUCCESSFUL; - return this.lastRefreshStatus; - } - - public UUID getUUID() { - return this.uuid; - } - - public String getPlayerName() { - return this.playerName; - } - - public String getAccessToken() { - return this.accessToken; - } - - public String getMojangUserName() { - return this.mojangUserName; - } - - public void saveToConfig() { - Minosoft.getConfig().putMojangAccount(this); - Minosoft.getConfig().saveToFile(); - } - - public void delete() { - Minosoft.getConfig().removeAccount(this); - Minosoft.getConfig().saveToFile(); - } - - @Override - public String toString() { - return getUserId(); - } - - public String getUserId() { - return this.userId; - } - - @Override - public int hashCode() { - return this.userId.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (super.equals(obj)) { - return true; - } - if (obj == null) { - return false; - } - if (hashCode() != obj.hashCode()) { - return false; - } - MojangAccount account = (MojangAccount) obj; - return account.getUserId().equals(getUserId()); - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - this.needsRefresh = false; - } - - public boolean needsRefresh() { - return this.needsRefresh; - } - - public enum RefreshStates { - SUCCESSFUL, - ERROR, // account not valid anymore - FAILED // error occurred while checking -> Unknown state - } - -} diff --git a/src/main/java/de/bixilon/minosoft/util/mojang/api/MojangAuthentication.java b/src/main/java/de/bixilon/minosoft/util/mojang/api/MojangAuthentication.java index 7e16f6d93..35d404fcf 100644 --- a/src/main/java/de/bixilon/minosoft/util/mojang/api/MojangAuthentication.java +++ b/src/main/java/de/bixilon/minosoft/util/mojang/api/MojangAuthentication.java @@ -18,19 +18,25 @@ import com.google.gson.JsonParser; import de.bixilon.minosoft.Minosoft; import de.bixilon.minosoft.config.ConfigurationPaths; import de.bixilon.minosoft.config.StaticConfiguration; +import de.bixilon.minosoft.data.accounts.MojangAccount; import de.bixilon.minosoft.logging.Log; +import de.bixilon.minosoft.logging.LogLevels; import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition; import de.bixilon.minosoft.util.HTTP; +import de.bixilon.minosoft.util.mojang.api.exceptions.AuthenticationException; +import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException; +import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException; +import java.io.IOException; import java.net.http.HttpResponse; public final class MojangAuthentication { - public static MojangAccountAuthenticationAttempt login(String username, String password) { + public static MojangAccount login(String username, String password) throws AuthenticationException, NoNetworkConnectionException { return login(Minosoft.getConfig().getString(ConfigurationPaths.StringPaths.CLIENT_TOKEN), username, password); } - public static MojangAccountAuthenticationAttempt login(String clientToken, String username, String password) { + public static MojangAccount login(String clientToken, String username, String password) throws NoNetworkConnectionException, AuthenticationException { JsonObject agent = new JsonObject(); agent.addProperty("name", "Minecraft"); agent.addProperty("version", 1); @@ -42,22 +48,28 @@ public final class MojangAuthentication { payload.addProperty("clientToken", clientToken); payload.addProperty("requestUser", true); - HttpResponse response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_LOGIN, payload); + HttpResponse response; + try { + response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_LOGIN, payload); + } catch (IOException | InterruptedException e) { + Log.printException(e, LogLevels.DEBUG); + throw new NoNetworkConnectionException(e); + } if (response == null) { Log.mojang(String.format("Failed to login with username %s", username)); - return new MojangAccountAuthenticationAttempt("Unknown error, check your Internet connection"); + throw new NoNetworkConnectionException("Unknown error, check your Internet connection"); } JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); if (response.statusCode() != 200) { Log.mojang(String.format("Failed to login with error code %d: %s", response.statusCode(), jsonResponse.get("errorMessage").getAsString())); - return new MojangAccountAuthenticationAttempt(jsonResponse.get("errorMessage").getAsString()); + throw new AuthenticationException(jsonResponse.get("errorMessage").getAsString()); } // now it is okay - return new MojangAccountAuthenticationAttempt(new MojangAccount(username, jsonResponse)); + return new MojangAccount(username, jsonResponse); } - public static void joinServer(MojangAccount account, String serverId) { + public static void joinServer(MojangAccount account, String serverId) throws NoNetworkConnectionException, MojangJoinServerErrorException { if (StaticConfiguration.SKIP_MOJANG_AUTHENTICATION) { return; } @@ -67,26 +79,31 @@ public final class MojangAuthentication { payload.addProperty("selectedProfile", account.getUUID().toString().replace("-", "")); payload.addProperty("serverId", serverId); - HttpResponse response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_JOIN, payload); + HttpResponse response; + try { + response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_JOIN, payload); + } catch (IOException | InterruptedException e) { + throw new NoNetworkConnectionException(e); + } if (response == null) { Log.mojang(String.format("Failed to join server: %s", serverId)); - return; + throw new MojangJoinServerErrorException(); } if (response.statusCode() != 204) { JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); Log.mojang(String.format("Failed to join server with error code %d: %s", response.statusCode(), jsonResponse.has("errorMessage") ? jsonResponse.get("errorMessage").getAsString() : "null")); - return; + throw new MojangJoinServerErrorException(jsonResponse.get("errorMessage").getAsString()); } // joined Log.mojang("Joined server successfully"); } - public static String refresh(String accessToken) { + public static String refresh(String accessToken) throws NoNetworkConnectionException, AuthenticationException { return refresh(Minosoft.getConfig().getString(ConfigurationPaths.StringPaths.CLIENT_TOKEN), accessToken); } - public static String refresh(String clientToken, String accessToken) { + public static String refresh(String clientToken, String accessToken) throws NoNetworkConnectionException, AuthenticationException { if (StaticConfiguration.SKIP_MOJANG_AUTHENTICATION) { return clientToken; } @@ -97,18 +114,18 @@ public final class MojangAuthentication { HttpResponse response; try { response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_REFRESH, payload); - } catch (Exception e) { + } catch (IOException | InterruptedException e) { Log.mojang(String.format("Could not connect to mojang server: %s", e.getCause().toString())); - return null; + throw new NoNetworkConnectionException(e); } if (response == null) { Log.mojang("Failed to refresh session"); - return null; + throw new NoNetworkConnectionException(); } JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); if (response.statusCode() != 200) { Log.mojang(String.format("Failed to refresh session with error code %d: %s", response.statusCode(), jsonResponse.get("errorMessage").getAsString())); - return ""; + throw new AuthenticationException(jsonResponse.get("errorMessage").getAsString()); } // now it is okay Log.mojang("Refreshed 1 session token"); diff --git a/src/main/java/de/bixilon/minosoft/util/mojang/api/MojangBlockedServers.java b/src/main/java/de/bixilon/minosoft/util/mojang/api/MojangBlockedServers.java deleted file mode 100644 index afa460116..000000000 --- a/src/main/java/de/bixilon/minosoft/util/mojang/api/MojangBlockedServers.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Minosoft - * Copyright (C) 2020 Moritz Zwerger - * - * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program.If not, see . - * - * This software is not affiliated with Mojang AB, the original developer of Minecraft. - */ - -package de.bixilon.minosoft.util.mojang.api; - -import de.bixilon.minosoft.logging.Log; -import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition; -import de.bixilon.minosoft.util.HTTP; -import de.bixilon.minosoft.util.Util; - -import java.net.http.HttpResponse; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public final class MojangBlockedServers { - - public static ArrayList getBlockedServers() { - HttpResponse response = HTTP.get(ProtocolDefinition.MOJANG_URL_BLOCKED_SERVERS); - if (response == null) { - Log.mojang("Failed to fetch blocked servers"); - return null; - } - if (response.statusCode() != 200) { - Log.mojang(String.format("Failed to fetch blocked server error code %d", response.statusCode())); - return null; - } - // now it is hopefully okay - return new ArrayList<>(Arrays.asList(response.body().split("\n"))); - } - - public boolean isServerBlocked(List list, String hostname) { - for (String hash : list) { - if (hash.equals(Util.sha1(hostname))) { - return true; - } - // ToDo: check subdomains and ip addresses - } - return false; - } -} diff --git a/src/main/java/de/bixilon/minosoft/util/mojang/api/exceptions/AuthenticationException.java b/src/main/java/de/bixilon/minosoft/util/mojang/api/exceptions/AuthenticationException.java new file mode 100644 index 000000000..c29536680 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/util/mojang/api/exceptions/AuthenticationException.java @@ -0,0 +1,21 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.util.mojang.api.exceptions; + +public class AuthenticationException extends Exception { + + public AuthenticationException(String message) { + super(message); + } +} diff --git a/src/main/java/de/bixilon/minosoft/util/mojang/api/exceptions/MojangJoinServerErrorException.java b/src/main/java/de/bixilon/minosoft/util/mojang/api/exceptions/MojangJoinServerErrorException.java new file mode 100644 index 000000000..b75e39df3 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/util/mojang/api/exceptions/MojangJoinServerErrorException.java @@ -0,0 +1,24 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.util.mojang.api.exceptions; + +public class MojangJoinServerErrorException extends Exception { + + public MojangJoinServerErrorException(String message) { + super(message); + } + + public MojangJoinServerErrorException() { + } +} diff --git a/src/main/java/de/bixilon/minosoft/util/mojang/api/MojangAccountAuthenticationAttempt.java b/src/main/java/de/bixilon/minosoft/util/mojang/api/exceptions/NoNetworkConnectionException.java similarity index 56% rename from src/main/java/de/bixilon/minosoft/util/mojang/api/MojangAccountAuthenticationAttempt.java rename to src/main/java/de/bixilon/minosoft/util/mojang/api/exceptions/NoNetworkConnectionException.java index e69e91d15..8baa59ccf 100644 --- a/src/main/java/de/bixilon/minosoft/util/mojang/api/MojangAccountAuthenticationAttempt.java +++ b/src/main/java/de/bixilon/minosoft/util/mojang/api/exceptions/NoNetworkConnectionException.java @@ -11,31 +11,19 @@ * This software is not affiliated with Mojang AB, the original developer of Minecraft. */ -package de.bixilon.minosoft.util.mojang.api; +package de.bixilon.minosoft.util.mojang.api.exceptions; -public class MojangAccountAuthenticationAttempt { - final MojangAccount account; - final String error; +public class NoNetworkConnectionException extends Exception { - public MojangAccountAuthenticationAttempt(MojangAccount account) { - this.account = account; - this.error = null; + public NoNetworkConnectionException(Exception cause) { + super(cause); } - public MojangAccountAuthenticationAttempt(String error) { - this.account = null; - this.error = error; + public NoNetworkConnectionException(String message) { + super(message); } - public String getError() { - return this.error; - } + public NoNetworkConnectionException() { - public MojangAccount getAccount() { - return this.account; - } - - public boolean succeeded() { - return this.account != null; } } diff --git a/src/main/resources/assets/locale/de_DE.json b/src/main/resources/assets/locale/de_DE.json index 45a42cf40..56e6a846f 100644 --- a/src/main/resources/assets/locale/de_DE.json +++ b/src/main/resources/assets/locale/de_DE.json @@ -56,7 +56,6 @@ "SETTINGS_DOWNLOAD": "Download", "LOGIN_DIALOG_TITLE": "Anmelden (Mojang) - Minosoft", "LOGIN_DIALOG_HEADER": "Bitte gib deine Mojang Zugangsdaten ein dich anzumelden", - "LOGIN_ACCOUNT_ALREADY_PRESENT": "Account existiert bereits!", "ERROR": "Fehler", "MINOSOFT_STILL_STARTING_TITLE": "Bitte warten", "MINOSOFT_STILL_STARTING_HEADER": "Minosoft muss noch ein paar Dinge erledigen, bevor du es verwenden kannst.\nBitte warte noch ein paar Sekunden...", diff --git a/src/main/resources/assets/locale/en_US.json b/src/main/resources/assets/locale/en_US.json index 63cbaf02a..1a6d4ed24 100644 --- a/src/main/resources/assets/locale/en_US.json +++ b/src/main/resources/assets/locale/en_US.json @@ -57,7 +57,6 @@ "SETTINGS_DOWNLOAD": "Download", "LOGIN_DIALOG_TITLE": "Login (Mojang) - Minosoft", "LOGIN_DIALOG_HEADER": "Please login to your mojang account", - "LOGIN_ACCOUNT_ALREADY_PRESENT": "Account is already present!", "ERROR": "Error", "MINOSOFT_STILL_STARTING_TITLE": "Please wait", "MINOSOFT_STILL_STARTING_HEADER": "Minosoft is still starting up...", diff --git a/src/main/resources/layout/cells/account.fxml b/src/main/resources/layout/cells/account.fxml index 6fc9e27f6..2144f497a 100644 --- a/src/main/resources/layout/cells/account.fxml +++ b/src/main/resources/layout/cells/account.fxml @@ -1,7 +1,4 @@ - - - + + +