Support @mojang suffix

This commit is contained in:
yushijinhun 2018-12-31 02:45:21 +08:00
parent 9cfb6325a3
commit eeda91c329
No known key found for this signature in database
GPG key ID: 5BC167F73EA558E4
6 changed files with 232 additions and 6 deletions

View file

@ -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));
}

View file

@ -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

View file

@ -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));
}
}
}

View file

@ -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());
}
}

View file

@ -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;
}

View file

@ -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();
}
}