YggdrasilConfiguration -> APIMetadata
InjectorInitializationException -> InitializationException
javaagent/AuthlibInjectorPremain -> Premain
This commit is contained in:
yushijinhun 2020-08-22 16:26:22 +08:00
parent 496baee488
commit 1ba5bbb678
No known key found for this signature in database
GPG key ID: 5BC167F73EA558E4
9 changed files with 76 additions and 104 deletions

View file

@ -28,8 +28,8 @@ jar {
'Implementation-Vendor': 'yushijinhun',
'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
'Automatic-Module-Name': 'moe.yushi.authlibinjector',
'Premain-Class': 'moe.yushi.authlibinjector.javaagent.AuthlibInjectorPremain',
'Agent-Class': 'moe.yushi.authlibinjector.javaagent.AuthlibInjectorPremain',
'Premain-Class': 'moe.yushi.authlibinjector.Premain',
'Agent-Class': 'moe.yushi.authlibinjector.Premain',
'Can-Retransform-Classes': true,
'Can-Redefine-Classes': true,
'Git-Commit': gitInfo.gitHashFull,

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors
* Copyright (C) 2020 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -37,9 +37,9 @@ import moe.yushi.authlibinjector.internal.org.json.simple.JSONObject;
import moe.yushi.authlibinjector.util.JsonUtils;
import moe.yushi.authlibinjector.util.KeyUtils;
public class YggdrasilConfiguration {
public class APIMetadata {
public static YggdrasilConfiguration parse(String apiRoot, String metadataResponse) throws UncheckedIOException {
public static APIMetadata parse(String apiRoot, String metadataResponse) throws UncheckedIOException {
JSONObject response = asJsonObject(parseJson(metadataResponse));
List<String> skinDomains =
@ -59,7 +59,7 @@ public class YggdrasilConfiguration {
.map(it -> (Map<String, Object>) new TreeMap<>(asJsonObject(it)))
.orElse(emptyMap());
return new YggdrasilConfiguration(apiRoot, unmodifiableList(skinDomains), unmodifiableMap(meta), decodedPublickey);
return new APIMetadata(apiRoot, unmodifiableList(skinDomains), unmodifiableMap(meta), decodedPublickey);
}
private String apiRoot;
@ -67,7 +67,7 @@ public class YggdrasilConfiguration {
private Optional<PublicKey> decodedPublickey;
private Map<String, Object> meta;
public YggdrasilConfiguration(String apiRoot, List<String> skinDomains, Map<String, Object> meta, Optional<PublicKey> decodedPublickey) {
public APIMetadata(String apiRoot, List<String> skinDomains, Map<String, Object> meta, Optional<PublicKey> decodedPublickey) {
this.apiRoot = requireNonNull(apiRoot);
this.skinDomains = requireNonNull(skinDomains);
this.meta = requireNonNull(meta);
@ -92,7 +92,6 @@ public class YggdrasilConfiguration {
@Override
public String toString() {
return format("YggdrasilConfiguration [apiRoot={0}, skinDomains={1}, decodedPublickey={2}, meta={3}]", apiRoot, skinDomains, decodedPublickey, meta);
return format("APIMetadata [apiRoot={0}, skinDomains={1}, decodedPublickey={2}, meta={3}]", apiRoot, skinDomains, decodedPublickey, meta);
}
}

View file

@ -18,8 +18,7 @@ package moe.yushi.authlibinjector;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyList;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static java.util.Objects.requireNonNull;
import static moe.yushi.authlibinjector.util.IOUtils.asBytes;
import static moe.yushi.authlibinjector.util.IOUtils.asString;
import static moe.yushi.authlibinjector.util.IOUtils.removeNewLines;
@ -42,7 +41,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream;
import moe.yushi.authlibinjector.httpd.DefaultURLRedirector;
import moe.yushi.authlibinjector.httpd.LegacySkinAPIFilter;
@ -65,12 +63,6 @@ import moe.yushi.authlibinjector.yggdrasil.MojangYggdrasilAPIProvider;
import moe.yushi.authlibinjector.yggdrasil.YggdrasilClient;
public final class AuthlibInjector {
/**
* Stores the API URL, should be set before {@link #bootstrap(Consumer)} is invoked.
*/
public static final String PROP_API_ROOT = "authlibinjector.api";
private AuthlibInjector() {}
private static boolean booted = false;
@ -78,13 +70,13 @@ public final class AuthlibInjector {
private static boolean retransformSupported;
private static ClassTransformer classTransformer;
public static synchronized void bootstrap(Instrumentation instrumentation) throws InjectorInitializationException {
public static synchronized void bootstrap(Instrumentation instrumentation, String apiUrl) throws InitializationException {
if (booted) {
log(INFO, "Already started, skipping");
return;
}
booted = true;
AuthlibInjector.instrumentation = instrumentation;
AuthlibInjector.instrumentation = requireNonNull(instrumentation);
Config.init();
retransformSupported = instrumentation.isRetransformClassesSupported();
@ -92,19 +84,14 @@ public final class AuthlibInjector {
log(WARNING, "Retransform is not supported");
}
log(INFO, "Version: " + getVersion());
log(INFO, "Version: " + AuthlibInjector.class.getPackage().getImplementationVersion());
Optional<YggdrasilConfiguration> optionalConfig = configure();
if (optionalConfig.isPresent()) {
classTransformer = createTransformer(optionalConfig.get());
APIMetadata apiMetadata = fetchAPIMetadata(apiUrl);
classTransformer = createTransformer(apiMetadata);
instrumentation.addTransformer(classTransformer, retransformSupported);
MC52974Workaround.init();
MC52974_1710Workaround.init();
} else {
log(ERROR, "No authentication server specified");
throw new InjectorInitializationException();
}
}
private static Optional<String> getPrefetchedResponse() {
@ -118,27 +105,40 @@ public final class AuthlibInjector {
return Optional.ofNullable(prefetched);
}
private static Optional<YggdrasilConfiguration> configure() {
String apiRoot = System.getProperty(PROP_API_ROOT);
if (apiRoot == null)
return empty();
private static APIMetadata fetchAPIMetadata(String apiUrl) {
if (apiUrl == null || apiUrl.isEmpty()) {
log(ERROR, "No authentication server specified");
throw new InitializationException();
}
apiRoot = addHttpsIfMissing(apiRoot);
log(INFO, "Authentication server: " + apiRoot);
warnIfHttp(apiRoot);
apiUrl = addHttpsIfMissing(apiUrl);
log(INFO, "Authentication server: " + apiUrl);
warnIfHttp(apiUrl);
String metadataResponse;
Optional<String> prefetched = getPrefetchedResponse();
if (!prefetched.isPresent()) {
if (prefetched.isPresent()) {
log(DEBUG, "Prefetched metadata detected");
try {
metadataResponse = new String(Base64.getDecoder().decode(removeNewLines(prefetched.get())), UTF_8);
} catch (IllegalArgumentException e) {
log(ERROR, "Unable to decode metadata: " + e + "\n"
+ "Encoded metadata:\n"
+ prefetched.get());
throw new InitializationException(e);
}
} else {
try {
HttpURLConnection connection = (HttpURLConnection) new URL(apiRoot).openConnection();
HttpURLConnection connection = (HttpURLConnection) new URL(apiUrl).openConnection();
String ali = connection.getHeaderField("x-authlib-injector-api-location");
if (ali != null) {
URL absoluteAli = new URL(connection.getURL(), ali);
if (!urlEqualsIgnoreSlash(apiRoot, absoluteAli.toString())) {
if (!urlEqualsIgnoreSlash(apiUrl, absoluteAli.toString())) {
// usually the URL that ALI points to is on the same host
// so the TCP connection can be reused
@ -150,8 +150,8 @@ public final class AuthlibInjector {
}
log(INFO, "Redirect to: " + absoluteAli);
apiRoot = absoluteAli.toString();
warnIfHttp(apiRoot);
apiUrl = absoluteAli.toString();
warnIfHttp(apiUrl);
connection = (HttpURLConnection) absoluteAli.openConnection();
}
}
@ -161,37 +161,28 @@ public final class AuthlibInjector {
}
} catch (IOException e) {
log(ERROR, "Failed to fetch metadata: " + e);
throw new InjectorInitializationException(e);
throw new InitializationException(e);
}
} else {
log(DEBUG, "Prefetched metadata detected");
try {
metadataResponse = new String(Base64.getDecoder().decode(removeNewLines(prefetched.get())), UTF_8);
} catch (IllegalArgumentException e) {
log(ERROR, "Unable to decode metadata: " + e + "\n"
+ "Encoded metadata:\n"
+ prefetched.get());
throw new InjectorInitializationException(e);
}
}
log(DEBUG, "Metadata: " + metadataResponse);
if (!apiRoot.endsWith("/"))
apiRoot += "/";
if (!apiUrl.endsWith("/")) {
apiUrl += "/";
}
YggdrasilConfiguration configuration;
APIMetadata metadata;
try {
configuration = YggdrasilConfiguration.parse(apiRoot, metadataResponse);
metadata = APIMetadata.parse(apiUrl, metadataResponse);
} catch (UncheckedIOException e) {
log(ERROR, "Unable to parse metadata: " + e.getCause() + "\n"
+ "Raw metadata:\n"
+ metadataResponse);
throw new InjectorInitializationException(e);
throw new InitializationException(e);
}
log(DEBUG, "Parsed metadata: " + configuration);
return of(configuration);
log(DEBUG, "Parsed metadata: " + metadata);
return metadata;
}
private static void warnIfHttp(String url) {
@ -216,7 +207,7 @@ public final class AuthlibInjector {
return a.equals(b);
}
private static List<URLFilter> createFilters(YggdrasilConfiguration config) {
private static List<URLFilter> createFilters(APIMetadata config) {
if (Config.httpdDisabled) {
return emptyList();
}
@ -238,7 +229,7 @@ public final class AuthlibInjector {
return filters;
}
private static ClassTransformer createTransformer(YggdrasilConfiguration config) {
private static ClassTransformer createTransformer(APIMetadata config) {
URLProcessor urlProcessor = new URLProcessor(createFilters(config), new DefaultURLRedirector(config));
ClassTransformer transformer = new ClassTransformer();
@ -265,10 +256,6 @@ public final class AuthlibInjector {
return transformer;
}
public static String getVersion() {
return AuthlibInjector.class.getPackage().getImplementationVersion();
}
public static void retransformClasses(String... classNames) {
if (!retransformSupported) {
return;

View file

@ -109,7 +109,7 @@ public final class Config {
break;
default:
log(ERROR, "Unrecognized debug option: " + option);
throw new InjectorInitializationException();
throw new InitializationException();
}
}
}
@ -182,7 +182,7 @@ public final class Config {
Matcher matcher = Pattern.compile("^(?<protocol>[^:]+)://(?<host>[^/]+)+:(?<port>\\d+)$").matcher(prop);
if (!matcher.find()) {
log(ERROR, "Unrecognized proxy URL: " + prop);
throw new InjectorInitializationException();
throw new InitializationException();
}
String protocol = matcher.group("protocol");
@ -196,7 +196,7 @@ public final class Config {
default:
log(ERROR, "Unsupported proxy protocol: " + protocol);
throw new InjectorInitializationException();
throw new InitializationException();
}
log(INFO, "Mojang proxy: " + mojangProxy);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors
* Copyright (C) 2020 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -16,12 +16,12 @@
*/
package moe.yushi.authlibinjector;
public class InjectorInitializationException extends RuntimeException {
public class InitializationException extends RuntimeException {
public InjectorInitializationException() {
public InitializationException() {
}
public InjectorInitializationException(Throwable cause) {
public InitializationException(Throwable cause) {
super(cause);
}
}

View file

@ -14,22 +14,21 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package moe.yushi.authlibinjector.javaagent;
package moe.yushi.authlibinjector;
import static moe.yushi.authlibinjector.util.Logging.log;
import static moe.yushi.authlibinjector.util.Logging.Level.DEBUG;
import static moe.yushi.authlibinjector.util.Logging.Level.ERROR;
import static moe.yushi.authlibinjector.util.Logging.Level.INFO;
import java.lang.instrument.Instrumentation;
import moe.yushi.authlibinjector.AuthlibInjector;
import moe.yushi.authlibinjector.InjectorInitializationException;
public class AuthlibInjectorPremain {
public final class Premain {
private Premain() {}
public static void premain(String arg, Instrumentation instrumentation) {
try {
initInjector(arg, instrumentation, false);
} catch (InjectorInitializationException e) {
} catch (InitializationException e) {
log(DEBUG, "A known exception has occurred", e);
System.exit(1);
} catch (Throwable e) {
@ -42,25 +41,18 @@ public class AuthlibInjectorPremain {
try {
log(INFO, "Launched from agentmain");
initInjector(arg, instrumentation, true);
} catch (InjectorInitializationException e) {
} catch (InitializationException e) {
log(DEBUG, "A known exception has occurred", e);
} catch (Throwable e) {
log(ERROR, "An exception has occurred", e);
}
}
public static void initInjector(String arg, Instrumentation instrumentation, boolean retransform) {
setupConfig(arg);
AuthlibInjector.bootstrap(instrumentation);
private static void initInjector(String arg, Instrumentation instrumentation, boolean retransform) {
AuthlibInjector.bootstrap(instrumentation, arg);
if (retransform) {
AuthlibInjector.retransformAllClasses();
}
}
private static void setupConfig(String arg) {
if (arg != null && !arg.isEmpty()) {
System.setProperty(AuthlibInjector.PROP_API_ROOT, arg);
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors
* Copyright (C) 2020 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -19,15 +19,14 @@ package moe.yushi.authlibinjector.httpd;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import moe.yushi.authlibinjector.YggdrasilConfiguration;
import moe.yushi.authlibinjector.APIMetadata;
public class DefaultURLRedirector implements URLRedirector {
private Map<String, String> domainMapping = new HashMap<>();
private String apiRoot;
public DefaultURLRedirector(YggdrasilConfiguration config) {
public DefaultURLRedirector(APIMetadata config) {
initDomainMapping();
apiRoot = config.getApiRoot();

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors
* Copyright (C) 2020 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -17,16 +17,14 @@
package moe.yushi.authlibinjector.yggdrasil;
import static moe.yushi.authlibinjector.util.UUIDUtils.toUnsignedUUID;
import java.util.UUID;
import moe.yushi.authlibinjector.YggdrasilConfiguration;
import moe.yushi.authlibinjector.APIMetadata;
public class CustomYggdrasilAPIProvider implements YggdrasilAPIProvider {
private String apiRoot;
public CustomYggdrasilAPIProvider(YggdrasilConfiguration configuration) {
public CustomYggdrasilAPIProvider(APIMetadata configuration) {
this.apiRoot = configuration.getApiRoot();
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors
* Copyright (C) 2020 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -19,18 +19,15 @@ package moe.yushi.authlibinjector.test;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static org.junit.Assert.assertEquals;
import java.util.Optional;
import org.junit.Test;
import moe.yushi.authlibinjector.YggdrasilConfiguration;
import moe.yushi.authlibinjector.APIMetadata;
import moe.yushi.authlibinjector.httpd.DefaultURLRedirector;
public class DefaultURLRedirectorTest {
private String apiRoot = "https://yggdrasil.example.com/";
private DefaultURLRedirector redirector = new DefaultURLRedirector(new YggdrasilConfiguration(apiRoot, emptyList(), emptyMap(), Optional.empty()));
private DefaultURLRedirector redirector = new DefaultURLRedirector(new APIMetadata(apiRoot, emptyList(), emptyMap(), Optional.empty()));
private void testTransform(String domain, String path, String output) {
assertEquals(redirector.redirect(domain, path).get(), output);