mirror of
https://github.com/yushijinhun/authlib-injector.git
synced 2024-11-15 06:11:09 +01:00
Support @mojang suffix
This commit is contained in:
parent
9cfb6325a3
commit
eeda91c329
6 changed files with 232 additions and 6 deletions
|
@ -23,6 +23,8 @@ import java.util.function.Consumer;
|
||||||
|
|
||||||
import moe.yushi.authlibinjector.httpd.DefaultURLRedirector;
|
import moe.yushi.authlibinjector.httpd.DefaultURLRedirector;
|
||||||
import moe.yushi.authlibinjector.httpd.LegacySkinAPIFilter;
|
import moe.yushi.authlibinjector.httpd.LegacySkinAPIFilter;
|
||||||
|
import moe.yushi.authlibinjector.httpd.QueryProfileFilter;
|
||||||
|
import moe.yushi.authlibinjector.httpd.QueryUUIDsFilter;
|
||||||
import moe.yushi.authlibinjector.httpd.URLFilter;
|
import moe.yushi.authlibinjector.httpd.URLFilter;
|
||||||
import moe.yushi.authlibinjector.httpd.URLProcessor;
|
import moe.yushi.authlibinjector.httpd.URLProcessor;
|
||||||
import moe.yushi.authlibinjector.transform.AuthlibLogInterceptor;
|
import moe.yushi.authlibinjector.transform.AuthlibLogInterceptor;
|
||||||
|
@ -33,6 +35,9 @@ import moe.yushi.authlibinjector.transform.SkinWhitelistTransformUnit;
|
||||||
import moe.yushi.authlibinjector.transform.YggdrasilKeyTransformUnit;
|
import moe.yushi.authlibinjector.transform.YggdrasilKeyTransformUnit;
|
||||||
import moe.yushi.authlibinjector.transform.support.CitizensTransformer;
|
import moe.yushi.authlibinjector.transform.support.CitizensTransformer;
|
||||||
import moe.yushi.authlibinjector.util.Logging;
|
import moe.yushi.authlibinjector.util.Logging;
|
||||||
|
import moe.yushi.authlibinjector.yggdrasil.CustomYggdrasilAPIProvider;
|
||||||
|
import moe.yushi.authlibinjector.yggdrasil.MojangYggdrasilAPIProvider;
|
||||||
|
import moe.yushi.authlibinjector.yggdrasil.YggdrasilClient;
|
||||||
|
|
||||||
public final class AuthlibInjector {
|
public final class AuthlibInjector {
|
||||||
|
|
||||||
|
@ -271,12 +276,18 @@ public final class AuthlibInjector {
|
||||||
private static URLProcessor createURLProcessor(YggdrasilConfiguration config) {
|
private static URLProcessor createURLProcessor(YggdrasilConfiguration config) {
|
||||||
List<URLFilter> filters = new ArrayList<>();
|
List<URLFilter> filters = new ArrayList<>();
|
||||||
|
|
||||||
|
YggdrasilClient customClient = new YggdrasilClient(new CustomYggdrasilAPIProvider(config));
|
||||||
|
YggdrasilClient mojangClient = new YggdrasilClient(new MojangYggdrasilAPIProvider());
|
||||||
|
|
||||||
if (Boolean.TRUE.equals(config.getMeta().get("feature.legacy_skin_api"))) {
|
if (Boolean.TRUE.equals(config.getMeta().get("feature.legacy_skin_api"))) {
|
||||||
Logging.CONFIG.info("Disabled local redirect for legacy skin API, as the remote Yggdrasil server supports it");
|
Logging.CONFIG.info("Disabled local redirect for legacy skin API, as the remote Yggdrasil server supports it");
|
||||||
} else {
|
} else {
|
||||||
filters.add(new LegacySkinAPIFilter(config));
|
filters.add(new LegacySkinAPIFilter(customClient));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filters.add(new QueryUUIDsFilter(mojangClient, customClient));
|
||||||
|
filters.add(new QueryProfileFilter(mojangClient, customClient));
|
||||||
|
|
||||||
return new URLProcessor(filters, new DefaultURLRedirector(config));
|
return new URLProcessor(filters, new DefaultURLRedirector(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,14 +17,12 @@ import java.util.Optional;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import moe.yushi.authlibinjector.YggdrasilConfiguration;
|
|
||||||
import moe.yushi.authlibinjector.internal.fi.iki.elonen.IHTTPSession;
|
import moe.yushi.authlibinjector.internal.fi.iki.elonen.IHTTPSession;
|
||||||
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Response;
|
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Response;
|
||||||
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Status;
|
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Status;
|
||||||
import moe.yushi.authlibinjector.internal.org.json.simple.JSONObject;
|
import moe.yushi.authlibinjector.internal.org.json.simple.JSONObject;
|
||||||
import moe.yushi.authlibinjector.util.JsonUtils;
|
import moe.yushi.authlibinjector.util.JsonUtils;
|
||||||
import moe.yushi.authlibinjector.util.Logging;
|
import moe.yushi.authlibinjector.util.Logging;
|
||||||
import moe.yushi.authlibinjector.yggdrasil.CustomYggdrasilAPIProvider;
|
|
||||||
import moe.yushi.authlibinjector.yggdrasil.YggdrasilClient;
|
import moe.yushi.authlibinjector.yggdrasil.YggdrasilClient;
|
||||||
|
|
||||||
public class LegacySkinAPIFilter implements URLFilter {
|
public class LegacySkinAPIFilter implements URLFilter {
|
||||||
|
@ -33,8 +31,8 @@ public class LegacySkinAPIFilter implements URLFilter {
|
||||||
|
|
||||||
private YggdrasilClient upstream;
|
private YggdrasilClient upstream;
|
||||||
|
|
||||||
public LegacySkinAPIFilter(YggdrasilConfiguration configuration) {
|
public LegacySkinAPIFilter(YggdrasilClient upstream) {
|
||||||
this.upstream = new YggdrasilClient(new CustomYggdrasilAPIProvider(configuration));
|
this.upstream = upstream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package moe.yushi.authlibinjector.httpd;
|
||||||
|
|
||||||
|
import static java.util.Optional.empty;
|
||||||
|
import static moe.yushi.authlibinjector.util.UUIDUtils.fromUnsignedUUID;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import moe.yushi.authlibinjector.internal.fi.iki.elonen.IHTTPSession;
|
||||||
|
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Response;
|
||||||
|
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Status;
|
||||||
|
import moe.yushi.authlibinjector.yggdrasil.GameProfile;
|
||||||
|
import moe.yushi.authlibinjector.yggdrasil.YggdrasilClient;
|
||||||
|
import moe.yushi.authlibinjector.yggdrasil.YggdrasilResponseBuilder;
|
||||||
|
|
||||||
|
public class QueryProfileFilter implements URLFilter {
|
||||||
|
|
||||||
|
private static final Pattern PATH_REGEX = Pattern.compile("^/session/minecraft/profile/(?<uuid>[0-9a-f]{32})$");
|
||||||
|
|
||||||
|
private YggdrasilClient mojangClient;
|
||||||
|
private YggdrasilClient customClient;
|
||||||
|
|
||||||
|
public QueryProfileFilter(YggdrasilClient mojangClient, YggdrasilClient customClient) {
|
||||||
|
this.mojangClient = mojangClient;
|
||||||
|
this.customClient = customClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canHandle(String domain, String path) {
|
||||||
|
return domain.equals("sessionserver.mojang.com") && path.startsWith("/session/minecraft/profile/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Response> handle(String domain, String path, IHTTPSession session) throws IOException {
|
||||||
|
if (!domain.equals("sessionserver.mojang.com"))
|
||||||
|
return empty();
|
||||||
|
Matcher matcher = PATH_REGEX.matcher(path);
|
||||||
|
if (!matcher.find())
|
||||||
|
return empty();
|
||||||
|
|
||||||
|
UUID uuid;
|
||||||
|
try {
|
||||||
|
uuid = fromUnsignedUUID(matcher.group("uuid"));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return Optional.of(Response.newFixedLength(Status.NO_CONTENT, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean withSignature = false;
|
||||||
|
List<String> unsignedValues = session.getParameters().get("unsigned");
|
||||||
|
if (unsignedValues != null && unsignedValues.get(0).equals("false")) {
|
||||||
|
withSignature = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<GameProfile> response;
|
||||||
|
if (QueryUUIDsFilter.isMaskedUUID(uuid)) {
|
||||||
|
response = mojangClient.queryProfile(QueryUUIDsFilter.unmaskUUID(uuid), withSignature);
|
||||||
|
response.ifPresent(profile -> {
|
||||||
|
profile.id = uuid;
|
||||||
|
profile.name += QueryUUIDsFilter.NAME_SUFFIX;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
response = customClient.queryProfile(uuid, withSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.isPresent()) {
|
||||||
|
return Optional.of(Response.newFixedLength(Status.OK, null, YggdrasilResponseBuilder.queryProfile(response.get(), withSignature)));
|
||||||
|
} else {
|
||||||
|
return Optional.of(Response.newFixedLength(Status.NO_CONTENT, null, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package moe.yushi.authlibinjector.httpd;
|
||||||
|
|
||||||
|
import static moe.yushi.authlibinjector.util.IOUtils.CONTENT_TYPE_JSON;
|
||||||
|
import static moe.yushi.authlibinjector.util.IOUtils.asBytes;
|
||||||
|
import static moe.yushi.authlibinjector.util.IOUtils.asString;
|
||||||
|
import static moe.yushi.authlibinjector.util.JsonUtils.asJsonArray;
|
||||||
|
import static moe.yushi.authlibinjector.util.JsonUtils.asJsonString;
|
||||||
|
import static moe.yushi.authlibinjector.util.JsonUtils.parseJson;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import moe.yushi.authlibinjector.internal.fi.iki.elonen.IHTTPSession;
|
||||||
|
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Response;
|
||||||
|
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Status;
|
||||||
|
import moe.yushi.authlibinjector.util.Logging;
|
||||||
|
import moe.yushi.authlibinjector.yggdrasil.YggdrasilClient;
|
||||||
|
import moe.yushi.authlibinjector.yggdrasil.YggdrasilResponseBuilder;
|
||||||
|
|
||||||
|
public class QueryUUIDsFilter implements URLFilter {
|
||||||
|
|
||||||
|
private YggdrasilClient mojangClient;
|
||||||
|
private YggdrasilClient customClient;
|
||||||
|
|
||||||
|
public QueryUUIDsFilter(YggdrasilClient mojangClient, YggdrasilClient customClient) {
|
||||||
|
this.mojangClient = mojangClient;
|
||||||
|
this.customClient = customClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canHandle(String domain, String path) {
|
||||||
|
return domain.equals("api.mojang.com") && path.startsWith("/profiles/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Response> handle(String domain, String path, IHTTPSession session) throws IOException {
|
||||||
|
if (domain.equals("api.mojang.com") && path.equals("/profiles/minecraft") && session.getMethod().equals("POST")) {
|
||||||
|
Set<String> request = new LinkedHashSet<>();
|
||||||
|
asJsonArray(parseJson(asString(asBytes(session.getInputStream()))))
|
||||||
|
.forEach(element -> request.add(asJsonString(element)));
|
||||||
|
return Optional.of(Response.newFixedLength(Status.OK, CONTENT_TYPE_JSON,
|
||||||
|
YggdrasilResponseBuilder.queryUUIDs(performQuery(request))));
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, UUID> performQuery(Set<String> names) {
|
||||||
|
Set<String> customNames = new LinkedHashSet<>();
|
||||||
|
Set<String> mojangNames = new LinkedHashSet<>();
|
||||||
|
names.forEach(name -> {
|
||||||
|
if (name.endsWith(NAME_SUFFIX)) {
|
||||||
|
mojangNames.add(name.substring(0, name.length() - NAME_SUFFIX.length()));
|
||||||
|
} else {
|
||||||
|
customNames.add(name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, UUID> result = new LinkedHashMap<>();
|
||||||
|
if (!customNames.isEmpty()) {
|
||||||
|
result.putAll(customClient.queryUUIDs(customNames));
|
||||||
|
}
|
||||||
|
if (!mojangNames.isEmpty()) {
|
||||||
|
mojangClient.queryUUIDs(mojangNames)
|
||||||
|
.forEach((name, uuid) -> {
|
||||||
|
result.put(name + NAME_SUFFIX, maskUUID(uuid));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int MSB_MASK = 0x00008000;
|
||||||
|
static final String NAME_SUFFIX = "@mojang";
|
||||||
|
|
||||||
|
static UUID maskUUID(UUID uuid) {
|
||||||
|
if (isMaskedUUID(uuid)) {
|
||||||
|
Logging.HTTPD.warning("UUID already masked: " + uuid);
|
||||||
|
}
|
||||||
|
return new UUID(uuid.getMostSignificantBits() | MSB_MASK, uuid.getLeastSignificantBits());
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isMaskedUUID(UUID uuid) {
|
||||||
|
return (uuid.getMostSignificantBits() & MSB_MASK) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static UUID unmaskUUID(UUID uuid) {
|
||||||
|
return new UUID(uuid.getMostSignificantBits() & (~MSB_MASK), uuid.getLeastSignificantBits());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package moe.yushi.authlibinjector.httpd;
|
package moe.yushi.authlibinjector.httpd;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import moe.yushi.authlibinjector.internal.fi.iki.elonen.IHTTPSession;
|
import moe.yushi.authlibinjector.internal.fi.iki.elonen.IHTTPSession;
|
||||||
|
@ -9,5 +10,5 @@ public interface URLFilter {
|
||||||
|
|
||||||
boolean canHandle(String domain, String path);
|
boolean canHandle(String domain, String path);
|
||||||
|
|
||||||
Optional<Response> handle(String domain, String path, IHTTPSession session);
|
Optional<Response> handle(String domain, String path, IHTTPSession session) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package moe.yushi.authlibinjector.yggdrasil;
|
||||||
|
|
||||||
|
import static moe.yushi.authlibinjector.util.UUIDUtils.toUnsignedUUID;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import moe.yushi.authlibinjector.internal.org.json.simple.JSONArray;
|
||||||
|
import moe.yushi.authlibinjector.internal.org.json.simple.JSONObject;
|
||||||
|
|
||||||
|
public final class YggdrasilResponseBuilder {
|
||||||
|
private YggdrasilResponseBuilder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String queryUUIDs(Map<String, UUID> result) {
|
||||||
|
JSONArray response = new JSONArray();
|
||||||
|
result.forEach((name, uuid) -> {
|
||||||
|
JSONObject entry = new JSONObject();
|
||||||
|
entry.put("id", toUnsignedUUID(uuid));
|
||||||
|
entry.put("name", name);
|
||||||
|
response.add(entry);
|
||||||
|
});
|
||||||
|
return response.toJSONString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String queryProfile(GameProfile profile, boolean withSignature) {
|
||||||
|
JSONObject response = new JSONObject();
|
||||||
|
response.put("id", toUnsignedUUID(profile.id));
|
||||||
|
response.put("name", profile.name);
|
||||||
|
|
||||||
|
JSONArray properties = new JSONArray();
|
||||||
|
profile.properties.forEach((name, value) -> {
|
||||||
|
JSONObject entry = new JSONObject();
|
||||||
|
entry.put("name", name);
|
||||||
|
entry.put("value", value.value);
|
||||||
|
if (withSignature && value.signature != null) {
|
||||||
|
entry.put("signature", value.signature);
|
||||||
|
}
|
||||||
|
properties.add(entry);
|
||||||
|
});
|
||||||
|
response.put("properties", properties);
|
||||||
|
|
||||||
|
return response.toJSONString();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue