Improvements, WIP microsoft accounts (1): multiple login endpoints

This commit is contained in:
Bixilon 2020-12-22 23:01:37 +01:00
parent 569b9f4ba4
commit 9adb18681a
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
28 changed files with 490 additions and 368 deletions

View File

@ -18,6 +18,7 @@ import com.jfoenix.controls.JFXDialogLayout;
import de.bixilon.minosoft.config.Configuration; import de.bixilon.minosoft.config.Configuration;
import de.bixilon.minosoft.config.ConfigurationPaths; import de.bixilon.minosoft.config.ConfigurationPaths;
import de.bixilon.minosoft.config.StaticConfiguration; 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.assets.AssetsManager;
import de.bixilon.minosoft.data.locale.LocaleManager; import de.bixilon.minosoft.data.locale.LocaleManager;
import de.bixilon.minosoft.data.locale.minecraft.MinecraftLocaleManager; 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.CountUpAndDownLatch;
import de.bixilon.minosoft.util.MinosoftCommandLineArguments; import de.bixilon.minosoft.util.MinosoftCommandLineArguments;
import de.bixilon.minosoft.util.Util; 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.AsyncTaskWorker;
import de.bixilon.minosoft.util.task.Task; import de.bixilon.minosoft.util.task.Task;
import de.bixilon.minosoft.util.task.TaskImportance; import de.bixilon.minosoft.util.task.TaskImportance;
@ -51,7 +51,6 @@ import java.util.UUID;
public final class Minosoft { public final class Minosoft {
public static final HashSet<EventManager> EVENT_MANAGERS = new HashSet<>(); public static final HashSet<EventManager> EVENT_MANAGERS = new HashSet<>();
private static final CountUpAndDownLatch START_STATUS_LATCH = new CountUpAndDownLatch(1); private static final CountUpAndDownLatch START_STATUS_LATCH = new CountUpAndDownLatch(1);
public static MojangAccount selectedAccount;
public static Configuration config; public static Configuration config;
public static void main(String[] args) { public static void main(String[] args) {
@ -136,7 +135,7 @@ public final class Minosoft {
taskWorker.addTask(new Task(progress -> { taskWorker.addTask(new Task(progress -> {
Log.debug("Refreshing account token..."); Log.debug("Refreshing account token...");
checkClientToken(); 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")); }, "Token refresh", "Refresh selected account token", Priorities.LOW, TaskImportance.OPTIONAL, "Configuration"));
taskWorker.addTask(new Task(progress -> { 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) { if (account == null) {
selectedAccount = null;
config.putString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED, ""); config.putString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED, "");
config.saveToFile(); config.saveToFile();
return; return;
} }
if (account.needsRefresh()) { if (!account.select()) {
MojangAccount.RefreshStates refreshState = account.refreshToken(); account.logout();
if (refreshState == MojangAccount.RefreshStates.ERROR) { AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.getItems().remove(account);
account.delete(); config.removeAccount(account);
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.getItems().remove(account); config.saveToFile();
selectedAccount = null; return;
return;
}
} }
config.putString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED, account.getUserId()); config.putAccount(account);
config.selectAccount(account);
if (Launcher.getMainWindow() != null) { if (Launcher.getMainWindow() != null) {
Launcher.getMainWindow().selectAccount(selectedAccount); Launcher.getMainWindow().selectAccount(account);
} }
account.saveToConfig(); config.saveToFile();
selectedAccount = account;
} }
public static Configuration getConfig() { public static Configuration getConfig() {
return config; return config;
} }
public static MojangAccount getSelectedAccount() {
return selectedAccount;
}
/** /**
* Waits until all critical components are started * Waits until all critical components are started

View File

@ -15,10 +15,12 @@ package de.bixilon.minosoft.config;
import com.google.common.collect.HashBiMap; import com.google.common.collect.HashBiMap;
import com.google.gson.*; 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.gui.main.Server;
import de.bixilon.minosoft.logging.Log; import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.util.Util; import de.bixilon.minosoft.util.Util;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
@ -30,7 +32,7 @@ public class Configuration {
public static final int LATEST_CONFIG_VERSION = 1; 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 static final JsonObject DEFAULT_CONFIGURATION = JsonParser.parseReader(new InputStreamReader(Configuration.class.getResourceAsStream("/config/" + StaticConfiguration.CONFIG_FILENAME))).getAsJsonObject();
private final HashMap<ConfigurationPaths.ConfigurationPath, Object> config = new HashMap<>(); private final HashMap<ConfigurationPaths.ConfigurationPath, Object> config = new HashMap<>();
private final HashBiMap<String, MojangAccount> accountList = HashBiMap.create(); private final HashBiMap<String, Account> accountList = HashBiMap.create();
private final HashBiMap<Integer, Server> serverList = HashBiMap.create(); private final HashBiMap<Integer, Server> serverList = HashBiMap.create();
private final Object lock = new Object(); private final Object lock = new Object();
@ -71,8 +73,13 @@ public class Configuration {
// accounts // accounts
for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject("accounts").getAsJsonObject("entries").entrySet()) { for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject("accounts").getAsJsonObject("entries").entrySet()) {
MojangAccount account = MojangAccount.deserialize(entry.getValue().getAsJsonObject()); JsonObject data = entry.getValue().getAsJsonObject();
this.accountList.put(account.getUserId(), account); 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; final File finalFile = file;
@ -106,7 +113,7 @@ public class Configuration {
// servers // servers
JsonObject serversEntriesJson = jsonObject.getAsJsonObject("accounts").getAsJsonObject("entries"); JsonObject serversEntriesJson = jsonObject.getAsJsonObject("accounts").getAsJsonObject("entries");
for (Map.Entry<String, MojangAccount> entry : this.accountList.entrySet()) { for (Map.Entry<String, Account> entry : this.accountList.entrySet()) {
serversEntriesJson.add(entry.getKey(), entry.getValue().serialize()); serversEntriesJson.add(entry.getKey(), entry.getValue().serialize());
} }
@ -159,8 +166,8 @@ public class Configuration {
this.config.put(path, value); this.config.put(path, value);
} }
public void putMojangAccount(MojangAccount account) { public void putAccount(Account account) {
this.accountList.put(account.getUserId(), account); this.accountList.put(account.getId(), account);
} }
public void putServer(Server server) { public void putServer(Server server) {
@ -177,15 +184,26 @@ public class Configuration {
} }
} }
public void removeAccount(MojangAccount account) { public Account getSelectedAccount() {
this.accountList.remove(account.getUserId()); 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<Integer, Server> getServerList() { public HashBiMap<Integer, Server> getServerList() {
return this.serverList; return this.serverList;
} }
public HashBiMap<String, MojangAccount> getAccountList() { public HashBiMap<String, Account> getSccounts() {
return this.accountList; return this.accountList;
} }

View File

@ -13,6 +13,7 @@
package de.bixilon.minosoft.data; 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.entities.entities.player.PlayerEntity;
import de.bixilon.minosoft.data.inventory.Inventory; import de.bixilon.minosoft.data.inventory.Inventory;
import de.bixilon.minosoft.data.inventory.InventoryProperties; 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.text.ChatComponent;
import de.bixilon.minosoft.data.world.BlockPosition; import de.bixilon.minosoft.data.world.BlockPosition;
import de.bixilon.minosoft.data.world.World; import de.bixilon.minosoft.data.world.World;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import java.util.HashMap; import java.util.HashMap;
import java.util.UUID; import java.util.UUID;
@ -32,7 +32,7 @@ import static de.bixilon.minosoft.protocol.protocol.ProtocolDefinition.PLAYER_IN
public class Player { public class Player {
public final HashMap<UUID, PlayerListItem> playerList = new HashMap<>(); public final HashMap<UUID, PlayerListItem> playerList = new HashMap<>();
final MojangAccount account; final Account account;
final ScoreboardManager scoreboardManager = new ScoreboardManager(); final ScoreboardManager scoreboardManager = new ScoreboardManager();
final World world = new World(); final World world = new World();
final HashMap<Integer, Inventory> inventories = new HashMap<>(); final HashMap<Integer, Inventory> inventories = new HashMap<>();
@ -50,21 +50,21 @@ public class Player {
ChatComponent tabHeader; ChatComponent tabHeader;
ChatComponent tabFooter; ChatComponent tabFooter;
public Player(MojangAccount account) { public Player(Account account) {
this.account = account; this.account = account;
// create our own inventory without any properties // create our own inventory without any properties
this.inventories.put(PLAYER_INVENTORY_ID, new Inventory(null)); this.inventories.put(PLAYER_INVENTORY_ID, new Inventory(null));
} }
public String getPlayerName() { public String getPlayerName() {
return this.account.getPlayerName(); return this.account.getUsername();
} }
public UUID getPlayerUUID() { public UUID getPlayerUUID() {
return this.account.getUUID(); return this.account.getUUID();
} }
public MojangAccount getAccount() { public Account getAccount() {
return this.account; return this.account;
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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());
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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();
}
}

View File

@ -70,7 +70,7 @@ public class AssetsManager {
return ret; return ret;
} }
public static void downloadAllAssets(CountUpAndDownLatch latch) throws IOException { public static void downloadAllAssets(CountUpAndDownLatch latch) throws IOException, InterruptedException {
if (!ASSETS_MAP.isEmpty()) { if (!ASSETS_MAP.isEmpty()) {
return; return;
} }
@ -184,7 +184,7 @@ public class AssetsManager {
return false; return false;
} }
public static void generateJarAssets() throws IOException { public static void generateJarAssets() throws IOException, InterruptedException {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
Log.verbose("Generating client.jar assets..."); Log.verbose("Generating client.jar assets...");
if (verifyAssetHash(ASSETS_CLIENT_JAR_HASH)) { if (verifyAssetHash(ASSETS_CLIENT_JAR_HASH)) {

View File

@ -80,7 +80,6 @@ public enum Strings {
LOGIN_DIALOG_TITLE, LOGIN_DIALOG_TITLE,
LOGIN_DIALOG_HEADER, LOGIN_DIALOG_HEADER,
LOGIN_ACCOUNT_ALREADY_PRESENT,
MINOSOFT_STILL_STARTING_TITLE, MINOSOFT_STILL_STARTING_TITLE,
MINOSOFT_STILL_STARTING_HEADER, MINOSOFT_STILL_STARTING_HEADER,

View File

@ -14,10 +14,10 @@
package de.bixilon.minosoft.gui.main; package de.bixilon.minosoft.gui.main;
import de.bixilon.minosoft.Minosoft; 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.LocaleManager;
import de.bixilon.minosoft.data.locale.Strings; import de.bixilon.minosoft.data.locale.Strings;
import de.bixilon.minosoft.logging.Log; import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.*; import javafx.scene.control.*;
@ -28,8 +28,8 @@ import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.ResourceBundle; import java.util.ResourceBundle;
public class AccountListCell extends ListCell<MojangAccount> implements Initializable { public class AccountListCell extends ListCell<Account> implements Initializable {
public static final ListView<MojangAccount> MOJANG_ACCOUNT_LIST_VIEW = new ListView<>(); public static final ListView<Account> MOJANG_ACCOUNT_LIST_VIEW = new ListView<>();
public MenuButton optionsMenu; public MenuButton optionsMenu;
public Label playerName; public Label playerName;
@ -38,7 +38,7 @@ public class AccountListCell extends ListCell<MojangAccount> implements Initiali
public MenuItem optionsDelete; public MenuItem optionsDelete;
public AnchorPane root; public AnchorPane root;
private MojangAccount account; private Account account;
public static AccountListCell newInstance() { public static AccountListCell newInstance() {
FXMLLoader loader = new FXMLLoader(AccountListCell.class.getResource("/layout/cells/account.fxml")); FXMLLoader loader = new FXMLLoader(AccountListCell.class.getResource("/layout/cells/account.fxml"));
@ -67,7 +67,7 @@ public class AccountListCell extends ListCell<MojangAccount> implements Initiali
} }
@Override @Override
protected void updateItem(MojangAccount account, boolean empty) { protected void updateItem(Account account, boolean empty) {
super.updateItem(account, empty); super.updateItem(account, empty);
this.root.setVisible(!empty); this.root.setVisible(!empty);
@ -85,9 +85,9 @@ public class AccountListCell extends ListCell<MojangAccount> implements Initiali
resetCell(); resetCell();
this.account = account; this.account = account;
this.playerName.setText(account.getPlayerName()); this.playerName.setText(account.getUsername());
this.email.setText(account.getMojangUserName()); // this.email.setText(account.getEmail());
if (Minosoft.getSelectedAccount() == account) { if (Minosoft.getConfig().getSelectedAccount() == account) {
setStyle("-fx-background-color: darkseagreen;"); setStyle("-fx-background-color: darkseagreen;");
this.optionsSelect.setDisable(true); this.optionsSelect.setDisable(true);
} }
@ -99,17 +99,19 @@ public class AccountListCell extends ListCell<MojangAccount> implements Initiali
this.optionsSelect.setDisable(false); this.optionsSelect.setDisable(false);
} }
public void delete() { public void logout() {
this.account.delete(); this.account.logout();
if (Minosoft.getSelectedAccount() == this.account) { Minosoft.getConfig().removeAccount(this.account);
if (Minosoft.getConfig().getAccountList().isEmpty()) { Minosoft.getConfig().saveToFile();
if (Minosoft.getConfig().getSelectedAccount() == this.account) {
if (Minosoft.getConfig().getSccounts().isEmpty()) {
Minosoft.selectAccount(null); Minosoft.selectAccount(null);
} else { } else {
Minosoft.selectAccount(Minosoft.getConfig().getAccountList().values().iterator().next()); Minosoft.selectAccount(Minosoft.getConfig().getSccounts().values().iterator().next());
} }
MOJANG_ACCOUNT_LIST_VIEW.refresh(); 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); MOJANG_ACCOUNT_LIST_VIEW.getItems().remove(this.account);
} }

View File

@ -14,9 +14,9 @@
package de.bixilon.minosoft.gui.main; package de.bixilon.minosoft.gui.main;
import de.bixilon.minosoft.Minosoft; 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.LocaleManager;
import de.bixilon.minosoft.data.locale.Strings; import de.bixilon.minosoft.data.locale.Strings;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@ -30,7 +30,6 @@ import java.net.URL;
import java.util.ResourceBundle; import java.util.ResourceBundle;
public class AccountWindow implements Initializable { public class AccountWindow implements Initializable {
public BorderPane accountPane; public BorderPane accountPane;
public MenuItem menuAddAccount; public MenuItem menuAddAccount;
@ -38,7 +37,7 @@ public class AccountWindow implements Initializable {
public void initialize(URL url, ResourceBundle resourceBundle) { public void initialize(URL url, ResourceBundle resourceBundle) {
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.setCellFactory((lv) -> AccountListCell.newInstance()); AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.setCellFactory((lv) -> AccountListCell.newInstance());
ObservableList<MojangAccount> accounts = FXCollections.observableArrayList(Minosoft.getConfig().getAccountList().values()); ObservableList<Account> accounts = FXCollections.observableArrayList(Minosoft.getConfig().getSccounts().values());
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.setItems(accounts); AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.setItems(accounts);
this.accountPane.setCenter(AccountListCell.MOJANG_ACCOUNT_LIST_VIEW); this.accountPane.setCenter(AccountListCell.MOJANG_ACCOUNT_LIST_VIEW);

View File

@ -92,7 +92,7 @@ public class Launcher {
stage.show(); stage.show();
if (Minosoft.getSelectedAccount() == null) { if (Minosoft.getConfig().getSelectedAccount() == null) {
MainWindow.manageAccounts(); MainWindow.manageAccounts();
} }
latch.countDown(); latch.countDown();

View File

@ -19,6 +19,7 @@ import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXTextField; import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.RequiredFieldValidator; import com.jfoenix.validation.RequiredFieldValidator;
import de.bixilon.minosoft.Minosoft; 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.LocaleManager;
import de.bixilon.minosoft.data.locale.Strings; import de.bixilon.minosoft.data.locale.Strings;
import de.bixilon.minosoft.data.mappings.versions.Versions; 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.logging.Log;
import de.bixilon.minosoft.protocol.protocol.LANServerListener; import de.bixilon.minosoft.protocol.protocol.LANServerListener;
import de.bixilon.minosoft.util.DNSUtil; import de.bixilon.minosoft.util.DNSUtil;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@ -71,7 +71,7 @@ public class MainWindow implements Initializable {
GUITools.initializeScene(stage.getScene()); GUITools.initializeScene(stage.getScene());
Platform.setImplicitExit(false); Platform.setImplicitExit(false);
stage.setOnCloseRequest(event -> { stage.setOnCloseRequest(event -> {
if (Minosoft.getSelectedAccount() == null) { if (Minosoft.getConfig().getSelectedAccount() == null) {
event.consume(); event.consume();
JFXAlert<?> alert = new JFXAlert<>(); JFXAlert<?> alert = new JFXAlert<>();
GUITools.initializePane(alert.getDialogPane()); GUITools.initializePane(alert.getDialogPane());
@ -212,10 +212,22 @@ public class MainWindow implements Initializable {
dialog.showAndWait(); 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(() -> { Platform.runLater(() -> {
if (account != null) { 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 { } else {
this.menuAccount.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS)); 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.menuHelp.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_HELP));
this.menuHelpAbout.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_HELP_ABOUT)); this.menuHelpAbout.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_HELP_ABOUT));
this.menuAccountManage.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS_MANAGE)); this.menuAccountManage.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS_MANAGE));
selectAccount(Minosoft.getSelectedAccount()); selectAccount(Minosoft.getConfig().getSelectedAccount());
} }
@FXML @FXML

View File

@ -281,7 +281,7 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
if (!this.canConnect || this.server.getLastPing() == null) { if (!this.canConnect || this.server.getLastPing() == null) {
return; 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; Version version;
if (this.server.getDesiredVersionId() == ProtocolDefinition.QUERY_PROTOCOL_VERSION_ID) { if (this.server.getDesiredVersionId() == ProtocolDefinition.QUERY_PROTOCOL_VERSION_ID) {
version = this.server.getLastPing().getVersion(); version = this.server.getLastPing().getVersion();
@ -317,7 +317,7 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
} }
} }
this.optionsConnect.setDisable(Minosoft.getSelectedAccount() == connection.getPlayer().getAccount()); this.optionsConnect.setDisable(Minosoft.getConfig().getSelectedAccount() == connection.getPlayer().getAccount());
getStyleClass().add("list-cell-connected"); getStyleClass().add("list-cell-connected");
this.optionsSessions.setDisable(false); this.optionsSessions.setDisable(false);
}); });

View File

@ -85,7 +85,7 @@ public class SessionListCell extends ListCell<Connection> implements Initializab
this.connection = connection; this.connection = connection;
connection.registerEvent(new EventInvokerCallback<>(ConnectionStateChangeEvent.class, this::handleConnectionCallback)); connection.registerEvent(new EventInvokerCallback<>(ConnectionStateChangeEvent.class, this::handleConnectionCallback));
this.connectionId.setText(String.format("#%d", connection.getConnectionId())); 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) { private void handleConnectionCallback(ConnectionStateChangeEvent event) {

View File

@ -17,13 +17,14 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXPasswordField; import com.jfoenix.controls.JFXPasswordField;
import com.jfoenix.controls.JFXTextField; import com.jfoenix.controls.JFXTextField;
import de.bixilon.minosoft.Minosoft; 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.LocaleManager;
import de.bixilon.minosoft.data.locale.Strings; import de.bixilon.minosoft.data.locale.Strings;
import de.bixilon.minosoft.gui.main.AccountListCell; import de.bixilon.minosoft.gui.main.AccountListCell;
import de.bixilon.minosoft.logging.Log; 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.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.application.Platform;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
@ -49,7 +50,6 @@ public class MojangLoginController implements Initializable {
@Override @Override
public void initialize(URL url, ResourceBundle resourceBundle) { public void initialize(URL url, ResourceBundle resourceBundle) {
// translate // translate
this.header.setText(LocaleManager.translate(Strings.LOGIN_DIALOG_HEADER)); this.header.setText(LocaleManager.translate(Strings.LOGIN_DIALOG_HEADER));
this.emailLabel.setText(LocaleManager.translate(Strings.EMAIL)); this.emailLabel.setText(LocaleManager.translate(Strings.EMAIL));
this.passwordLabel.setText(LocaleManager.translate(Strings.PASSWORD)); this.passwordLabel.setText(LocaleManager.translate(Strings.PASSWORD));
@ -83,45 +83,31 @@ public class MojangLoginController implements Initializable {
new Thread(() -> { // ToDo: recycle thread new Thread(() -> { // ToDo: recycle thread
MojangAccountAuthenticationAttempt attempt = MojangAuthentication.login(this.email.getText(), this.password.getText()); try {
if (attempt.succeeded()) { MojangAccount account = MojangAuthentication.login(this.email.getText(), this.password.getText());
// login okay
final MojangAccount account = attempt.getAccount(); account.setNeedRefresh(false);
if (Minosoft.getConfig().getAccountList().containsValue(account)) { Minosoft.getConfig().putAccount(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);
account.saveToConfig(); 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(() -> { Platform.runLater(() -> {
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.getItems().add(account); AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.getItems().add(account);
close(); close();
}); });
if (Minosoft.getSelectedAccount() == null) { if (Minosoft.getConfig().getSelectedAccount() == null) {
// select account // select account
Minosoft.selectAccount(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(); }, "AccountLoginThread").start();
} }

View File

@ -17,7 +17,7 @@ import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.protocol.exceptions.PacketNotImplementedException; import de.bixilon.minosoft.protocol.exceptions.PacketNotImplementedException;
import de.bixilon.minosoft.protocol.exceptions.PacketParseException; import de.bixilon.minosoft.protocol.exceptions.PacketParseException;
import de.bixilon.minosoft.protocol.exceptions.UnknownPacketException; 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.ClientboundPacket;
import de.bixilon.minosoft.protocol.packets.ServerboundPacket; import de.bixilon.minosoft.protocol.packets.ServerboundPacket;
import de.bixilon.minosoft.protocol.packets.clientbound.interfaces.CompressionThresholdChange; import de.bixilon.minosoft.protocol.packets.clientbound.interfaces.CompressionThresholdChange;
@ -36,7 +36,7 @@ public abstract class Network {
} }
public static Network getNetworkInstance(Connection connection) { public static Network getNetworkInstance(Connection connection) {
return new NonBlockingSocketNetwork(connection); return new BlockingSocketNetwork(connection);
} }
public abstract void connect(ServerAddress address); public abstract void connect(ServerAddress address);

View File

@ -40,7 +40,6 @@ import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
@Deprecated
public class BlockingSocketNetwork extends Network { public class BlockingSocketNetwork extends Network {
private final LinkedBlockingQueue<ServerboundPacket> queue = new LinkedBlockingQueue<>(); private final LinkedBlockingQueue<ServerboundPacket> queue = new LinkedBlockingQueue<>();
private Thread socketReceiveThread; private Thread socketReceiveThread;

View File

@ -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.packets.serverbound.login.PacketEncryptionResponse;
import de.bixilon.minosoft.protocol.protocol.CryptManager; import de.bixilon.minosoft.protocol.protocol.CryptManager;
import de.bixilon.minosoft.protocol.protocol.InByteBuffer; 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 javax.crypto.SecretKey;
import java.math.BigInteger; import java.math.BigInteger;
@ -43,7 +45,13 @@ public class PacketEncryptionRequest extends ClientboundPacket {
SecretKey secretKey = CryptManager.createNewSharedKey(); SecretKey secretKey = CryptManager.createNewSharedKey();
PublicKey publicKey = CryptManager.decodePublicKey(getPublicKey()); PublicKey publicKey = CryptManager.decodePublicKey(getPublicKey());
String serverHash = new BigInteger(CryptManager.getServerHash(getServerId(), publicKey, secretKey)).toString(16); 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)); connection.sendPacket(new PacketEncryptionResponse(secretKey, getVerifyToken(), publicKey));
} }

View File

@ -16,8 +16,6 @@ package de.bixilon.minosoft.util;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.logging.LogLevels;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@ -27,32 +25,22 @@ import java.net.http.HttpResponse;
public final class HTTP { public final class HTTP {
public static HttpResponse<String> postJson(String url, JsonObject json) { public static HttpResponse<String> postJson(String url, JsonObject json) throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).POST(HttpRequest.BodyPublishers.ofString(json.toString())).header("Content-Type", "application/json").build(); 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());
return client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (Exception e) {
Log.printException(e, LogLevels.DEBUG);
}
return null;
} }
public static HttpResponse<String> get(String url) { public static HttpResponse<String> get(String url) throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build(); HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build();
try { return client.send(request, HttpResponse.BodyHandlers.ofString());
return client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
return null;
} }
public static JsonElement getJson(String url) { public static JsonElement getJson(String url) throws IOException, InterruptedException {
HttpResponse<String> response = get(url); HttpResponse<String> response = get(url);
if (response == null || response.statusCode() != 200) { if (response.statusCode() != 200) {
return null; throw new IOException();
} }
return JsonParser.parseString(response.body()); return JsonParser.parseString(response.body());
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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
}
}

View File

@ -18,19 +18,25 @@ import com.google.gson.JsonParser;
import de.bixilon.minosoft.Minosoft; import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.config.ConfigurationPaths; import de.bixilon.minosoft.config.ConfigurationPaths;
import de.bixilon.minosoft.config.StaticConfiguration; import de.bixilon.minosoft.config.StaticConfiguration;
import de.bixilon.minosoft.data.accounts.MojangAccount;
import de.bixilon.minosoft.logging.Log; import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.logging.LogLevels;
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition; import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
import de.bixilon.minosoft.util.HTTP; 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; import java.net.http.HttpResponse;
public final class MojangAuthentication { 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); 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(); JsonObject agent = new JsonObject();
agent.addProperty("name", "Minecraft"); agent.addProperty("name", "Minecraft");
agent.addProperty("version", 1); agent.addProperty("version", 1);
@ -42,22 +48,28 @@ public final class MojangAuthentication {
payload.addProperty("clientToken", clientToken); payload.addProperty("clientToken", clientToken);
payload.addProperty("requestUser", true); payload.addProperty("requestUser", true);
HttpResponse<String> response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_LOGIN, payload); HttpResponse<String> 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) { if (response == null) {
Log.mojang(String.format("Failed to login with username %s", username)); 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(); JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject();
if (response.statusCode() != 200) { if (response.statusCode() != 200) {
Log.mojang(String.format("Failed to login with error code %d: %s", response.statusCode(), jsonResponse.get("errorMessage").getAsString())); 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 // 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) { if (StaticConfiguration.SKIP_MOJANG_AUTHENTICATION) {
return; return;
} }
@ -67,26 +79,31 @@ public final class MojangAuthentication {
payload.addProperty("selectedProfile", account.getUUID().toString().replace("-", "")); payload.addProperty("selectedProfile", account.getUUID().toString().replace("-", ""));
payload.addProperty("serverId", serverId); payload.addProperty("serverId", serverId);
HttpResponse<String> response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_JOIN, payload); HttpResponse<String> response;
try {
response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_JOIN, payload);
} catch (IOException | InterruptedException e) {
throw new NoNetworkConnectionException(e);
}
if (response == null) { if (response == null) {
Log.mojang(String.format("Failed to join server: %s", serverId)); Log.mojang(String.format("Failed to join server: %s", serverId));
return; throw new MojangJoinServerErrorException();
} }
if (response.statusCode() != 204) { if (response.statusCode() != 204) {
JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); 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")); 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 // joined
Log.mojang("Joined server successfully"); 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); 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) { if (StaticConfiguration.SKIP_MOJANG_AUTHENTICATION) {
return clientToken; return clientToken;
} }
@ -97,18 +114,18 @@ public final class MojangAuthentication {
HttpResponse<String> response; HttpResponse<String> response;
try { try {
response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_REFRESH, payload); 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())); Log.mojang(String.format("Could not connect to mojang server: %s", e.getCause().toString()));
return null; throw new NoNetworkConnectionException(e);
} }
if (response == null) { if (response == null) {
Log.mojang("Failed to refresh session"); Log.mojang("Failed to refresh session");
return null; throw new NoNetworkConnectionException();
} }
JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject();
if (response.statusCode() != 200) { if (response.statusCode() != 200) {
Log.mojang(String.format("Failed to refresh session with error code %d: %s", response.statusCode(), jsonResponse.get("errorMessage").getAsString())); 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 // now it is okay
Log.mojang("Refreshed 1 session token"); Log.mojang("Refreshed 1 session token");

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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<String> getBlockedServers() {
HttpResponse<String> 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<String> list, String hostname) {
for (String hash : list) {
if (hash.equals(Util.sha1(hostname))) {
return true;
}
// ToDo: check subdomains and ip addresses
}
return false;
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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() {
}
}

View File

@ -11,31 +11,19 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft. * 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 { public class NoNetworkConnectionException extends Exception {
final MojangAccount account;
final String error;
public MojangAccountAuthenticationAttempt(MojangAccount account) { public NoNetworkConnectionException(Exception cause) {
this.account = account; super(cause);
this.error = null;
} }
public MojangAccountAuthenticationAttempt(String error) { public NoNetworkConnectionException(String message) {
this.account = null; super(message);
this.error = error;
} }
public String getError() { public NoNetworkConnectionException() {
return this.error;
}
public MojangAccount getAccount() {
return this.account;
}
public boolean succeeded() {
return this.account != null;
} }
} }

View File

@ -56,7 +56,6 @@
"SETTINGS_DOWNLOAD": "Download", "SETTINGS_DOWNLOAD": "Download",
"LOGIN_DIALOG_TITLE": "Anmelden (Mojang) - Minosoft", "LOGIN_DIALOG_TITLE": "Anmelden (Mojang) - Minosoft",
"LOGIN_DIALOG_HEADER": "Bitte gib deine Mojang Zugangsdaten ein dich anzumelden", "LOGIN_DIALOG_HEADER": "Bitte gib deine Mojang Zugangsdaten ein dich anzumelden",
"LOGIN_ACCOUNT_ALREADY_PRESENT": "Account existiert bereits!",
"ERROR": "Fehler", "ERROR": "Fehler",
"MINOSOFT_STILL_STARTING_TITLE": "Bitte warten", "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...", "MINOSOFT_STILL_STARTING_HEADER": "Minosoft muss noch ein paar Dinge erledigen, bevor du es verwenden kannst.\nBitte warte noch ein paar Sekunden...",

View File

@ -57,7 +57,6 @@
"SETTINGS_DOWNLOAD": "Download", "SETTINGS_DOWNLOAD": "Download",
"LOGIN_DIALOG_TITLE": "Login (Mojang) - Minosoft", "LOGIN_DIALOG_TITLE": "Login (Mojang) - Minosoft",
"LOGIN_DIALOG_HEADER": "Please login to your mojang account", "LOGIN_DIALOG_HEADER": "Please login to your mojang account",
"LOGIN_ACCOUNT_ALREADY_PRESENT": "Account is already present!",
"ERROR": "Error", "ERROR": "Error",
"MINOSOFT_STILL_STARTING_TITLE": "Please wait", "MINOSOFT_STILL_STARTING_TITLE": "Please wait",
"MINOSOFT_STILL_STARTING_HEADER": "Minosoft is still starting up...", "MINOSOFT_STILL_STARTING_HEADER": "Minosoft is still starting up...",

View File

@ -1,7 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<!-- <!--
~ Minosoft ~ Minosoft
~ Copyright (C) 2020 Moritz Zwerger ~ Copyright (C) 2020 Moritz Zwerger
@ -14,6 +11,9 @@
~ ~
~ This software is not affiliated with Mojang AB, the original developer of Minecraft. ~ This software is not affiliated with Mojang AB, the original developer of Minecraft.
--> -->
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<AnchorPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:id="root" maxHeight="-Infinity" maxWidth="500.0" minHeight="-Infinity" minWidth="500.0" onMouseClicked="#clicked" prefWidth="500.0" fx:controller="de.bixilon.minosoft.gui.main.AccountListCell"> <AnchorPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:id="root" maxHeight="-Infinity" maxWidth="500.0" minHeight="-Infinity" minWidth="500.0" onMouseClicked="#clicked" prefWidth="500.0" fx:controller="de.bixilon.minosoft.gui.main.AccountListCell">
<Label fx:id="playerName" layoutX="111.0" layoutY="14.0" maxWidth="200.0" minWidth="10.0" text="#Player name#" underline="true" AnchorPane.leftAnchor="5.0" AnchorPane.topAnchor="5.0"> <Label fx:id="playerName" layoutX="111.0" layoutY="14.0" maxWidth="200.0" minWidth="10.0" text="#Player name#" underline="true" AnchorPane.leftAnchor="5.0" AnchorPane.topAnchor="5.0">
<font> <font>
@ -23,7 +23,7 @@
<MenuButton fx:id="optionsMenu" layoutX="389.0" layoutY="81.0" mnemonicParsing="false" text="⋮" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0"> <MenuButton fx:id="optionsMenu" layoutX="389.0" layoutY="81.0" mnemonicParsing="false" text="⋮" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0">
<items> <items>
<MenuItem fx:id="optionsSelect" mnemonicParsing="false" onAction="#select" text="-Select-" /> <MenuItem fx:id="optionsSelect" mnemonicParsing="false" onAction="#select" text="-Select-" />
<MenuItem fx:id="optionsDelete" mnemonicParsing="false" onAction="#delete" style="-fx-text-fill: red" text="-Delete-" /> <MenuItem fx:id="optionsDelete" mnemonicParsing="false" onAction="#logout" style="-fx-text-fill: red" text="-Delete-"/>
</items> </items>
</MenuButton> </MenuButton>
<Label fx:id="email" layoutX="121.0" layoutY="24.0" maxWidth="200.0" minWidth="10.0" text="#Email#" AnchorPane.leftAnchor="5.0" AnchorPane.topAnchor="30.0" /> <Label fx:id="email" layoutX="121.0" layoutY="24.0" maxWidth="200.0" minWidth="10.0" text="#Email#" AnchorPane.leftAnchor="5.0" AnchorPane.topAnchor="30.0" />