remove HTTP, refactor MicrosoftOAuthUtils

This commit is contained in:
Bixilon 2021-08-02 15:31:07 +02:00
parent 3bf80ccea3
commit 854e0bcd8f
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
5 changed files with 83 additions and 170 deletions

View File

@ -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,

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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<String> postJson(String url, String json, HashMap<String, String> 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<String> postJson(String url, JsonObject json) throws IOException, InterruptedException {
return postJson(url, Util.GSON.toJson(json), new HashMap<>());
}
public static HttpResponse<String> postJson(String url, String json) throws IOException, InterruptedException {
return postJson(url, json, new HashMap<>());
}
public static HttpResponse<String> get(String url, HashMap<String, String> 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<String> get(String url) throws IOException, InterruptedException {
return get(url, new HashMap<>());
}
public static JsonElement getJson(String url, HashMap<String, String> headers) throws IOException, InterruptedException {
HttpResponse<String> 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<String> postData(String url, HashMap<String, String> 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());
}
}

View File

@ -282,6 +282,10 @@ object KUtil {
return this.nullCast()
}
fun Any?.asList(): List<Any> {
return this.unsafeCast()
}
fun Any.toJson(beautiful: Boolean = false, adapter: JsonAdapter<Any> = 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

View File

@ -59,4 +59,26 @@ object HTTP2 {
)
)
}
fun <Response> String.get(bodyBuilder: (String) -> Response, headers: Map<String, Any> = mapOf()): HTTPResponse<Response> {
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<String, Any> = mapOf()): HTTPResponse<Map<String, Any>?> {
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",
)
)
}
}

View File

@ -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<Boolean>()
// // 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<String, String> {
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()
}