Merge branch 'javafx' into aarch64
This commit is contained in:
@@ -50,25 +50,33 @@ public abstract class AccountFactory<T extends Account> {
|
||||
}
|
||||
}
|
||||
|
||||
public interface ProgressCallback {
|
||||
void onProgressChanged(String stageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs how this account factory verifies user's credential.
|
||||
*
|
||||
* @see AccountLoginType
|
||||
*/
|
||||
public abstract AccountLoginType getLoginType();
|
||||
|
||||
/**
|
||||
* Create a new(to be verified via network) account, and log in.
|
||||
* @param selector for character selection if multiple characters belong to single account. Pick out which character to act as.
|
||||
* @param username username of the account if needed.
|
||||
* @param password password of the account if needed.
|
||||
* @param additionalData extra data for specific account factory.
|
||||
*
|
||||
* @param selector for character selection if multiple characters belong to single account. Pick out which character to act as.
|
||||
* @param username username of the account if needed.
|
||||
* @param password password of the account if needed.
|
||||
* @param progressCallback notify login progress.
|
||||
* @param additionalData extra data for specific account factory.
|
||||
* @return logged-in account.
|
||||
* @throws AuthenticationException if an error occurs when logging in.
|
||||
*/
|
||||
public abstract T create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException;
|
||||
public abstract T create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException;
|
||||
|
||||
/**
|
||||
* Create a existing(stored in local files) account.
|
||||
*
|
||||
* @param storage serialized account data.
|
||||
* @return account stored in local storage. Credentials may expired, and you should refresh account state later.
|
||||
*/
|
||||
|
||||
@@ -50,7 +50,7 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException {
|
||||
public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException {
|
||||
Objects.requireNonNull(selector);
|
||||
Objects.requireNonNull(username);
|
||||
Objects.requireNonNull(password);
|
||||
|
||||
@@ -21,14 +21,17 @@ import javafx.beans.binding.ObjectBinding;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public class MicrosoftAccount extends Account {
|
||||
|
||||
@@ -77,7 +80,7 @@ public class MicrosoftAccount extends Account {
|
||||
@Override
|
||||
public AuthInfo logIn() throws AuthenticationException {
|
||||
if (!authenticated) {
|
||||
if (service.validate(session.getTokenType(), session.getAccessToken())) {
|
||||
if (service.validate(session.getNotAfter(), session.getTokenType(), session.getAccessToken())) {
|
||||
authenticated = true;
|
||||
} else {
|
||||
MicrosoftSession acquiredSession = service.refresh(session);
|
||||
@@ -116,9 +119,15 @@ public class MicrosoftAccount extends Account {
|
||||
|
||||
@Override
|
||||
public ObjectBinding<Optional<Map<TextureType, Texture>>> getTextures() {
|
||||
return BindingMapping.of(service.getProfileRepository().binding(session.getAuthorization()))
|
||||
.map(profile -> profile.flatMap(MicrosoftService::getTextures));
|
||||
|
||||
return BindingMapping.of(service.getProfileRepository().binding(getUUID()))
|
||||
.map(profile -> profile.flatMap(it -> {
|
||||
try {
|
||||
return YggdrasilService.getTextures(it);
|
||||
} catch (ServerResponseMalformedException e) {
|
||||
LOG.log(Level.WARNING, "Failed to parse texture payload", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -38,7 +38,7 @@ public class MicrosoftAccountFactory extends AccountFactory<MicrosoftAccount> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MicrosoftAccount create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException {
|
||||
public MicrosoftAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException {
|
||||
Objects.requireNonNull(selector);
|
||||
|
||||
return new MicrosoftAccount(service, selector);
|
||||
|
||||
@@ -17,15 +17,16 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.microsoft;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.gson.*;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
@@ -60,17 +61,17 @@ public class MicrosoftService {
|
||||
|
||||
private final OAuthCallback callback;
|
||||
|
||||
private final ObservableOptionalCache<String, MinecraftProfileResponse, AuthenticationException> profileRepository;
|
||||
private final ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> profileRepository;
|
||||
|
||||
public MicrosoftService(OAuthCallback callback) {
|
||||
this.callback = requireNonNull(callback);
|
||||
this.profileRepository = new ObservableOptionalCache<>(authorization -> {
|
||||
LOG.info("Fetching properties");
|
||||
return getCompleteProfile(authorization);
|
||||
this.profileRepository = new ObservableOptionalCache<>(uuid -> {
|
||||
LOG.info("Fetching properties of " + uuid);
|
||||
return getCompleteGameProfile(uuid);
|
||||
}, (uuid, e) -> LOG.log(Level.WARNING, "Failed to fetch properties of " + uuid, e), POOL);
|
||||
}
|
||||
|
||||
public ObservableOptionalCache<String, MinecraftProfileResponse, AuthenticationException> getProfileRepository() {
|
||||
public ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> getProfileRepository() {
|
||||
return profileRepository;
|
||||
}
|
||||
|
||||
@@ -213,10 +214,14 @@ public class MicrosoftService {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean validate(String tokenType, String accessToken) throws AuthenticationException {
|
||||
public boolean validate(long notAfter, String tokenType, String accessToken) throws AuthenticationException {
|
||||
requireNonNull(tokenType);
|
||||
requireNonNull(accessToken);
|
||||
|
||||
if (System.currentTimeMillis() > notAfter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
getMinecraftProfile(tokenType, accessToken);
|
||||
return true;
|
||||
@@ -266,7 +271,7 @@ public class MicrosoftService {
|
||||
.createConnection();
|
||||
int responseCode = conn.getResponseCode();
|
||||
if (responseCode == HTTP_NOT_FOUND) {
|
||||
throw new NoCharacterException();
|
||||
throw new NoMinecraftJavaEditionProfileException();
|
||||
} else if (responseCode != 200) {
|
||||
throw new ResponseCodeException(new URL("https://api.minecraftservices.com/minecraft/profile"), responseCode);
|
||||
}
|
||||
@@ -275,6 +280,31 @@ public class MicrosoftService {
|
||||
return JsonUtils.fromNonNullJson(result, MinecraftProfileResponse.class);
|
||||
}
|
||||
|
||||
public Optional<CompleteGameProfile> getCompleteGameProfile(UUID uuid) throws AuthenticationException {
|
||||
Objects.requireNonNull(uuid);
|
||||
|
||||
return Optional.ofNullable(GSON.fromJson(request(NetworkUtils.toURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)), null), CompleteGameProfile.class));
|
||||
}
|
||||
|
||||
private static String request(URL url, Object payload) throws AuthenticationException {
|
||||
try {
|
||||
if (payload == null)
|
||||
return NetworkUtils.doGet(url);
|
||||
else
|
||||
return NetworkUtils.doPost(url, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json");
|
||||
} catch (IOException e) {
|
||||
throw new ServerDisconnectException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T fromJson(String text, Class<T> typeOfT) throws ServerResponseMalformedException {
|
||||
try {
|
||||
return GSON.fromJson(text, typeOfT);
|
||||
} catch (JsonParseException e) {
|
||||
throw new ServerResponseMalformedException(text, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class XboxAuthorizationException extends AuthenticationException {
|
||||
private final long errorCode;
|
||||
|
||||
@@ -290,6 +320,9 @@ public class MicrosoftService {
|
||||
public static final long ADD_FAMILY = 2148916238L;
|
||||
}
|
||||
|
||||
public static class NoMinecraftJavaEditionProfileException extends AuthenticationException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Error response: {"error":"invalid_grant","error_description":"The provided
|
||||
* value for the 'redirect_uri' is not valid. The value must exactly match the
|
||||
@@ -487,4 +520,9 @@ public class MicrosoftService {
|
||||
String waitFor() throws InterruptedException, ExecutionException;
|
||||
}
|
||||
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE)
|
||||
.registerTypeAdapterFactory(ValidationTypeAdapterFactory.INSTANCE)
|
||||
.create();
|
||||
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public final class OfflineAccountFactory extends AccountFactory<OfflineAccount>
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineAccount create(CharacterSelector selector, String username, String password, Object additionalData) {
|
||||
public OfflineAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) {
|
||||
return new OfflineAccount(username, getUUIDFromUserName(username));
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public YggdrasilAccount create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException {
|
||||
public YggdrasilAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException {
|
||||
Objects.requireNonNull(selector);
|
||||
Objects.requireNonNull(username);
|
||||
Objects.requireNonNull(password);
|
||||
|
||||
Reference in New Issue
Block a user