package com.mojang.authlib.yggdrasil;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.mojang.authlib.AuthenticationService;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.HttpAuthenticationService;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.exceptions.AuthenticationUnavailableException;
import com.mojang.authlib.minecraft.HttpMinecraftSessionService;
import com.mojang.authlib.minecraft.InsecureTextureException;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService;
import com.mojang.authlib.yggdrasil.request.JoinMinecraftServerRequest;
import com.mojang.authlib.yggdrasil.response.HasJoinedMinecraftServerResponse;
import com.mojang.authlib.yggdrasil.response.MinecraftProfilePropertiesResponse;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.authlib.yggdrasil.response.Response;
import com.mojang.util.UUIDTypeAdapter;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.apache.commons.codec.Charsets;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class YggdrasilMinecraftSessionService
extends HttpMinecraftSessionService {
private static final String[] WHITELISTED_DOMAINS
= new String[]{".minecraft.net",
".mojang.com"}; private static final Logger LOGGER = LogManager.getLogger();
private static final String BASE_URL
= "http://a...content-available-to-author-only...s.ru/web/new"; private static final URL JOIN_URL
= HttpAuthenticationService.
constantURL((String)"http://a...content-available-to-author-only...s.ru/web/new/join"); private static final URL CHECK_URL
= HttpAuthenticationService.
constantURL((String)"http://a...content-available-to-author-only...s.ru/web/new/hasJoined"); private final Gson gson
= new GsonBuilder
().
registerTypeAdapter((Type
)UUID.
class,
(Object)new UUIDTypeAdapter
()).
create(); private final LoadingCache<GameProfile, GameProfile> insecureProfiles;
protected YggdrasilMinecraftSessionService(YggdrasilAuthenticationService authenticationService) {
super((HttpAuthenticationService)authenticationService);
this.insecureProfiles = CacheBuilder.newBuilder().expireAfterWrite(6, TimeUnit.HOURS).build((CacheLoader)new /* Unavailable Anonymous Inner Class!! */);
try {
this.publicKey = keyFactory.generatePublic(spec);
}
throw new Error("Missing/invalid yggdrasil public key!"); }
}
JoinMinecraftServerRequest request = new JoinMinecraftServerRequest();
request.accessToken = authenticationToken;
request.selectedProfile = profile.getId();
request.serverId = serverId;
this.
getAuthenticationService().
makeRequest(JOIN_URL,
(Object)request,
(Class)Response.
class); }
public GameProfile hasJoinedServer
(GameProfile user,
String serverId
) throws AuthenticationUnavailableException
{ HashMap
<String, String
> arguments
= new HashMap
<String, String
>(); arguments.put("username", user.getName());
arguments.put("serverId", serverId);
URL url
= HttpAuthenticationService.
concatenateURL((URL)CHECK_URL,
(String)HttpAuthenticationService.
buildQuery(arguments
)); try {
HasJoinedMinecraftServerResponse response
= (HasJoinedMinecraftServerResponse
)this.
getAuthenticationService().
makeRequest(url,
(Object)null,
(Class)HasJoinedMinecraftServerResponse.
class); if (response != null && response.getId() != null) {
GameProfile result = new GameProfile(response.getId(), user.getName());
if (response.getProperties() != null) {
result.getProperties().putAll((Multimap)response.getProperties());
}
return result;
}
return null;
}
catch (AuthenticationUnavailableException e) {
throw e;
}
return null;
}
}
public Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getTextures(GameProfile profile, boolean requireSecure) {
MinecraftTexturesPayload result;
Property textureProperty
= (Property
)Iterables.
getFirst((Iterable
)profile.
getProperties().
get((Object)"textures"),
(Object)null); if (textureProperty == null) {
return new HashMap<MinecraftProfileTexture.Type, MinecraftProfileTexture>();
}
if (requireSecure) {
if (!textureProperty.hasSignature()) {
LOGGER.error("Signature is missing from textures payload");
throw new InsecureTextureException("Signature is missing from textures payload");
}
if (!textureProperty.isSignatureValid(this.publicKey)) {
LOGGER.error("Textures payload has been tampered with (signature invalid)");
throw new InsecureTextureException("Textures payload has been tampered with (signature invalid)");
}
}
try {
String json
= new String(Base64.
decodeBase64((String)textureProperty.
getValue()), Charsets.
UTF_8); result = (MinecraftTexturesPayload)this.gson.fromJson(json, (Class)MinecraftTexturesPayload.class);
}
catch (JsonParseException e) {
LOGGER.
error("Could not decode textures payload",
(Throwable)e
); return new HashMap<MinecraftProfileTexture.Type, MinecraftProfileTexture>();
}
if (result.getTextures() == null) {
return new HashMap<MinecraftProfileTexture.Type, MinecraftProfileTexture>();
}
for (Map.Entry entry
: result.
getTextures().
entrySet()) { if (YggdrasilMinecraftSessionService.isWhitelistedDomain(((MinecraftProfileTexture)entry.getValue()).getUrl())) continue;
LOGGER.error("Textures payload has been tampered with (non-whitelisted domain)");
return new HashMap<MinecraftProfileTexture.Type, MinecraftProfileTexture>();
}
return result.getTextures();
}
public GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure) {
if (profile.getId() == null) {
return profile;
}
if (!requireSecure) {
return (GameProfile
)this.
insecureProfiles.
getUnchecked((Object)profile
); }
return this.fillGameProfile(profile, true);
}
protected GameProfile fillGameProfile(GameProfile profile, boolean requireSecure) {
try {
URL url
= HttpAuthenticationService.
constantURL((String)("https://s...content-available-to-author-only...g.com/session/minecraft/profile/" + UUIDTypeAdapter.
fromUUID((UUID
)profile.
getId()))); url
= HttpAuthenticationService.
concatenateURL((URL)url,
(String)("unsigned=" + !requireSecure
)); MinecraftProfilePropertiesResponse response
= (MinecraftProfilePropertiesResponse
)this.
getAuthenticationService().
makeRequest(url,
(Object)null,
(Class)MinecraftProfilePropertiesResponse.
class); if (response == null) {
LOGGER.
debug("Couldn't fetch profile properties for " + (Object)profile
+ " as the profile does not exist"); return profile;
}
GameProfile result = new GameProfile(response.getId(), response.getName());
result.getProperties().putAll((Multimap)response.getProperties());
profile.getProperties().putAll((Multimap)response.getProperties());
LOGGER.
debug("Successfully fetched profile properties for " + (Object)profile
); return result;
}
LOGGER.
warn("Couldn't look up profile properties for " + (Object)profile,
(Throwable)e
); return profile;
}
}
public YggdrasilAuthenticationService getAuthenticationService() {
return (YggdrasilAuthenticationService)super.getAuthenticationService();
}
private static boolean isWhitelistedDomain
(String url
) { URI uri = null;
try {
uri = new URI(url);
}
catch (URISyntaxException e) {
}
String domain
= uri.
getHost(); for (int i = 0; i < WHITELISTED_DOMAINS.length; ++i) {
if (!domain.endsWith(WHITELISTED_DOMAINS[i])) continue;
return true;
}
return false;
}
}