From 6782602141427b410146ac025cb0ad3704ee64a7 Mon Sep 17 00:00:00 2001 From: yushijinhun Date: Thu, 7 Jun 2018 21:12:38 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84YggdrasilService=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jackhuang/hmcl/game/LauncherHelper.java | 2 +- .../java/org/jackhuang/hmcl/auth/Account.java | 2 - .../org/jackhuang/hmcl/auth/AuthInfo.java | 31 ++- .../AuthlibInjectorAccount.java | 6 +- .../AuthlibInjectorAccountFactory.java | 8 +- .../hmcl/auth/offline/OfflineAccount.java | 28 ++- .../auth/offline/OfflineAccountFactory.java | 14 +- .../yggdrasil/AuthenticationResponse.java | 66 ------ .../hmcl/auth/yggdrasil/ErrorResponse.java | 29 --- .../hmcl/auth/yggdrasil/GameProfile.java | 3 + .../hmcl/auth/yggdrasil/TextureResponse.java | 53 ----- .../hmcl/auth/yggdrasil/YggdrasilAccount.java | 36 +-- .../yggdrasil/YggdrasilAccountFactory.java | 6 +- .../hmcl/auth/yggdrasil/YggdrasilService.java | 216 ++++++++---------- .../hmcl/auth/yggdrasil/YggdrasilSession.java | 108 ++++----- .../hmcl/launch/DefaultLauncher.java | 2 +- 16 files changed, 213 insertions(+), 397 deletions(-) delete mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationResponse.java delete mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/ErrorResponse.java delete mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/TextureResponse.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 32e2acaac..1f0bd4a9a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -302,7 +302,7 @@ public final class LauncherHelper { else forbiddenTokens = mapOf( pair(authInfo.getAccessToken(), ""), - pair(authInfo.getUserId(), ""), + pair(UUIDTypeAdapter.fromUUID(authInfo.getUUID()), ""), pair(authInfo.getUsername(), "") ); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java index 389a64d1f..708a523df 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java @@ -61,8 +61,6 @@ public abstract class Account { */ public abstract AuthInfo playOffline(); - public abstract void logOut(); - public abstract Map toStorage(); public abstract void clearCache(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.java index e878ce602..92cfea787 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.java @@ -17,6 +17,8 @@ */ package org.jackhuang.hmcl.auth; +import java.util.UUID; + import org.jackhuang.hmcl.game.Arguments; import org.jackhuang.hmcl.util.Immutable; @@ -28,27 +30,19 @@ import org.jackhuang.hmcl.util.Immutable; public final class AuthInfo { private final String username; - private final String userId; + private final UUID uuid; private final String accessToken; private final UserType userType; private final String userProperties; private final Arguments arguments; - public AuthInfo(String username, String userId, String accessToken) { - this(username, userId, accessToken, UserType.LEGACY); + public AuthInfo(String username, UUID uuid, String accessToken, UserType userType, String userProperties) { + this(username, uuid, accessToken, userType, userProperties, null); } - public AuthInfo(String username, String userId, String accessToken, UserType userType) { - this(username, userId, accessToken, userType, "{}"); - } - - public AuthInfo(String username, String userId, String accessToken, UserType userType, String userProperties) { - this(username, userId, accessToken, userType, userProperties, null); - } - - public AuthInfo(String username, String userId, String accessToken, UserType userType, String userProperties, Arguments arguments) { + public AuthInfo(String username, UUID uuid, String accessToken, UserType userType, String userProperties, Arguments arguments) { this.username = username; - this.userId = userId; + this.uuid = uuid; this.accessToken = accessToken; this.userType = userType; this.userProperties = userProperties; @@ -59,8 +53,8 @@ public final class AuthInfo { return username; } - public String getUserId() { - return userId; + public UUID getUUID() { + return uuid; } public String getAccessToken() { @@ -81,11 +75,14 @@ public final class AuthInfo { return userProperties; } + /** + * @return null if no argument is specified + */ public Arguments getArguments() { return arguments; } - public AuthInfo setArguments(Arguments arguments) { - return new AuthInfo(username, userId, accessToken, userType, userProperties, arguments); + public AuthInfo withArguments(Arguments arguments) { + return new AuthInfo(username, uuid, accessToken, userType, userProperties, arguments); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccount.java index 45f08f6f8..9fda11c6d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccount.java @@ -39,8 +39,8 @@ public class AuthlibInjectorAccount extends YggdrasilAccount { private final String serverBaseURL; private final ExceptionalSupplier injectorJarPath; - protected AuthlibInjectorAccount(YggdrasilService service, String serverBaseURL, ExceptionalSupplier injectorJarPath, String username, String clientToken, String character, YggdrasilSession session) { - super(service, username, clientToken, character, session); + protected AuthlibInjectorAccount(YggdrasilService service, String serverBaseURL, ExceptionalSupplier injectorJarPath, String username, String character, YggdrasilSession session) { + super(service, username, character, session); this.injectorJarPath = injectorJarPath; this.serverBaseURL = serverBaseURL; @@ -72,7 +72,7 @@ public class AuthlibInjectorAccount extends YggdrasilAccount { if (flag.get()) arguments = Arguments.addJVMArguments(arguments, "-Dorg.to2mbn.authlibinjector.config.prefetched=" + new String(Base64.getEncoder().encode(getTask.getResult().getBytes()), UTF_8)); - return info.setArguments(arguments); + return info.withArguments(arguments); } catch (Exception e) { throw new AuthenticationException("Unable to get authlib injector jar path", e); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccountFactory.java index 92abd7647..ebdbcb5c9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccountFactory.java @@ -7,12 +7,10 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession; import org.jackhuang.hmcl.util.ExceptionalSupplier; import org.jackhuang.hmcl.util.NetworkUtils; -import org.jackhuang.hmcl.util.UUIDTypeAdapter; import java.net.Proxy; import java.util.Map; import java.util.Objects; -import java.util.UUID; import static org.jackhuang.hmcl.util.Lang.tryCast; @@ -34,7 +32,7 @@ public class AuthlibInjectorAccountFactory extends AccountFactory new IllegalArgumentException("storage does not have username")); - String clientToken = tryCast(storage.get("clientToken"), String.class) - .orElseThrow(() -> new IllegalArgumentException("storage does not have client token.")); String character = tryCast(storage.get("clientToken"), String.class) .orElseThrow(() -> new IllegalArgumentException("storage does not have selected character name.")); String apiRoot = tryCast(storage.get("serverBaseURL"), String.class) .orElseThrow(() -> new IllegalArgumentException("storage does not have API root.")); return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(apiRoot), proxy), - apiRoot, injectorJarPathSupplier, username, clientToken, character, YggdrasilSession.fromStorage(storage)); + apiRoot, injectorJarPathSupplier, username, character, YggdrasilSession.fromStorage(storage)); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccount.java index 4bce482b9..bccbe9dcd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccount.java @@ -20,15 +20,18 @@ package org.jackhuang.hmcl.auth.offline; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; -import org.jackhuang.hmcl.auth.Account; -import org.jackhuang.hmcl.auth.AuthInfo; -import org.jackhuang.hmcl.auth.AuthenticationException; -import org.jackhuang.hmcl.util.*; - import java.util.Map; import java.util.Objects; import java.util.UUID; +import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.auth.AuthInfo; +import org.jackhuang.hmcl.auth.AuthenticationException; +import org.jackhuang.hmcl.auth.UserType; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.ToStringBuilder; +import org.jackhuang.hmcl.util.UUIDTypeAdapter; + /** * * @author huang @@ -36,9 +39,9 @@ import java.util.UUID; public class OfflineAccount extends Account { private final String username; - private final String uuid; + private final UUID uuid; - OfflineAccount(String username, String uuid) { + OfflineAccount(String username, UUID uuid) { Objects.requireNonNull(username); Objects.requireNonNull(uuid); @@ -51,7 +54,7 @@ public class OfflineAccount extends Account { @Override public UUID getUUID() { - return UUIDTypeAdapter.fromString(uuid); + return uuid; } @Override @@ -69,7 +72,7 @@ public class OfflineAccount extends Account { if (StringUtils.isBlank(username)) throw new AuthenticationException("Username cannot be empty"); - return new AuthInfo(username, uuid, uuid); + return new AuthInfo(username, uuid, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), UserType.MOJANG, "{}"); } @Override @@ -77,11 +80,6 @@ public class OfflineAccount extends Account { return logIn(); } - @Override - public void logOut() { - // Offline account need not log out. - } - @Override public boolean canPlayOffline() { return false; @@ -95,7 +93,7 @@ public class OfflineAccount extends Account { @Override public Map toStorage() { return mapOf( - pair("uuid", uuid), + pair("uuid", UUIDTypeAdapter.fromUUID(uuid)), pair("username", username) ); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccountFactory.java index 6672c6498..a1ac4f56b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccountFactory.java @@ -23,9 +23,9 @@ import org.jackhuang.hmcl.util.UUIDTypeAdapter; import java.net.Proxy; import java.util.Map; +import java.util.UUID; -import static org.jackhuang.hmcl.util.DigestUtils.digest; -import static org.jackhuang.hmcl.util.Hex.encodeHex; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.jackhuang.hmcl.util.Lang.tryCast; /** @@ -47,17 +47,15 @@ public class OfflineAccountFactory extends AccountFactory { public OfflineAccount fromStorage(Map storage, Proxy proxy) { String username = tryCast(storage.get("username"), String.class) .orElseThrow(() -> new IllegalStateException("Offline account configuration malformed.")); - String uuid = tryCast(storage.get("uuid"), String.class) + UUID uuid = tryCast(storage.get("uuid"), String.class) + .map(UUIDTypeAdapter::fromString) .orElse(getUUIDFromUserName(username)); - // Check if the uuid is vaild - UUIDTypeAdapter.fromString(uuid); - return new OfflineAccount(username, uuid); } - private static String getUUIDFromUserName(String username) { - return encodeHex(digest("MD5", username)); + private static UUID getUUIDFromUserName(String username) { + return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(UTF_8)); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationResponse.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationResponse.java deleted file mode 100644 index 3b475a54d..000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationResponse.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2018 huangyuhui - * - * 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 {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth.yggdrasil; - -/** - * - * @author huangyuhui - */ -final class AuthenticationResponse extends ErrorResponse { - - private final String accessToken; - private final String clientToken; - private final GameProfile selectedProfile; - private final GameProfile[] availableProfiles; - private final User user; - - public AuthenticationResponse() { - this(null, null, null, null, null, null, null, null); - } - - public AuthenticationResponse(String accessToken, String clientToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user, String error, String errorMessage, String cause) { - super(error, errorMessage, cause); - - this.accessToken = accessToken; - this.clientToken = clientToken; - this.selectedProfile = selectedProfile; - this.availableProfiles = availableProfiles; - this.user = user; - } - - public String getAccessToken() { - return accessToken; - } - - public String getClientToken() { - return clientToken; - } - - public GameProfile getSelectedProfile() { - return selectedProfile; - } - - public GameProfile[] getAvailableProfiles() { - return availableProfiles; - } - - public User getUser() { - return user; - } - -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/ErrorResponse.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/ErrorResponse.java deleted file mode 100644 index 3327a486e..000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/ErrorResponse.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.jackhuang.hmcl.auth.yggdrasil; - -class ErrorResponse { - private final String error; - private final String errorMessage; - private final String cause; - - public ErrorResponse() { - this(null, null, null); - } - - public ErrorResponse(String error, String errorMessage, String cause) { - this.error = error; - this.errorMessage = errorMessage; - this.cause = cause; - } - - public String getError() { - return error; - } - - public String getErrorMessage() { - return errorMessage; - } - - public String getCause() { - return cause; - } -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/GameProfile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/GameProfile.java index 8ea3f4d45..2da491eea 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/GameProfile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/GameProfile.java @@ -63,6 +63,9 @@ public final class GameProfile { return name; } + /** + * @return nullable + */ public PropertyMap getProperties() { return properties; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/TextureResponse.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/TextureResponse.java deleted file mode 100644 index 4e383859b..000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/TextureResponse.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2018 huangyuhui - * - * 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 {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth.yggdrasil; - -import org.jackhuang.hmcl.util.Immutable; - -import java.util.Collections; -import java.util.Map; -import java.util.UUID; - -@Immutable -public final class TextureResponse { - private final UUID profileId; - private final String profileName; - private final Map textures; - - public TextureResponse() { - this(UUID.randomUUID(), "", Collections.emptyMap()); - } - - public TextureResponse(UUID profileId, String profileName, Map textures) { - this.profileId = profileId; - this.profileName = profileName; - this.textures = textures; - } - - public UUID getProfileId() { - return profileId; - } - - public String getProfileName() { - return profileName; - } - - public Map getTextures() { - return textures == null ? null : Collections.unmodifiableMap(textures); - } -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java index 200449925..4b7fd3135 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.auth.yggdrasil; -import com.google.gson.GsonBuilder; import org.jackhuang.hmcl.auth.*; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.UUIDTypeAdapter; @@ -34,14 +33,12 @@ public class YggdrasilAccount extends Account { private final YggdrasilService service; private boolean isOnline = false; private YggdrasilSession session; - private final String clientToken; private String character; - protected YggdrasilAccount(YggdrasilService service, String username, String clientToken, String character, YggdrasilSession session) { + protected YggdrasilAccount(YggdrasilService service, String username, String character, YggdrasilSession session) { this.service = service; this.username = username; this.session = session; - this.clientToken = clientToken; this.character = character; if (session == null || session.getSelectedProfile() == null || StringUtils.isBlank(session.getAccessToken())) @@ -72,7 +69,7 @@ public class YggdrasilAccount extends Account { logInWithToken(); selectProfile(new SpecificCharacterSelector(character)); } - return toAuthInfo(); + return session.toAuthInfo(); } @Override @@ -81,9 +78,9 @@ public class YggdrasilAccount extends Account { } protected AuthInfo logInWithPassword(String password, CharacterSelector selector) throws AuthenticationException { - session = service.authenticate(username, password, clientToken); + session = service.authenticate(username, password, UUIDTypeAdapter.fromUUID(UUID.randomUUID())); selectProfile(selector); - return toAuthInfo(); + return session.toAuthInfo(); } private void selectProfile(CharacterSelector selector) throws AuthenticationException { @@ -91,25 +88,18 @@ public class YggdrasilAccount extends Account { if (session.getAvailableProfiles() == null || session.getAvailableProfiles().length <= 0) throw new NoCharacterException(this); - session.setSelectedProfile(selector.select(this, Arrays.asList(session.getAvailableProfiles()))); + session = service.refresh(session.getAccessToken(), session.getClientToken(), selector.select(this, Arrays.asList(session.getAvailableProfiles()))); } character = session.getSelectedProfile().getName(); } private void logInWithToken() throws AuthenticationException { - if (service.validate(session.getAccessToken(), clientToken)) { + if (service.validate(session.getAccessToken(), session.getClientToken())) { isOnline = true; return; } - session = service.refresh(session.getAccessToken(), clientToken); - } - - private AuthInfo toAuthInfo() { - GameProfile profile = session.getSelectedProfile(); - - return new AuthInfo(profile.getName(), UUIDTypeAdapter.fromUUID(profile.getId()), session.getAccessToken(), profile.getUserType(), - new GsonBuilder().registerTypeAdapter(PropertyMap.class, PropertyMap.LegacySerializer.INSTANCE).create().toJson(Optional.ofNullable(session.getUser()).map(User::getProperties).orElseGet(PropertyMap::new))); + session = service.refresh(session.getAccessToken(), session.getClientToken(), null); } @Override @@ -122,13 +112,7 @@ public class YggdrasilAccount extends Account { if (!canPlayOffline()) throw new IllegalStateException("Current account " + this + " cannot play offline."); - return toAuthInfo(); - } - - @Override - public void logOut() { - isOnline = false; - session = null; + return session.toAuthInfo(); } @Override @@ -136,7 +120,6 @@ public class YggdrasilAccount extends Account { HashMap storage = new HashMap<>(); storage.put("username", getUsername()); - storage.put("clientToken", clientToken); storage.put("character", character); if (session != null) storage.putAll(session.toStorage()); @@ -144,6 +127,7 @@ public class YggdrasilAccount extends Account { return storage; } + @Override public UUID getUUID() { if (session == null || session.getSelectedProfile() == null) return null; @@ -157,7 +141,7 @@ public class YggdrasilAccount extends Account { public Optional getSkin(GameProfile profile) throws AuthenticationException { if (!service.getTextures(profile).isPresent()) { - session.setAvailableProfile(profile = service.getCompleteGameProfile(profile.getId())); + profile = service.getCompleteGameProfile(profile.getId()).orElse(profile); } return service.getTextures(profile).map(map -> map.get(TextureType.SKIN)); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java index e4e15dde8..38bc399fb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java @@ -48,7 +48,7 @@ public class YggdrasilAccountFactory extends AccountFactory { Objects.requireNonNull(password); Objects.requireNonNull(proxy); - YggdrasilAccount account = new YggdrasilAccount(new YggdrasilService(provider, proxy), username, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), null, null); + YggdrasilAccount account = new YggdrasilAccount(new YggdrasilService(provider, proxy), username, null, null); account.logInWithPassword(password, selector); return account; } @@ -60,12 +60,10 @@ public class YggdrasilAccountFactory extends AccountFactory { String username = tryCast(storage.get("username"), String.class) .orElseThrow(() -> new IllegalArgumentException("storage does not have username")); - String clientToken = tryCast(storage.get("clientToken"), String.class) - .orElseThrow(() -> new IllegalArgumentException("storage does not have client token.")); String character = tryCast(storage.get("clientToken"), String.class) .orElseThrow(() -> new IllegalArgumentException("storage does not have selected character name.")); - return new YggdrasilAccount(new YggdrasilService(provider, proxy), username, clientToken, character, YggdrasilSession.fromStorage(storage)); + return new YggdrasilAccount(new YggdrasilService(provider, proxy), username, character, YggdrasilSession.fromStorage(storage)); } public static String randomToken() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java index 4db19b654..670a913bd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java @@ -1,20 +1,33 @@ package org.jackhuang.hmcl.auth.yggdrasil; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.jackhuang.hmcl.util.Lang.liftFunction; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonParseException; -import org.jackhuang.hmcl.auth.*; -import org.jackhuang.hmcl.util.*; - import java.io.IOException; import java.net.Proxy; import java.net.URL; -import java.util.*; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; -import static java.nio.charset.StandardCharsets.UTF_8; +import org.jackhuang.hmcl.auth.AuthenticationException; +import org.jackhuang.hmcl.auth.InvalidCredentialsException; +import org.jackhuang.hmcl.auth.InvalidPasswordException; +import org.jackhuang.hmcl.auth.InvalidTokenException; +import org.jackhuang.hmcl.auth.ServerDisconnectException; +import org.jackhuang.hmcl.auth.ServerResponseMalformedException; +import org.jackhuang.hmcl.util.NetworkUtils; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.UUIDTypeAdapter; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; public class YggdrasilService { @@ -45,14 +58,30 @@ public class YggdrasilService { request.put("clientToken", clientToken); request.put("requestUser", true); - return handle(request(provider.getAuthenticationURL(), request), clientToken); + return handleAuthenticationResponse(request(provider.getAuthenticationURL(), request), clientToken); } - public YggdrasilSession refresh(String accessToken, String clientToken) throws AuthenticationException { + private Map createRequestWithCredentials(String accessToken, String clientToken) { + Map request = new HashMap<>(); + request.put("accessToken", accessToken); + request.put("clientToken", clientToken); + return request; + } + + public YggdrasilSession refresh(String accessToken, String clientToken, GameProfile characterToSelect) throws AuthenticationException { Objects.requireNonNull(accessToken); Objects.requireNonNull(clientToken); - return handle(request(provider.getRefreshmentURL(), new RefreshRequest(accessToken, clientToken)), clientToken); + Map request = createRequestWithCredentials(accessToken, clientToken); + request.put("requestUser", true); + + if (characterToSelect != null) { + request.put("selectedProfile", mapOf( + pair("id", characterToSelect.getId()), + pair("name", characterToSelect.getName()))); + } + + return handleAuthenticationResponse(request(provider.getRefreshmentURL(), request), clientToken); } public boolean validate(String accessToken) throws AuthenticationException { @@ -63,7 +92,7 @@ public class YggdrasilService { Objects.requireNonNull(accessToken); try { - requireEmpty(request(provider.getValidationURL(), new RefreshRequest(accessToken, clientToken))); + requireEmpty(request(provider.getValidationURL(), createRequestWithCredentials(accessToken, clientToken))); return true; } catch (InvalidCredentialsException | InvalidTokenException e) { return false; @@ -77,7 +106,7 @@ public class YggdrasilService { public void invalidate(String accessToken, String clientToken) throws AuthenticationException { Objects.requireNonNull(accessToken); - requireEmpty(request(provider.getInvalidationURL(), GSON.toJson(new RefreshRequest(accessToken, clientToken)))); + requireEmpty(request(provider.getInvalidationURL(), createRequestWithCredentials(accessToken, clientToken))); } /** @@ -85,49 +114,34 @@ public class YggdrasilService { * * Game profile provided from authentication is not complete (no skin data in properties). * - * @param userId the userId that the character corresponding to. - * @return the complete game profile(filled with more properties), null if character corresponding to {@code userId} does not exist. - * @throws AuthenticationException if an I/O error occurred or server response malformed. + * @param uuid the uuid that the character corresponding to. + * @return the complete game profile(filled with more properties) */ - public GameProfile getCompleteGameProfile(UUID userId) throws AuthenticationException { - Objects.requireNonNull(userId); + public Optional getCompleteGameProfile(UUID uuid) throws AuthenticationException { + Objects.requireNonNull(uuid); - ProfileResponse response = fromJson(request(provider.getProfilePropertiesURL(userId), null), ProfileResponse.class); - if (response == null) - return null; - - return new GameProfile(response.getId(), response.getName(), response.getProperties()); + return Optional.ofNullable(fromJson(request(provider.getProfilePropertiesURL(uuid), null), ProfileResponse.class)) + .map(response -> new GameProfile(response.id, response.name, response.properties)); } public Optional> getTextures(GameProfile profile) throws AuthenticationException { Objects.requireNonNull(profile); return Optional.ofNullable(profile.getProperties()) - .map(properties -> properties.get("textures")) + .flatMap(properties -> Optional.ofNullable(properties.get("textures"))) .map(encodedTextures -> new String(Base64.getDecoder().decode(encodedTextures), UTF_8)) - .map(Lang.liftFunction(textures -> fromJson(textures, TextureResponse.class))) - .map(TextureResponse::getTextures); + .map(liftFunction(textures -> fromJson(textures, TextureResponse.class))) + .flatMap(response -> Optional.ofNullable(response.textures)); } - private String request(URL url, Object input) throws AuthenticationException { - try { - if (input == null) - return NetworkUtils.doGet(url, proxy); - else - return NetworkUtils.doPost(url, input instanceof String ? (String) input : GSON.toJson(input), "application/json"); - } catch (IOException e) { - throw new ServerDisconnectException(e); - } - } - - private static YggdrasilSession handle(String responseText, String clientToken) throws AuthenticationException { + private static YggdrasilSession handleAuthenticationResponse(String responseText, String clientToken) throws AuthenticationException { AuthenticationResponse response = fromJson(responseText, AuthenticationResponse.class); handleErrorMessage(response); - if (!clientToken.equals(response.getClientToken())) - throw new AuthenticationException("Client token changed from " + response.getClientToken() + " to " + clientToken); + if (!clientToken.equals(response.clientToken)) + throw new AuthenticationException("Client token changed from " + clientToken + " to " + response.clientToken); - return new YggdrasilSession(response.getAccessToken(), response.getSelectedProfile(), response.getAvailableProfiles(), response.getUser()); + return new YggdrasilSession(response.clientToken, response.accessToken, response.selectedProfile, response.availableProfiles, response.user); } private static void requireEmpty(String response) throws AuthenticationException { @@ -142,15 +156,30 @@ public class YggdrasilService { } private static void handleErrorMessage(ErrorResponse response) throws AuthenticationException { - if (!StringUtils.isBlank(response.getError())) { - if (response.getErrorMessage() != null) - if (response.getErrorMessage().contains("Invalid credentials")) + if (!StringUtils.isBlank(response.error)) { + if (response.errorMessage != null && "ForbiddenOperationException".equals(response.error)) { + if (response.errorMessage.contains("Invalid credentials")) throw new InvalidCredentialsException(); - else if (response.getErrorMessage().contains("Invalid token")) + + else if (response.errorMessage.contains("Invalid token")) throw new InvalidTokenException(); - else if (response.getErrorMessage().contains("Invalid username or password")) + + else if (response.errorMessage.contains("Invalid username or password")) throw new InvalidPasswordException(); - throw new RemoteAuthenticationException(response.getError(), response.getErrorMessage(), response.getCause()); + } + + throw new RemoteAuthenticationException(response.error, response.errorMessage, response.cause); + } + } + + private String request(URL url, Object payload) throws AuthenticationException { + try { + if (payload == null) + return NetworkUtils.doGet(url, proxy); + else + return NetworkUtils.doPost(url, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json"); + } catch (IOException e) { + throw new ServerDisconnectException(e); } } @@ -162,77 +191,34 @@ public class YggdrasilService { } } - static final Gson GSON = new GsonBuilder() + private class ProfileResponse { + public UUID id; + public String name; + public PropertyMap properties; + } + + private class TextureResponse { + public Map textures; + } + + private class AuthenticationResponse extends ErrorResponse { + public String accessToken; + public String clientToken; + public GameProfile selectedProfile; + public GameProfile[] availableProfiles; + public User user; + } + + private class ErrorResponse { + public String error; + public String errorMessage; + public String cause; + } + + private static final Gson GSON = new GsonBuilder() .registerTypeAdapter(GameProfile.class, GameProfile.Serializer.INSTANCE) .registerTypeAdapter(PropertyMap.class, PropertyMap.Serializer.INSTANCE) .registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE) .create(); - private static final class RefreshRequest { - - private final String accessToken; - private final String clientToken; - private final GameProfile selectedProfile; - private final boolean requestUser; - - public RefreshRequest(String accessToken, String clientToken) { - this(accessToken, clientToken, null); - } - - public RefreshRequest(String accessToken, String clientToken, GameProfile selectedProfile) { - this(accessToken, clientToken, selectedProfile, true); - } - - public RefreshRequest(String accessToken, String clientToken, GameProfile selectedProfile, boolean requestUser) { - this.accessToken = accessToken; - this.clientToken = clientToken; - this.selectedProfile = selectedProfile; - this.requestUser = requestUser; - } - - public String getAccessToken() { - return accessToken; - } - - public String getClientToken() { - return clientToken; - } - - public GameProfile getSelectedProfile() { - return selectedProfile; - } - - public boolean isRequestUser() { - return requestUser; - } - - } - - private static final class ProfileResponse { - private final UUID id; - private final String name; - private final PropertyMap properties; - - public ProfileResponse() { - this(UUID.randomUUID(), "", null); - } - - public ProfileResponse(UUID id, String name, PropertyMap properties) { - this.id = id; - this.name = name; - this.properties = properties; - } - - public UUID getId() { - return id; - } - - public String getName() { - return name; - } - - public PropertyMap getProperties() { - return properties; - } - } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilSession.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilSession.java index e901b105b..4f99697ee 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilSession.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilSession.java @@ -1,35 +1,54 @@ package org.jackhuang.hmcl.auth.yggdrasil; -import org.jackhuang.hmcl.util.UUIDTypeAdapter; +import static org.jackhuang.hmcl.util.Lang.mapOf; +import static org.jackhuang.hmcl.util.Lang.tryCast; +import static org.jackhuang.hmcl.util.Pair.pair; -import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.UUID; -import static org.jackhuang.hmcl.util.Lang.tryCast; +import org.jackhuang.hmcl.auth.AuthInfo; +import org.jackhuang.hmcl.auth.UserType; +import org.jackhuang.hmcl.util.UUIDTypeAdapter; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; public class YggdrasilSession { - private final String accessToken; + private String clientToken; + private String accessToken; private GameProfile selectedProfile; - private final GameProfile[] availableProfiles; - private final User user; + private GameProfile[] availableProfiles; + private User user; - public YggdrasilSession(String accessToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user) { + public YggdrasilSession(String clientToken, String accessToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user) { + this.clientToken = clientToken; this.accessToken = accessToken; this.selectedProfile = selectedProfile; this.availableProfiles = availableProfiles; this.user = user; } + public String getClientToken() { + return clientToken; + } + public String getAccessToken() { return accessToken; } + /** + * @return nullable (null if no character is selected) + */ public GameProfile getSelectedProfile() { return selectedProfile; } + /** + * @return nullable (null if the YggdrasilSession is loaded from storage) + */ public GameProfile[] getAvailableProfiles() { return availableProfiles; } @@ -38,53 +57,40 @@ public class YggdrasilSession { return user; } - public void setAvailableProfile(GameProfile profile) { - if (availableProfiles != null) - for (int i = 0; i < availableProfiles.length; ++i) - if (availableProfiles[i].getId().equals(profile.getId())) - availableProfiles[i] = profile; - - if (selectedProfile != null && profile.getId().equals(selectedProfile.getId())) - selectedProfile = profile; - } - - public void setSelectedProfile(GameProfile selectedProfile) { - this.selectedProfile = selectedProfile; - - setAvailableProfile(selectedProfile); - } - public static YggdrasilSession fromStorage(Map storage) { - Optional profileId = tryCast(storage.get("uuid"), String.class); - Optional profileName = tryCast(storage.get("displayName"), String.class); - GameProfile profile = null; - if (profileId.isPresent() && profileName.isPresent()) { - profile = new GameProfile(UUIDTypeAdapter.fromString(profileId.get()), profileName.get(), - tryCast(storage.get("profileProperties"), Map.class).map(PropertyMap::fromMap).orElseGet(PropertyMap::new)); - } - - return new YggdrasilSession( - tryCast(storage.get("accessToken"), String.class).orElse(null), - profile, - null, - tryCast(storage.get("userid"), String.class) - .map(userId -> new User(userId, tryCast(storage.get("userProperties"), Map.class).map(PropertyMap::fromMap).orElse(null))) - .orElse(null) - ); + UUID uuid = tryCast(storage.get("uuid"), String.class).map(UUIDTypeAdapter::fromString).orElseThrow(() -> new IllegalArgumentException("uuid is missing")); + String name = tryCast(storage.get("displayName"), String.class).orElseThrow(() -> new IllegalArgumentException("displayName is missing")); + String clientToken = tryCast(storage.get("clientToken"), String.class).orElseThrow(() -> new IllegalArgumentException("clientToken is missing")); + String accessToken = tryCast(storage.get("accessToken"), String.class).orElseThrow(() -> new IllegalArgumentException("accessToken is missing")); + String userId = tryCast(storage.get("userid"), String.class).orElseThrow(() -> new IllegalArgumentException("userid is missing")); + PropertyMap userProperties = tryCast(storage.get("userProperties"), Map.class).map(PropertyMap::fromMap).orElse(null); + return new YggdrasilSession(clientToken, accessToken, new GameProfile(uuid, name), null, new User(userId, userProperties)); } public Map toStorage() { - Map storage = new HashMap<>(); - storage.put("accessToken", accessToken); - if (selectedProfile != null) { - storage.put("uuid", UUIDTypeAdapter.fromUUID(selectedProfile.getId())); - storage.put("displayName", selectedProfile.getName()); - storage.put("profileProperties", selectedProfile.getProperties()); - } - if (user != null) { - storage.put("userid", user.getId()); - storage.put("userProperties", user.getProperties()); - } - return storage; + if (selectedProfile == null) + throw new IllegalStateException("No character is selected"); + if (user == null) + throw new IllegalStateException("No user is specified"); + + return mapOf( + pair("clientToken", clientToken), + pair("accessToken", accessToken), + pair("uuid", UUIDTypeAdapter.fromUUID(selectedProfile.getId())), + pair("displayName", selectedProfile.getName()), + pair("userid", user.getId()), + pair("userProperties", user.getProperties())); } + + public AuthInfo toAuthInfo() { + if (selectedProfile == null) + throw new IllegalStateException("No character is selected"); + if (user == null) + throw new IllegalStateException("No user is specified"); + + return new AuthInfo(selectedProfile.getName(), selectedProfile.getId(), accessToken, UserType.MOJANG, + Optional.ofNullable(user.getProperties()).map(GSON_PROPERTIES::toJson).orElse("{}")); + } + + private static final Gson GSON_PROPERTIES = new GsonBuilder().registerTypeAdapter(PropertyMap.class, PropertyMap.LegacySerializer.INSTANCE).create(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java index a9d3c8ccc..c8162e1c9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -239,7 +239,7 @@ public class DefaultLauncher extends Launcher { pair("${auth_player_name}", authInfo.getUsername()), pair("${auth_session}", authInfo.getAccessToken()), pair("${auth_access_token}", authInfo.getAccessToken()), - pair("${auth_uuid}", authInfo.getUserId()), + pair("${auth_uuid}", UUIDTypeAdapter.fromUUID(authInfo.getUUID())), pair("${version_name}", Optional.ofNullable(options.getVersionName()).orElse(version.getId())), pair("${profile_name}", Optional.ofNullable(options.getProfileName()).orElse("Minecraft")), pair("${version_type}", version.getType().getId()),