forked from MirrorHub/authlib-injector
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.LegacySkinAPIFilter;
|
||||
import moe.yushi.authlibinjector.httpd.QueryProfileFilter;
|
||||
import moe.yushi.authlibinjector.httpd.QueryUUIDsFilter;
|
||||
import moe.yushi.authlibinjector.httpd.URLFilter;
|
||||
import moe.yushi.authlibinjector.httpd.URLProcessor;
|
||||
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.support.CitizensTransformer;
|
||||
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 {
|
||||
|
||||
|
@ -271,12 +276,18 @@ public final class AuthlibInjector {
|
|||
private static URLProcessor createURLProcessor(YggdrasilConfiguration config) {
|
||||
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"))) {
|
||||
Logging.CONFIG.info("Disabled local redirect for legacy skin API, as the remote Yggdrasil server supports it");
|
||||
} 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));
|
||||
}
|
||||
|
||||
|
|
|
@ -17,14 +17,12 @@ import java.util.Optional;
|
|||
import java.util.regex.Matcher;
|
||||
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.Response;
|
||||
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Status;
|
||||
import moe.yushi.authlibinjector.internal.org.json.simple.JSONObject;
|
||||
import moe.yushi.authlibinjector.util.JsonUtils;
|
||||
import moe.yushi.authlibinjector.util.Logging;
|
||||
import moe.yushi.authlibinjector.yggdrasil.CustomYggdrasilAPIProvider;
|
||||
import moe.yushi.authlibinjector.yggdrasil.YggdrasilClient;
|
||||
|
||||
public class LegacySkinAPIFilter implements URLFilter {
|
||||
|
@ -33,8 +31,8 @@ public class LegacySkinAPIFilter implements URLFilter {
|
|||
|
||||
private YggdrasilClient upstream;
|
||||
|
||||
public LegacySkinAPIFilter(YggdrasilConfiguration configuration) {
|
||||
this.upstream = new YggdrasilClient(new CustomYggdrasilAPIProvider(configuration));
|
||||
public LegacySkinAPIFilter(YggdrasilClient upstream) {
|
||||
this.upstream = upstream;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import moe.yushi.authlibinjector.internal.fi.iki.elonen.IHTTPSession;
|
||||
|
@ -9,5 +10,5 @@ public interface URLFilter {
|
|||
|
||||
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