diff --git a/src/main/java/de/bixilon/minosoft/gui/eros/main/MainErosController.kt b/src/main/java/de/bixilon/minosoft/gui/eros/main/MainErosController.kt index 19fd4f22f..edb60d0de 100644 --- a/src/main/java/de/bixilon/minosoft/gui/eros/main/MainErosController.kt +++ b/src/main/java/de/bixilon/minosoft/gui/eros/main/MainErosController.kt @@ -79,7 +79,7 @@ class MainErosController : JavaFXWindowController() { override fun init() { logoFX.image = JavaFXUtil.MINOSOFT_LOGO - versionTextFX.text = "Minosoft " + GitInfo.IS_INITIALIZED.decide(GitInfo.GIT_COMMIT_ID, StaticConfiguration.VERSION) + versionTextFX.text = "Minosoft " + GitInfo.IS_INITIALIZED.decide(GitInfo.GIT_COMMIT_ID_ABBREV, StaticConfiguration.VERSION) iconMap = mapOf( ErosMainActivities.PlAY to playIconFX, ErosMainActivities.SETTINGS to settingsIconFX, diff --git a/src/main/java/de/bixilon/minosoft/util/HTTP.java b/src/main/java/de/bixilon/minosoft/util/HTTP.java deleted file mode 100644 index 9812ea1fd..000000000 --- a/src/main/java/de/bixilon/minosoft/util/HTTP.java +++ /dev/null @@ -1,86 +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; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.HashMap; - -@Deprecated -public final class HTTP { - - public static HttpResponse postJson(String url, String json, HashMap headers) throws IOException, InterruptedException { - headers.put("Content-Type", "application/json"); - headers.put("Accept", "application/json"); - - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .POST(HttpRequest.BodyPublishers.ofString(json)) - .headers(Util.headersMapToArray(headers)) - .build(); - return client.send(request, HttpResponse.BodyHandlers.ofString()); - } - - public static HttpResponse postJson(String url, JsonObject json) throws IOException, InterruptedException { - return postJson(url, Util.GSON.toJson(json), new HashMap<>()); - } - - public static HttpResponse postJson(String url, String json) throws IOException, InterruptedException { - return postJson(url, json, new HashMap<>()); - } - - public static HttpResponse get(String url, HashMap headers) throws IOException, InterruptedException { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .headers(Util.headersMapToArray(headers)) - .build(); - return client.send(request, HttpResponse.BodyHandlers.ofString()); - } - - public static HttpResponse get(String url) throws IOException, InterruptedException { - return get(url, new HashMap<>()); - } - - - public static JsonElement getJson(String url, HashMap headers) throws IOException, InterruptedException { - HttpResponse response = get(url, headers); - if (response.statusCode() != 200) { - throw new IOException(); - } - return JsonParser.parseString(response.body()); - } - - public static JsonElement getJson(String url) throws IOException, InterruptedException { - return getJson(url, new HashMap<>()); - } - - public static HttpResponse postData(String url, HashMap data) throws IOException, InterruptedException { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .POST(HttpRequest.BodyPublishers.ofString(Util.mapToUrlQuery(data))) - .header("Content-Type", "application/x-www-form-urlencoded") - .build(); - return client.send(request, HttpResponse.BodyHandlers.ofString()); - } -} diff --git a/src/main/java/de/bixilon/minosoft/util/KUtil.kt b/src/main/java/de/bixilon/minosoft/util/KUtil.kt index fdbcbfb07..b7aa48abd 100644 --- a/src/main/java/de/bixilon/minosoft/util/KUtil.kt +++ b/src/main/java/de/bixilon/minosoft/util/KUtil.kt @@ -282,6 +282,10 @@ object KUtil { return this.nullCast() } + fun Any?.asList(): List { + return this.unsafeCast() + } + fun Any.toJson(beautiful: Boolean = false, adapter: JsonAdapter = JSONSerializer.ANY_ADAPTER): String { val buffer = Buffer() val jsonWriter: JsonWriter = JsonWriter.of(buffer) @@ -308,6 +312,15 @@ object KUtil { } } + fun Any?.toLong(): Long { + return when (this) { + is Long -> this + is Number -> this.toLong() + is Int -> this.toLong() + else -> TODO() + } + } + fun Any?.toDouble(): Double { return when (this) { is Double -> this diff --git a/src/main/java/de/bixilon/minosoft/util/http/HTTP2.kt b/src/main/java/de/bixilon/minosoft/util/http/HTTP2.kt index 4feddd611..e2a7c4cdf 100644 --- a/src/main/java/de/bixilon/minosoft/util/http/HTTP2.kt +++ b/src/main/java/de/bixilon/minosoft/util/http/HTTP2.kt @@ -59,4 +59,26 @@ object HTTP2 { ) ) } + + fun String.get(bodyBuilder: (String) -> Response, headers: Map = mapOf()): HTTPResponse { + val client = HttpClient.newHttpClient() + val request = HttpRequest.newBuilder() + .uri(URI.create(this)) + .GET() + .headers(*headers.headers()) + .build() + + val response = client.send(request, HttpResponse.BodyHandlers.ofString()) + return HTTPResponse(response.statusCode(), bodyBuilder(response.body())) + } + + fun String.getJson(headers: Map = mapOf()): HTTPResponse?> { + return this.get( + bodyBuilder = { it.isBlank().decide(null) { JSONSerializer.MAP_ADAPTER.fromJson(it) } }, + headers = headers.extend( + "Content-Type" to "application/json", + "Accept" to "application/json", + ) + ) + } } diff --git a/src/main/java/de/bixilon/minosoft/util/microsoft/MicrosoftOAuthUtils.kt b/src/main/java/de/bixilon/minosoft/util/microsoft/MicrosoftOAuthUtils.kt index 5468aac9a..738ca7543 100644 --- a/src/main/java/de/bixilon/minosoft/util/microsoft/MicrosoftOAuthUtils.kt +++ b/src/main/java/de/bixilon/minosoft/util/microsoft/MicrosoftOAuthUtils.kt @@ -13,13 +13,17 @@ package de.bixilon.minosoft.util.microsoft -import com.google.gson.JsonParser import de.bixilon.minosoft.data.accounts.types.MicrosoftAccount import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition -import de.bixilon.minosoft.terminal.RunConfiguration -import de.bixilon.minosoft.util.HTTP -import de.bixilon.minosoft.util.Util +import de.bixilon.minosoft.util.KUtil.asList +import de.bixilon.minosoft.util.KUtil.toLong +import de.bixilon.minosoft.util.KUtil.unsafeCast +import de.bixilon.minosoft.util.http.HTTP2.getJson +import de.bixilon.minosoft.util.http.HTTP2.postJson import de.bixilon.minosoft.util.logging.Log +import de.bixilon.minosoft.util.logging.LogLevels +import de.bixilon.minosoft.util.logging.LogMessageType +import de.bixilon.minosoft.util.nbt.tag.NBTUtil.asCompound import java.net.URLConnection object MicrosoftOAuthUtils { @@ -27,69 +31,34 @@ object MicrosoftOAuthUtils { override fun connect() {} } - fun loginToMicrosoftAccount(authorizationCode: String) { - Log.verbose("Logging into microsoft account...") - try { - val authorizationToken = getAuthorizationToken(authorizationCode) - val xboxLiveToken = getXboxLiveToken(authorizationToken) - val xstsToken = getXSTSToken(xboxLiveToken.first) + fun loginToMicrosoftAccount(authorizationCode: String): MicrosoftAccount { + Log.log(LogMessageType.AUTHENTICATION, LogLevels.INFO) { "Logging into microsoft account..." } + val authorizationToken = getAuthorizationToken(authorizationCode) + val xboxLiveToken = getXboxLiveToken(authorizationToken) + val xstsToken = getXSTSToken(xboxLiveToken.first) - val microsoftAccount = getMicrosoftAccount(getMinecraftAccessToken(xboxLiveToken.second, xstsToken)) - // ToDo: Account.addAccount(microsoftAccount) - } catch (exception: Exception) { - Log.warn("Can not login into microsoft account") - exception.printStackTrace() - - if (RunConfiguration.DISABLE_EROS) { - return - } - - var message = "Could not login!" - var errorMessage = exception.javaClass.canonicalName + ": " + exception.message - if (exception is LoginException) { - message = "${exception.message} (${exception.errorCode})" - errorMessage = exception.errorMessage - } - - // Platform.runLater { - // val dialog = JFXAlert() - // // ToDo: GUITools.initializePane(dialog.dialogPane) - // // Do not translate this, translations might fail to load... - // dialog.title = "Login error" - // val layout = JFXDialogLayout() - // layout.setHeading(Text(message)) - // val text = TextArea(errorMessage) - // text.isEditable = false - // text.isWrapText = true - // layout.setBody(text) - // dialog.dialogPane.content = layout - // val stage = dialog.dialogPane.scene.window as Stage - // stage.toFront() - // dialog.show() - // } - } + return getMicrosoftAccount(getMinecraftAccessToken(xboxLiveToken.second, xstsToken)) } fun getAuthorizationToken(authorizationCode: String): String { - val data = mapOf( + val response = mapOf( "client_id" to ProtocolDefinition.MICROSOFT_ACCOUNT_APPLICATION_ID, "code" to authorizationCode, "grant_type" to "authorization_code", "scope" to "service::user.auth.xboxlive.com::MBI_SSL", - ) - val response = HTTP.postData(ProtocolDefinition.MICROSOFT_ACCOUNT_AUTH_TOKEN_URL, HashMap(data)) - if (response.statusCode() != 200) { - throw LoginException(response.statusCode(), "Could not get authorization token ", response.body()) + ).postJson(ProtocolDefinition.MICROSOFT_ACCOUNT_AUTH_TOKEN_URL) + if (response.statusCode != 200) { + throw LoginException(response.statusCode, "Could not get authorization token ", response.body.toString()) } - val body = JsonParser.parseString(response.body()).asJsonObject - return body["access_token"]!!.asString + response.body!! + return response.body["access_token"].unsafeCast() } /** * returns A: XBL Token; B: UHS Token */ fun getXboxLiveToken(authorizationToken: String): Pair { - val payload = mapOf( + val response = mapOf( "Properties" to mapOf( "AuthMethod" to "RPS", "SiteName" to "user.auth.xboxlive.com", @@ -97,68 +66,63 @@ object MicrosoftOAuthUtils { ), "RelyingParty" to "http://auth.xboxlive.com", "TokenType" to "JWT", - ) - val response = HTTP.postJson(ProtocolDefinition.MICROSOFT_ACCOUNT_XBOX_LIVE_AUTHENTICATE_URL, Util.GSON.toJson(payload)) + ).postJson(ProtocolDefinition.MICROSOFT_ACCOUNT_XBOX_LIVE_AUTHENTICATE_URL) - if (response.statusCode() != 200) { - throw LoginException(response.statusCode(), "Could not get authenticate against xbox live ", response.body()) + response.body!! + if (response.statusCode != 200) { + throw LoginException(response.statusCode, "Could not get authenticate against xbox live ", response.body.toString()) } - val body = JsonParser.parseString(response.body()).asJsonObject - return Pair(body["Token"]!!.asString, body["DisplayClaims"].asJsonObject["xui"].asJsonArray[0].asJsonObject["uhs"].asString) + return Pair(response.body["Token"].unsafeCast(), response.body["DisplayClaims"].asCompound()["xui"].asList()[0].asCompound()["uhs"].unsafeCast()) } fun getXSTSToken(xBoxLiveToken: String): String { - val payload = mapOf( + val response = mapOf( "Properties" to mapOf( "SandboxId" to "RETAIL", "UserTokens" to listOf(xBoxLiveToken) ), "RelyingParty" to "rp://api.minecraftservices.com/", "TokenType" to "JWT", - ) - val response = HTTP.postJson(ProtocolDefinition.MICROSOFT_ACCOUNT_XSTS_URL, Util.GSON.toJson(payload)) + ).postJson(ProtocolDefinition.MICROSOFT_ACCOUNT_XSTS_URL) - if (response.statusCode() != 200) { - val error = JsonParser.parseString(response.body()).asJsonObject - val errorMessage = when (error["XErr"].asLong) { + response.body!! + if (response.statusCode != 200) { + val errorMessage = when (response.body["XErr"].toLong()) { 2148916233 -> "You don't have an XBox account!" 2148916238 -> "This account is a child account!" - else -> error["Message"].asString + else -> response.body["Message"].unsafeCast() } - throw LoginException(response.statusCode(), "Could not get authenticate against XSTS token ", errorMessage) + throw LoginException(response.statusCode, "Could not get authenticate against XSTS token ", errorMessage) } - val body = JsonParser.parseString(response.body()).asJsonObject - return body["Token"].asString!! + return response.body["Token"].unsafeCast() } fun getMinecraftAccessToken(uhs: String, xstsToken: String): String { - val payload = mapOf( + val response = mapOf( "identityToken" to "XBL3.0 x=${uhs};${xstsToken}" - ) - val response = HTTP.postJson(ProtocolDefinition.MICROSOFT_ACCOUNT_MINECRAFT_LOGIN_WITH_XBOX_URL, Util.GSON.toJson(payload)) + ).postJson(ProtocolDefinition.MICROSOFT_ACCOUNT_MINECRAFT_LOGIN_WITH_XBOX_URL) - if (response.statusCode() != 200) { - val error = JsonParser.parseString(response.body()).asJsonObject - throw LoginException(response.statusCode(), "Could not get minecraft access token ", error["errorMessage"].asString) + response.body!! + if (response.statusCode != 200) { + throw LoginException(response.statusCode, "Could not get minecraft access token ", response.body["errorMessage"].unsafeCast()) } - val body = JsonParser.parseString(response.body()).asJsonObject - return body["access_token"].asString!! + return response.body["access_token"].unsafeCast() } fun getMicrosoftAccount(bearerToken: String): MicrosoftAccount { - val response = HTTP.get(ProtocolDefinition.MICROSOFT_ACCOUNT_GET_MOJANG_PROFILE_URL, HashMap(mapOf( + val response = ProtocolDefinition.MICROSOFT_ACCOUNT_GET_MOJANG_PROFILE_URL.getJson(mapOf( "Authorization" to "Bearer $bearerToken" - ))) + )) - if (response.statusCode() != 200) { - val errorMessage = when (response.statusCode()) { + response.body!! + if (response.statusCode != 200) { + val errorMessage = when (response.statusCode) { 404 -> "You don't have a copy of minecraft!" - else -> JsonParser.parseString(response.body()).asJsonObject["errorMessage"].asString + else -> response.body["errorMessage"].unsafeCast() } - throw LoginException(response.statusCode(), "Could not get minecraft profile", errorMessage) + throw LoginException(response.statusCode, "Could not get minecraft profile", errorMessage) } - val body = JsonParser.parseString(response.body()).asJsonObject // return MicrosoftAccount(bearerToken, body["id"].asString!!, Util.getUUIDFromString(body["id"].asString!!), body["name"].asString!!) TODO() }