first commit
This commit is contained in:
parent
8d91e280b6
commit
80352a24bf
13
.settings/org.eclipse.buildship.core.prefs
Normal file
13
.settings/org.eclipse.buildship.core.prefs
Normal file
@ -0,0 +1,13 @@
|
||||
arguments=
|
||||
auto.sync=false
|
||||
build.scans.enabled=false
|
||||
connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(8.5))
|
||||
connection.project.dir=
|
||||
eclipse.preferences.version=1
|
||||
gradle.user.home=
|
||||
java.home=
|
||||
jvm.arguments=
|
||||
offline.mode=false
|
||||
override.workspace.settings=false
|
||||
show.console.view=false
|
||||
show.executions.view=false
|
||||
11
.settings/org.eclipse.jdt.core.prefs
Normal file
11
.settings/org.eclipse.jdt.core.prefs
Normal file
@ -0,0 +1,11 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=21
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.source=21
|
||||
66
build.gradle
Normal file
66
build.gradle
Normal file
@ -0,0 +1,66 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.github.johnrengelman:shadow:8.1.1"
|
||||
classpath group: "org.yaml", name: "snakeyaml", version: "1.19"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "net.researchgate.release" version "3.0.2"
|
||||
}
|
||||
|
||||
apply plugin: "java-library"
|
||||
apply plugin: "eclipse"
|
||||
apply plugin: "com.github.johnrengelman.shadow"
|
||||
|
||||
|
||||
version = "1.5.21.1"
|
||||
group = "fr.azures04.modun"
|
||||
archivesBaseName = "authlib"
|
||||
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
maven {
|
||||
url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/"
|
||||
}
|
||||
maven { url = "https://oss.sonatype.org/content/repositories/snapshots" }
|
||||
maven { url = "https://oss.sonatype.org/content/repositories/central" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.apache.logging.log4j:log4j-core:2.9.0")
|
||||
implementation("com.google.code.gson:gson:2.8.6")
|
||||
implementation("org.apache.commons:commons-lang3:3.3.2")
|
||||
implementation("commons-io:commons-io:2.4")
|
||||
implementation("javax.annotation:javax.annotation-api:1.3.2")
|
||||
implementation("com.google.code.findbugs:jsr305:3.0.2")
|
||||
implementation("com.google.guava:guava:15.0")
|
||||
implementation("commons-codec:commons-codec:1.9")
|
||||
}
|
||||
|
||||
sourceCompatibility = targetCompatibility = '1.8'
|
||||
compileJava {
|
||||
sourceCompatibility = targetCompatibility = '1.8'
|
||||
}
|
||||
|
||||
release {
|
||||
failOnPublishNeeded = true
|
||||
failOnSnapshotDependencies = true
|
||||
failOnUnversionedFiles = true
|
||||
failOnUpdateNeeded = true
|
||||
preTagCommitMessage = "[Gradle Release Plugin] - pre tag commit: "
|
||||
tagCommitMessage = "[Gradle Release Plugin] - creating tag: "
|
||||
newVersionCommitMessage = "[Gradle Release Plugin] - new version commit: "
|
||||
tagTemplate = "1.5.21.1"
|
||||
versionPropertyFile = "gradle.properties"
|
||||
snapshotSuffix = "-SNAPSHOT"
|
||||
buildTasks = ["shadowJar"]
|
||||
|
||||
git {
|
||||
requireBranch.set("main")
|
||||
}
|
||||
}
|
||||
12
gradle/libs.versions.toml
Normal file
12
gradle/libs.versions.toml
Normal file
@ -0,0 +1,12 @@
|
||||
# This file was generated by the Gradle 'init' task.
|
||||
# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format
|
||||
|
||||
[versions]
|
||||
commons-math3 = "3.6.1"
|
||||
guava = "32.1.2-jre"
|
||||
junit-jupiter = "5.10.0"
|
||||
|
||||
[libraries]
|
||||
commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" }
|
||||
guava = { module = "com.google.guava:guava", version.ref = "guava" }
|
||||
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
|
||||
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
172
gradlew
vendored
Normal file
172
gradlew
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
84
gradlew.bat
vendored
Normal file
84
gradlew.bat
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
18
settings.gradle
Normal file
18
settings.gradle
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* This settings file was generated by the Gradle 'init' task.
|
||||
*
|
||||
* The settings file is used to specify which projects to include in your build.
|
||||
* In a single project build this file can be empty or even removed.
|
||||
*
|
||||
* Detailed information about configuring a multi-project build in Gradle can be found
|
||||
* in the user guide at https://docs.gradle.org/4.1/userguide/multi_project_builds.html
|
||||
*/
|
||||
|
||||
/*
|
||||
// To declare projects as part of a multi-project build use the 'include' method
|
||||
include 'shared'
|
||||
include 'api'
|
||||
include 'services:webservice'
|
||||
*/
|
||||
|
||||
rootProject.name = 'Authlib'
|
||||
25
src/main/java/com/mojang/authlib/Agent.java
Normal file
25
src/main/java/com/mojang/authlib/Agent.java
Normal file
@ -0,0 +1,25 @@
|
||||
package com.mojang.authlib;
|
||||
|
||||
public class Agent {
|
||||
public static final Agent MINECRAFT = new Agent("Minecraft", 1);
|
||||
public static final Agent SCROLLS = new Agent("Scrolls", 1);
|
||||
private final String name;
|
||||
private final int version;
|
||||
|
||||
public Agent(String name, int version) {
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Agent{name='" + this.name + '\'' + ", version=" + this.version + '}';
|
||||
}
|
||||
}
|
||||
11
src/main/java/com/mojang/authlib/AuthenticationService.java
Normal file
11
src/main/java/com/mojang/authlib/AuthenticationService.java
Normal file
@ -0,0 +1,11 @@
|
||||
package com.mojang.authlib;
|
||||
|
||||
import com.mojang.authlib.minecraft.MinecraftSessionService;
|
||||
|
||||
public interface AuthenticationService {
|
||||
UserAuthentication createUserAuthentication(Agent agent);
|
||||
|
||||
MinecraftSessionService createMinecraftSessionService();
|
||||
|
||||
GameProfileRepository createProfileRepository();
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
package com.mojang.authlib;
|
||||
|
||||
public abstract class BaseAuthenticationService implements AuthenticationService {
|
||||
}
|
||||
245
src/main/java/com/mojang/authlib/BaseUserAuthentication.java
Normal file
245
src/main/java/com/mojang/authlib/BaseUserAuthentication.java
Normal file
@ -0,0 +1,245 @@
|
||||
package com.mojang.authlib;
|
||||
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
import com.mojang.util.UUIDTypeAdapter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public abstract class BaseUserAuthentication implements UserAuthentication {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
protected static final String STORAGE_KEY_PROFILE_NAME = "displayName";
|
||||
protected static final String STORAGE_KEY_PROFILE_ID = "uuid";
|
||||
protected static final String STORAGE_KEY_PROFILE_PROPERTIES = "profileProperties";
|
||||
protected static final String STORAGE_KEY_USER_NAME = "username";
|
||||
protected static final String STORAGE_KEY_USER_ID = "userid";
|
||||
protected static final String STORAGE_KEY_USER_PROPERTIES = "userProperties";
|
||||
private final AuthenticationService authenticationService;
|
||||
private final PropertyMap userProperties = new PropertyMap();
|
||||
private String userid;
|
||||
private String username;
|
||||
private String password;
|
||||
private GameProfile selectedProfile;
|
||||
private UserType userType;
|
||||
|
||||
protected BaseUserAuthentication(AuthenticationService authenticationService) {
|
||||
Validate.notNull(authenticationService);
|
||||
this.authenticationService = authenticationService;
|
||||
}
|
||||
|
||||
public boolean canLogIn() {
|
||||
return !this.canPlayOnline() && StringUtils.isNotBlank(this.getUsername()) && StringUtils.isNotBlank(this.getPassword());
|
||||
}
|
||||
|
||||
public void logOut() {
|
||||
this.password = null;
|
||||
this.userid = null;
|
||||
this.setSelectedProfile((GameProfile) null);
|
||||
this.getModifiableUserProperties().clear();
|
||||
this.setUserType((UserType) null);
|
||||
}
|
||||
|
||||
public boolean isLoggedIn() {
|
||||
return this.getSelectedProfile() != null;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
if (this.isLoggedIn() && this.canPlayOnline()) {
|
||||
throw new IllegalStateException("Cannot change username whilst logged in & online");
|
||||
} else {
|
||||
this.username = username;
|
||||
}
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
if (this.isLoggedIn() && this.canPlayOnline() && StringUtils.isNotBlank(password)) {
|
||||
throw new IllegalStateException("Cannot set password whilst logged in & online");
|
||||
} else {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
protected String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
protected String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void loadFromStorage(Map <String, Object>credentials) {
|
||||
this.logOut();
|
||||
this.setUsername(String.valueOf(credentials.get("username")));
|
||||
if (credentials.containsKey("userid")) {
|
||||
this.userid = String.valueOf(credentials.get("userid"));
|
||||
} else {
|
||||
this.userid = this.username;
|
||||
}
|
||||
|
||||
if (credentials.containsKey("userProperties")) {
|
||||
try {
|
||||
for (Map <String, String> propertyMap : (List <Map<String, String>> ) credentials.get("userProperties")) {
|
||||
String name = (String) propertyMap.get("name");
|
||||
String value = (String) propertyMap.get("value");
|
||||
String signature = (String) propertyMap.get("signature");
|
||||
if (signature == null) {
|
||||
this.getModifiableUserProperties().put(name, new Property(name, value));
|
||||
} else {
|
||||
this.getModifiableUserProperties().put(name, new Property(name, value, signature));
|
||||
}
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
LOGGER.warn("Couldn't deserialize user properties", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
if (credentials.containsKey("displayName") && credentials.containsKey("uuid")) {
|
||||
GameProfile profile = new GameProfile(UUIDTypeAdapter.fromString(String.valueOf(credentials.get("uuid"))), String.valueOf(credentials.get("displayName")));
|
||||
if (credentials.containsKey("profileProperties")) {
|
||||
try {
|
||||
for (Map <String, String> propertyMap : (List <Map <String, String>> ) credentials.get("profileProperties")) {
|
||||
String name = (String) propertyMap.get("name");
|
||||
String value = (String) propertyMap.get("value");
|
||||
String signature = (String) propertyMap.get("signature");
|
||||
if (signature == null) {
|
||||
profile.getProperties().put(name, new Property(name, value));
|
||||
} else {
|
||||
profile.getProperties().put(name, new Property(name, value, signature));
|
||||
}
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
LOGGER.warn("Couldn't deserialize profile properties", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
this.setSelectedProfile(profile);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Map <String, Object>saveForStorage() {
|
||||
Map <String, Object>result = new HashMap<>();
|
||||
if (this.getUsername() != null) {
|
||||
result.put("username", this.getUsername());
|
||||
}
|
||||
|
||||
if (this.getUserID() != null) {
|
||||
result.put("userid", this.getUserID());
|
||||
} else if (this.getUsername() != null) {
|
||||
result.put("username", this.getUsername());
|
||||
}
|
||||
|
||||
if (!this.getUserProperties().isEmpty()) {
|
||||
List <Map <String, String>> properties = new ArrayList<>();
|
||||
|
||||
for (Property userProperty : this.getUserProperties().values()) {
|
||||
Map <String, String>property = new HashMap<>();
|
||||
property.put("name", userProperty.getName());
|
||||
property.put("value", userProperty.getValue());
|
||||
property.put("signature", userProperty.getSignature());
|
||||
properties.add(property);
|
||||
}
|
||||
|
||||
result.put("userProperties", properties);
|
||||
}
|
||||
|
||||
GameProfile selectedProfile = this.getSelectedProfile();
|
||||
if (selectedProfile != null) {
|
||||
result.put("displayName", selectedProfile.getName());
|
||||
result.put("uuid", selectedProfile.getId());
|
||||
List <Map <String, String>> properties = new ArrayList<>();
|
||||
|
||||
for (Property profileProperty : selectedProfile.getProperties().values()) {
|
||||
Map <String, String>property = new HashMap<>();
|
||||
property.put("name", profileProperty.getName());
|
||||
property.put("value", profileProperty.getValue());
|
||||
property.put("signature", profileProperty.getSignature());
|
||||
properties.add(property);
|
||||
}
|
||||
|
||||
if (!properties.isEmpty()) {
|
||||
result.put("profileProperties", properties);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void setSelectedProfile(GameProfile selectedProfile) {
|
||||
this.selectedProfile = selectedProfile;
|
||||
}
|
||||
|
||||
public GameProfile getSelectedProfile() {
|
||||
return this.selectedProfile;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(this.getClass().getSimpleName());
|
||||
result.append("{");
|
||||
if (this.isLoggedIn()) {
|
||||
result.append("Logged in as ");
|
||||
result.append(this.getUsername());
|
||||
if (this.getSelectedProfile() != null) {
|
||||
result.append(" / ");
|
||||
result.append(this.getSelectedProfile());
|
||||
result.append(" - ");
|
||||
if (this.canPlayOnline()) {
|
||||
result.append("Online");
|
||||
} else {
|
||||
result.append("Offline");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.append("Not logged in");
|
||||
}
|
||||
|
||||
result.append("}");
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public AuthenticationService getAuthenticationService() {
|
||||
return this.authenticationService;
|
||||
}
|
||||
|
||||
public String getUserID() {
|
||||
return this.userid;
|
||||
}
|
||||
|
||||
public PropertyMap getUserProperties() {
|
||||
if (this.isLoggedIn()) {
|
||||
PropertyMap result = new PropertyMap();
|
||||
result.putAll(this.getModifiableUserProperties());
|
||||
return result;
|
||||
} else {
|
||||
return new PropertyMap();
|
||||
}
|
||||
}
|
||||
|
||||
protected PropertyMap getModifiableUserProperties() {
|
||||
return this.userProperties;
|
||||
}
|
||||
|
||||
public UserType getUserType() {
|
||||
if (this.isLoggedIn()) {
|
||||
return this.userType == null ? UserType.LEGACY : this.userType;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void setUserType(UserType userType) {
|
||||
this.userType = userType;
|
||||
}
|
||||
|
||||
protected void setUserid(String userid) {
|
||||
this.userid = userid;
|
||||
}
|
||||
}
|
||||
78
src/main/java/com/mojang/authlib/GameProfile.java
Normal file
78
src/main/java/com/mojang/authlib/GameProfile.java
Normal file
@ -0,0 +1,78 @@
|
||||
package com.mojang.authlib;
|
||||
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
import java.util.UUID;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
public class GameProfile {
|
||||
private final UUID id;
|
||||
private final String name;
|
||||
private final PropertyMap properties = new PropertyMap();
|
||||
private boolean legacy;
|
||||
|
||||
public GameProfile(UUID id, String name) {
|
||||
if (id == null && StringUtils.isBlank(name)) {
|
||||
throw new IllegalArgumentException("Name and ID cannot both be blank");
|
||||
} else {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public PropertyMap getProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
return this.id != null && StringUtils.isNotBlank(this.getName());
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
} else if (o != null && this.getClass() == o.getClass()) {
|
||||
GameProfile that = (GameProfile) o;
|
||||
if (this.id != null) {
|
||||
if (!this.id.equals(that.id)) {
|
||||
return false;
|
||||
}
|
||||
} else if (that.id != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.name != null) {
|
||||
if (!this.name.equals(that.name)) {
|
||||
return false;
|
||||
}
|
||||
} else if (that.name != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int result = this.id != null ? this.id.hashCode() : 0;
|
||||
return 31 * result + (this.name != null ? this.name.hashCode() : 0);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return (new ToStringBuilder(this)).append("id", this.id).append("name", this.name).append("properties", this.properties).append("legacy", this.legacy).toString();
|
||||
}
|
||||
|
||||
public boolean isLegacy() {
|
||||
return this.legacy;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package com.mojang.authlib;
|
||||
|
||||
public interface GameProfileRepository {
|
||||
void findProfilesByNames(String[] args, Agent agent, ProfileLookupCallback callback);
|
||||
}
|
||||
173
src/main/java/com/mojang/authlib/HttpAuthenticationService.java
Normal file
173
src/main/java/com/mojang/authlib/HttpAuthenticationService.java
Normal file
@ -0,0 +1,173 @@
|
||||
package com.mojang.authlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.io.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public abstract class HttpAuthenticationService extends BaseAuthenticationService {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private final Proxy proxy;
|
||||
|
||||
protected HttpAuthenticationService(Proxy proxy) {
|
||||
Validate.notNull(proxy);
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
public Proxy getProxy() {
|
||||
return this.proxy;
|
||||
}
|
||||
|
||||
protected HttpURLConnection createUrlConnection(URL url) throws IOException {
|
||||
Validate.notNull(url);
|
||||
LOGGER.debug("Opening connection to " + url);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection(this.proxy);
|
||||
connection.setConnectTimeout(15000);
|
||||
connection.setReadTimeout(15000);
|
||||
connection.setUseCaches(false);
|
||||
return connection;
|
||||
}
|
||||
|
||||
public String performPostRequest(URL url, String post, String contentType) throws IOException {
|
||||
Validate.notNull(url);
|
||||
Validate.notNull(post);
|
||||
Validate.notNull(contentType);
|
||||
HttpURLConnection connection = this.createUrlConnection(url);
|
||||
byte[] postAsBytes = post.getBytes(Charsets.UTF_8);
|
||||
connection.setRequestProperty("Content-Type", contentType + "; charset=utf-8");
|
||||
connection.setRequestProperty("Content-Length", "" + postAsBytes.length);
|
||||
connection.setDoOutput(true);
|
||||
LOGGER.debug("Writing POST data to " + url + ": " + post);
|
||||
OutputStream outputStream = null;
|
||||
|
||||
try {
|
||||
outputStream = connection.getOutputStream();
|
||||
IOUtils.write(postAsBytes, outputStream);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(outputStream);
|
||||
}
|
||||
|
||||
LOGGER.debug("Reading data from " + url);
|
||||
InputStream inputStream = null;
|
||||
|
||||
String response;
|
||||
try {
|
||||
inputStream = connection.getInputStream();
|
||||
String result = IOUtils.toString(inputStream, Charsets.UTF_8);
|
||||
LOGGER.debug("Successful read, server response was " + connection.getResponseCode());
|
||||
LOGGER.debug("Response: " + result);
|
||||
return result;
|
||||
} catch (IOException exception) {
|
||||
IOUtils.closeQuietly(inputStream);
|
||||
inputStream = connection.getErrorStream();
|
||||
if (inputStream == null) {
|
||||
LOGGER.debug("Request failed", exception);
|
||||
throw exception;
|
||||
}
|
||||
|
||||
LOGGER.debug("Reading error page from " + url);
|
||||
String result = IOUtils.toString(inputStream, Charsets.UTF_8);
|
||||
LOGGER.debug("Successful read, server response was " + connection.getResponseCode());
|
||||
LOGGER.debug("Response: " + result);
|
||||
response = result;
|
||||
} finally {
|
||||
IOUtils.closeQuietly(inputStream);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public String performGetRequest(URL url) throws IOException {
|
||||
Validate.notNull(url);
|
||||
HttpURLConnection connection = this.createUrlConnection(url);
|
||||
LOGGER.debug("Reading data from " + url);
|
||||
InputStream inputStream = null;
|
||||
|
||||
String response;
|
||||
try {
|
||||
inputStream = connection.getInputStream();
|
||||
String result = IOUtils.toString(inputStream, Charsets.UTF_8);
|
||||
LOGGER.debug("Successful read, server response was " + connection.getResponseCode());
|
||||
LOGGER.debug("Response: " + result);
|
||||
return result;
|
||||
} catch (IOException exception) {
|
||||
IOUtils.closeQuietly(inputStream);
|
||||
inputStream = connection.getErrorStream();
|
||||
if (inputStream == null) {
|
||||
LOGGER.debug("Request failed", exception);
|
||||
throw exception;
|
||||
}
|
||||
|
||||
LOGGER.debug("Reading error page from " + url);
|
||||
String result = IOUtils.toString(inputStream, Charsets.UTF_8);
|
||||
LOGGER.debug("Successful read, server response was " + connection.getResponseCode());
|
||||
LOGGER.debug("Response: " + result);
|
||||
response = result;
|
||||
} finally {
|
||||
IOUtils.closeQuietly(inputStream);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static URL constantURL(String url) {
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (MalformedURLException exception) {
|
||||
throw new Error("Couldn't create constant for " + url, exception);
|
||||
}
|
||||
}
|
||||
|
||||
public static String buildQuery(Map <String, Object>query) {
|
||||
if (query == null) {
|
||||
return "";
|
||||
} else {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (Map.Entry <String, Object> entry : query.entrySet()) {
|
||||
if (builder.length()>0) {
|
||||
builder.append('&');
|
||||
}
|
||||
|
||||
try {
|
||||
builder.append(URLEncoder.encode((String) entry.getKey(), "UTF-8"));
|
||||
} catch (UnsupportedEncodingException exception) {
|
||||
LOGGER.error("Unexpected exception building query", exception);
|
||||
}
|
||||
|
||||
if (entry.getValue() != null) {
|
||||
builder.append('=');
|
||||
|
||||
try {
|
||||
builder.append(URLEncoder.encode(entry.getValue().toString(), "UTF-8"));
|
||||
} catch (UnsupportedEncodingException exception) {
|
||||
LOGGER.error("Unexpected exception building query", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static URL concatenateURL(URL url, String query) {
|
||||
try {
|
||||
return url.getQuery() != null && url.getQuery().length()>0 ? new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile() + "&" + query) : new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile() + "?" + query);
|
||||
} catch (MalformedURLException exception) {
|
||||
throw new IllegalArgumentException("Could not concatenate given URL with GET arguments!", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/main/java/com/mojang/authlib/HttpUserAuthentication.java
Normal file
11
src/main/java/com/mojang/authlib/HttpUserAuthentication.java
Normal file
@ -0,0 +1,11 @@
|
||||
package com.mojang.authlib;
|
||||
|
||||
public abstract class HttpUserAuthentication extends BaseUserAuthentication {
|
||||
protected HttpUserAuthentication(HttpAuthenticationService authenticationService) {
|
||||
super(authenticationService);
|
||||
}
|
||||
|
||||
public HttpAuthenticationService getAuthenticationService() {
|
||||
return (HttpAuthenticationService) super.getAuthenticationService();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.mojang.authlib;
|
||||
|
||||
public interface ProfileLookupCallback {
|
||||
void onProfileLookupSucceeded(GameProfile profile);
|
||||
|
||||
void onProfileLookupFailed(GameProfile profile, Exception exception);
|
||||
}
|
||||
39
src/main/java/com/mojang/authlib/UserAuthentication.java
Normal file
39
src/main/java/com/mojang/authlib/UserAuthentication.java
Normal file
@ -0,0 +1,39 @@
|
||||
package com.mojang.authlib;
|
||||
|
||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
import java.util.Map;
|
||||
|
||||
public interface UserAuthentication {
|
||||
boolean canLogIn();
|
||||
|
||||
void logIn() throws AuthenticationException;
|
||||
|
||||
void logOut();
|
||||
|
||||
boolean isLoggedIn();
|
||||
|
||||
boolean canPlayOnline();
|
||||
|
||||
GameProfile[] getAvailableProfiles();
|
||||
|
||||
GameProfile getSelectedProfile();
|
||||
|
||||
void selectGameProfile(GameProfile profile) throws AuthenticationException;
|
||||
|
||||
void loadFromStorage(Map<String, Object> credentials);
|
||||
|
||||
Map <String, Object>saveForStorage();
|
||||
|
||||
void setUsername(String username);
|
||||
|
||||
void setPassword(String password);
|
||||
|
||||
String getAuthenticatedToken();
|
||||
|
||||
String getUserID();
|
||||
|
||||
PropertyMap getUserProperties();
|
||||
|
||||
UserType getUserType();
|
||||
}
|
||||
30
src/main/java/com/mojang/authlib/UserType.java
Normal file
30
src/main/java/com/mojang/authlib/UserType.java
Normal file
@ -0,0 +1,30 @@
|
||||
package com.mojang.authlib;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum UserType {
|
||||
LEGACY("legacy"),
|
||||
MOJANG("mojang");
|
||||
|
||||
private static final Map<String, UserType> BY_NAME = new HashMap <>();
|
||||
private final String name;
|
||||
|
||||
private UserType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static UserType byName(String name) {
|
||||
return (UserType) BY_NAME.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
static {
|
||||
for (UserType type : values()) {
|
||||
BY_NAME.put(type.name, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.mojang.authlib.exceptions;
|
||||
|
||||
public class AuthenticationException extends Exception {
|
||||
private static final long serialVersionUID = 4871782997693067748L;
|
||||
|
||||
public AuthenticationException() {}
|
||||
|
||||
public AuthenticationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AuthenticationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public AuthenticationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.mojang.authlib.exceptions;
|
||||
|
||||
public class AuthenticationUnavailableException extends AuthenticationException {
|
||||
private static final long serialVersionUID = 3489067028489894211L;
|
||||
|
||||
public AuthenticationUnavailableException() {}
|
||||
|
||||
public AuthenticationUnavailableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AuthenticationUnavailableException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public AuthenticationUnavailableException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.mojang.authlib.exceptions;
|
||||
|
||||
public class InvalidCredentialsException extends AuthenticationException {
|
||||
private static final long serialVersionUID = -2648308630825738711L;
|
||||
|
||||
public InvalidCredentialsException() {}
|
||||
|
||||
public InvalidCredentialsException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InvalidCredentialsException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public InvalidCredentialsException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.mojang.authlib.exceptions;
|
||||
|
||||
public class UserMigratedException extends InvalidCredentialsException {
|
||||
private static final long serialVersionUID = 6537065075102000065L;
|
||||
|
||||
public UserMigratedException() {}
|
||||
|
||||
public UserMigratedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UserMigratedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public UserMigratedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.mojang.authlib.legacy;
|
||||
|
||||
import com.mojang.authlib.Agent;
|
||||
import com.mojang.authlib.GameProfileRepository;
|
||||
import com.mojang.authlib.HttpAuthenticationService;
|
||||
import java.net.Proxy;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
public class LegacyAuthenticationService extends HttpAuthenticationService {
|
||||
protected LegacyAuthenticationService(Proxy proxy) {
|
||||
super(proxy);
|
||||
}
|
||||
|
||||
public LegacyUserAuthentication createUserAuthentication(Agent agent) {
|
||||
Validate.notNull(agent);
|
||||
if (agent != Agent.MINECRAFT) {
|
||||
throw new IllegalArgumentException("Legacy authentication cannot handle anything but Minecraft");
|
||||
} else {
|
||||
return new LegacyUserAuthentication(this);
|
||||
}
|
||||
}
|
||||
|
||||
public LegacyMinecraftSessionService createMinecraftSessionService() {
|
||||
return new LegacyMinecraftSessionService(this);
|
||||
}
|
||||
|
||||
public GameProfileRepository createProfileRepository() {
|
||||
throw new UnsupportedOperationException("Legacy authentication service has no profile repository");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package com.mojang.authlib.legacy;
|
||||
|
||||
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.MinecraftProfileTexture;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class LegacyMinecraftSessionService extends HttpMinecraftSessionService {
|
||||
private static final String BASE_URL = "http://session.minecraft.net/game/";
|
||||
private static final URL JOIN_URL = HttpAuthenticationService.constantURL(BASE_URL + "joinserver.jsp");
|
||||
private static final URL CHECK_URL = HttpAuthenticationService.constantURL(BASE_URL + "checkserver.jsp");
|
||||
|
||||
protected LegacyMinecraftSessionService(LegacyAuthenticationService authenticationService) {
|
||||
super(authenticationService);
|
||||
}
|
||||
|
||||
public void joinServer(GameProfile profile, String authenticationToken, String serverId) throws AuthenticationException {
|
||||
Map <String, Object>arguments = new HashMap <>();
|
||||
arguments.put("user", profile.getName());
|
||||
arguments.put("sessionId", authenticationToken);
|
||||
arguments.put("serverId", serverId);
|
||||
URL url = HttpAuthenticationService.concatenateURL(JOIN_URL, HttpAuthenticationService.buildQuery(arguments));
|
||||
|
||||
try {
|
||||
String response = this.getAuthenticationService().performGetRequest(url);
|
||||
if (!response.equals("OK")) {
|
||||
throw new AuthenticationException(response);
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
throw new AuthenticationUnavailableException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public GameProfile hasJoinedServer(GameProfile user, String serverId) throws AuthenticationUnavailableException {
|
||||
Map <String, Object>arguments = new HashMap <>();
|
||||
arguments.put("user", user.getName());
|
||||
arguments.put("serverId", serverId);
|
||||
URL url = HttpAuthenticationService.concatenateURL(CHECK_URL, HttpAuthenticationService.buildQuery(arguments));
|
||||
|
||||
try {
|
||||
String response = this.getAuthenticationService().performGetRequest(url);
|
||||
return response.equals("YES") ? user : null;
|
||||
} catch (IOException exception) {
|
||||
throw new AuthenticationUnavailableException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getTextures(GameProfile profile, boolean requireSecure) {
|
||||
return new HashMap <>();
|
||||
}
|
||||
|
||||
public GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure) {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public LegacyAuthenticationService getAuthenticationService() {
|
||||
return (LegacyAuthenticationService) super.getAuthenticationService();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
package com.mojang.authlib.legacy;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.HttpAuthenticationService;
|
||||
import com.mojang.authlib.HttpUserAuthentication;
|
||||
import com.mojang.authlib.UserType;
|
||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||
import com.mojang.authlib.exceptions.InvalidCredentialsException;
|
||||
import com.mojang.util.UUIDTypeAdapter;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class LegacyUserAuthentication extends HttpUserAuthentication {
|
||||
private static final URL AUTHENTICATION_URL = HttpAuthenticationService.constantURL("https://login.minecraft.net");
|
||||
private static final int AUTHENTICATION_VERSION = 14;
|
||||
private static final int RESPONSE_PART_PROFILE_NAME = 2;
|
||||
private static final int RESPONSE_PART_SESSION_TOKEN = 3;
|
||||
private static final int RESPONSE_PART_PROFILE_ID = 4;
|
||||
private String sessionToken;
|
||||
|
||||
protected LegacyUserAuthentication(LegacyAuthenticationService authenticationService) {
|
||||
super(authenticationService);
|
||||
}
|
||||
|
||||
public void logIn() throws AuthenticationException {
|
||||
if (StringUtils.isBlank(this.getUsername())) {
|
||||
throw new InvalidCredentialsException("Invalid username");
|
||||
} else if (StringUtils.isBlank(this.getPassword())) {
|
||||
throw new InvalidCredentialsException("Invalid password");
|
||||
} else {
|
||||
Map <String, Object>args = new HashMap <>();
|
||||
args.put("user", this.getUsername());
|
||||
args.put("password", this.getPassword());
|
||||
args.put("version", AUTHENTICATION_VERSION);
|
||||
|
||||
String response;
|
||||
try {
|
||||
response = this.getAuthenticationService().performPostRequest(AUTHENTICATION_URL, HttpAuthenticationService.buildQuery(args), "application/x-www-form-urlencoded").trim();
|
||||
} catch (IOException exception) {
|
||||
throw new AuthenticationException("Authentication server is not responding", exception);
|
||||
}
|
||||
|
||||
String[] split = response.split(":");
|
||||
if (split.length == 5) {
|
||||
String profileId = split[RESPONSE_PART_PROFILE_ID];
|
||||
String profileName = split[RESPONSE_PART_PROFILE_NAME];
|
||||
String sessionToken = split[RESPONSE_PART_SESSION_TOKEN];
|
||||
if (!StringUtils.isBlank(profileId) && !StringUtils.isBlank(profileName) && !StringUtils.isBlank(sessionToken)) {
|
||||
this.setSelectedProfile(new GameProfile(UUIDTypeAdapter.fromString(profileId), profileName));
|
||||
this.sessionToken = sessionToken;
|
||||
this.setUserType(UserType.LEGACY);
|
||||
} else {
|
||||
throw new AuthenticationException("Unknown response from authentication server: " + response);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidCredentialsException(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void logOut() {
|
||||
super.logOut();
|
||||
this.sessionToken = null;
|
||||
}
|
||||
|
||||
public boolean canPlayOnline() {
|
||||
return this.isLoggedIn() && this.getSelectedProfile() != null && this.getAuthenticatedToken() != null;
|
||||
}
|
||||
|
||||
public GameProfile[] getAvailableProfiles() {
|
||||
return this.getSelectedProfile() != null ? new GameProfile[] {
|
||||
this.getSelectedProfile()
|
||||
} : new GameProfile[0];
|
||||
}
|
||||
|
||||
public void selectGameProfile(GameProfile profile) throws AuthenticationException {
|
||||
throw new UnsupportedOperationException("Game profiles cannot be changed in the legacy authentication service");
|
||||
}
|
||||
|
||||
public String getAuthenticatedToken() {
|
||||
return this.sessionToken;
|
||||
}
|
||||
|
||||
public String getUserID() {
|
||||
return this.getUsername();
|
||||
}
|
||||
|
||||
public LegacyAuthenticationService getAuthenticationService() {
|
||||
return (LegacyAuthenticationService) super.getAuthenticationService();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package com.mojang.authlib.minecraft;
|
||||
|
||||
import com.mojang.authlib.AuthenticationService;
|
||||
|
||||
public abstract class BaseMinecraftSessionService implements MinecraftSessionService {
|
||||
private final AuthenticationService authenticationService;
|
||||
|
||||
protected BaseMinecraftSessionService(AuthenticationService authenticationService) {
|
||||
this.authenticationService = authenticationService;
|
||||
}
|
||||
|
||||
public AuthenticationService getAuthenticationService() {
|
||||
return this.authenticationService;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.mojang.authlib.minecraft;
|
||||
|
||||
import com.mojang.authlib.HttpAuthenticationService;
|
||||
|
||||
public abstract class HttpMinecraftSessionService extends BaseMinecraftSessionService {
|
||||
protected HttpMinecraftSessionService(HttpAuthenticationService authenticationService) {
|
||||
super(authenticationService);
|
||||
}
|
||||
|
||||
public HttpAuthenticationService getAuthenticationService() {
|
||||
return (HttpAuthenticationService) super.getAuthenticationService();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package com.mojang.authlib.minecraft;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
public class InsecureTextureException extends RuntimeException {
|
||||
private static final long serialVersionUID = 8583839911134111760L;
|
||||
|
||||
public InsecureTextureException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public static class MissingTextureException extends InsecureTextureException {
|
||||
private static final long serialVersionUID = 923263531191208887L;
|
||||
|
||||
public MissingTextureException() {
|
||||
super("No texture information found");
|
||||
}
|
||||
}
|
||||
|
||||
public static class OutdatedTextureException extends InsecureTextureException {
|
||||
private static final long serialVersionUID = -599847585679123867L;
|
||||
private final Date validFrom;
|
||||
private final Calendar limit;
|
||||
|
||||
public OutdatedTextureException(Date validFrom, Calendar limit) {
|
||||
super("Decrypted textures payload is too old (" + validFrom + ", but we need it to be at least " + limit + ")");
|
||||
this.validFrom = validFrom;
|
||||
this.limit = limit;
|
||||
}
|
||||
}
|
||||
|
||||
public static class WrongTextureOwnerException extends InsecureTextureException {
|
||||
private static final long serialVersionUID = -5004489121188040013L;
|
||||
private final GameProfile expected;
|
||||
private final UUID resultId;
|
||||
private final String resultName;
|
||||
|
||||
public WrongTextureOwnerException(GameProfile expected, UUID resultId, String resultName) {
|
||||
super("Decrypted textures payload was for another user (expected " + expected.getId() + "/" + expected.getName() + " but was for " + resultId + "/" + resultName + ")");
|
||||
this.expected = expected;
|
||||
this.resultId = resultId;
|
||||
this.resultName = resultName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.mojang.authlib.minecraft;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
public class MinecraftProfileTexture {
|
||||
private final String url;
|
||||
private final Map <String, String>metadata;
|
||||
|
||||
public MinecraftProfileTexture(String url, Map <String, String>metadata) {
|
||||
this.url = url;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMetadata(String key) {
|
||||
return this.metadata == null ? null : (String) this.metadata.get(key);
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return FilenameUtils.getBaseName(this.url);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return (new ToStringBuilder(this)).append("url", this.url).append("hash", this.getHash()).toString();
|
||||
}
|
||||
|
||||
public static enum Type {
|
||||
SKIN,
|
||||
CAPE;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.mojang.authlib.minecraft;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||
import com.mojang.authlib.exceptions.AuthenticationUnavailableException;
|
||||
import java.util.Map;
|
||||
|
||||
public interface MinecraftSessionService {
|
||||
void joinServer(GameProfile profile, String authenticationToken, String serverId) throws AuthenticationException;
|
||||
|
||||
GameProfile hasJoinedServer(GameProfile profile, String serverId) throws AuthenticationUnavailableException;
|
||||
|
||||
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getTextures(GameProfile profile, boolean requireSecure);
|
||||
|
||||
GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure);
|
||||
}
|
||||
57
src/main/java/com/mojang/authlib/properties/Property.java
Normal file
57
src/main/java/com/mojang/authlib/properties/Property.java
Normal file
@ -0,0 +1,57 @@
|
||||
package com.mojang.authlib.properties;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
public class Property {
|
||||
private final String name;
|
||||
private final String value;
|
||||
private final String signature;
|
||||
|
||||
public Property(String value, String name) {
|
||||
this(value, name, (String) null);
|
||||
}
|
||||
|
||||
public Property(String name, String value, String signature) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
public boolean hasSignature() {
|
||||
return this.signature != null;
|
||||
}
|
||||
|
||||
public boolean isSignatureValid(PublicKey publicKey) {
|
||||
try {
|
||||
Signature signature = Signature.getInstance("SHA1withRSA");
|
||||
signature.initVerify(publicKey);
|
||||
signature.update(this.value.getBytes());
|
||||
return signature.verify(Base64.decodeBase64(this.signature));
|
||||
} catch (NoSuchAlgorithmException exception) {
|
||||
exception.printStackTrace();
|
||||
} catch (InvalidKeyException exception) {
|
||||
exception.printStackTrace();
|
||||
} catch (SignatureException exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
75
src/main/java/com/mojang/authlib/properties/PropertyMap.java
Normal file
75
src/main/java/com/mojang/authlib/properties/PropertyMap.java
Normal file
@ -0,0 +1,75 @@
|
||||
package com.mojang.authlib.properties;
|
||||
|
||||
import com.google.common.collect.ForwardingMultimap;
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
|
||||
public class PropertyMap extends ForwardingMultimap <String, Property>{
|
||||
private final Multimap <String,
|
||||
Property>properties = LinkedHashMultimap.create();
|
||||
|
||||
protected Multimap <String,
|
||||
Property>delegate() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonSerializer <PropertyMap>,
|
||||
JsonDeserializer <PropertyMap>{
|
||||
public PropertyMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
PropertyMap result = new PropertyMap();
|
||||
if (json instanceof JsonObject) {
|
||||
JsonObject object = (JsonObject) json;
|
||||
|
||||
for (Map.Entry <String, JsonElement> entry : object.entrySet()) {
|
||||
if (entry.getValue() instanceof JsonArray) {
|
||||
for (JsonElement element : (JsonArray) entry.getValue()) {
|
||||
result.put(entry.getKey(), new Property((String) entry.getKey(), element.getAsString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (json instanceof JsonArray) {
|
||||
for (JsonElement element : (JsonArray) json) {
|
||||
if (element instanceof JsonObject) {
|
||||
JsonObject object = (JsonObject) element;
|
||||
String name = object.getAsJsonPrimitive("name").getAsString();
|
||||
String value = object.getAsJsonPrimitive("value").getAsString();
|
||||
if (object.has("signature")) {
|
||||
result.put(name, new Property(name, value, object.getAsJsonPrimitive("signature").getAsString()));
|
||||
} else {
|
||||
result.put(name, new Property(name, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public JsonElement serialize(PropertyMap src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonArray result = new JsonArray();
|
||||
|
||||
for (Property property : src.values()) {
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty("name", property.getName());
|
||||
object.addProperty("value", property.getValue());
|
||||
if (property.hasSignature()) {
|
||||
object.addProperty("signature", property.getSignature());
|
||||
}
|
||||
|
||||
result.add(object);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.mojang.authlib.yggdrasil;
|
||||
|
||||
public class ProfileIncompleteException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -3427258556051913114L;
|
||||
|
||||
public ProfileIncompleteException() {}
|
||||
|
||||
public ProfileIncompleteException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ProfileIncompleteException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ProfileIncompleteException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.mojang.authlib.yggdrasil;
|
||||
|
||||
public class ProfileNotFoundException extends RuntimeException {
|
||||
private static final long serialVersionUID = -4170318180689190630L;
|
||||
|
||||
public ProfileNotFoundException() {}
|
||||
|
||||
public ProfileNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ProfileNotFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ProfileNotFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
package com.mojang.authlib.yggdrasil;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.mojang.authlib.Agent;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.GameProfileRepository;
|
||||
import com.mojang.authlib.HttpAuthenticationService;
|
||||
import com.mojang.authlib.UserAuthentication;
|
||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||
import com.mojang.authlib.exceptions.AuthenticationUnavailableException;
|
||||
import com.mojang.authlib.exceptions.InvalidCredentialsException;
|
||||
import com.mojang.authlib.exceptions.UserMigratedException;
|
||||
import com.mojang.authlib.minecraft.MinecraftSessionService;
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
import com.mojang.authlib.yggdrasil.response.ProfileSearchResultsResponse;
|
||||
import com.mojang.authlib.yggdrasil.response.Response;
|
||||
import com.mojang.util.UUIDTypeAdapter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class YggdrasilAuthenticationService extends HttpAuthenticationService {
|
||||
private final String clientToken;
|
||||
private final Gson gson;
|
||||
|
||||
public YggdrasilAuthenticationService(Proxy proxy, String clientToken) {
|
||||
super(proxy);
|
||||
this.clientToken = clientToken;
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
builder.registerTypeAdapter(GameProfile.class, new YggdrasilAuthenticationService.GameProfileSerializer());
|
||||
builder.registerTypeAdapter(PropertyMap.class, new PropertyMap.Serializer());
|
||||
builder.registerTypeAdapter(UUID.class, new UUIDTypeAdapter());
|
||||
builder.registerTypeAdapter(ProfileSearchResultsResponse.class, new ProfileSearchResultsResponse.Serializer());
|
||||
this.gson = builder.create();
|
||||
}
|
||||
|
||||
public UserAuthentication createUserAuthentication(Agent agent) {
|
||||
return new YggdrasilUserAuthentication(this, agent);
|
||||
}
|
||||
|
||||
public MinecraftSessionService createMinecraftSessionService() {
|
||||
return new YggdrasilMinecraftSessionService(this);
|
||||
}
|
||||
|
||||
public GameProfileRepository createProfileRepository() {
|
||||
return new YggdrasilGameProfileRepository(this);
|
||||
}
|
||||
|
||||
protected <T extends Response>T makeRequest(URL url, Object input, Class <T>classOfT) throws AuthenticationException {
|
||||
try {
|
||||
String jsonResult = input == null ? this.performGetRequest(url) : this.performPostRequest(url, this.gson.toJson(input), "application/json");
|
||||
T result = (T)(this.gson.fromJson(jsonResult, classOfT));
|
||||
if (result == null) {
|
||||
return (T) null;
|
||||
} else if (StringUtils.isNotBlank(result.getError())) {
|
||||
if ("UserMigratedException".equals(result.getCause())) {
|
||||
throw new UserMigratedException(result.getErrorMessage());
|
||||
} else if (result.getError().equals("ForbiddenOperationException")) {
|
||||
throw new InvalidCredentialsException(result.getErrorMessage());
|
||||
} else {
|
||||
throw new AuthenticationException(result.getErrorMessage());
|
||||
}
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
throw new AuthenticationUnavailableException("Cannot contact authentication server", exception);
|
||||
} catch (IllegalStateException exception) {
|
||||
throw new AuthenticationUnavailableException("Cannot contact authentication server", exception);
|
||||
} catch (JsonParseException exception) {
|
||||
throw new AuthenticationUnavailableException("Cannot contact authentication server", exception);
|
||||
}
|
||||
}
|
||||
|
||||
public String getClientToken() {
|
||||
return this.clientToken;
|
||||
}
|
||||
|
||||
private static class GameProfileSerializer implements JsonSerializer <GameProfile>, JsonDeserializer <GameProfile>{
|
||||
private GameProfileSerializer() {}
|
||||
|
||||
public GameProfile deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject object = (JsonObject) json;
|
||||
UUID id = object.has("id") ? (UUID) context.deserialize(object.get("id"), UUID.class) : null;
|
||||
String name = object.has("name") ? object.getAsJsonPrimitive("name").getAsString() : null;
|
||||
return new GameProfile(id, name);
|
||||
}
|
||||
|
||||
public JsonElement serialize(GameProfile src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject result = new JsonObject();
|
||||
if (src.getId() != null) {
|
||||
result.add("id", context.serialize(src.getId()));
|
||||
}
|
||||
|
||||
if (src.getName() != null) {
|
||||
result.addProperty("name", src.getName());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
package com.mojang.authlib.yggdrasil;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.mojang.authlib.Agent;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.GameProfileRepository;
|
||||
import com.mojang.authlib.HttpAuthenticationService;
|
||||
import com.mojang.authlib.ProfileLookupCallback;
|
||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||
import com.mojang.authlib.yggdrasil.response.ProfileSearchResultsResponse;
|
||||
|
||||
import fr.azures04.modun.EndpointsManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class YggdrasilGameProfileRepository implements GameProfileRepository {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final String BASE_URL = EndpointsManager.getAPI();
|
||||
private static final String SEARCH_PAGE_URL = BASE_URL + "profiles";
|
||||
private static final int ENTRIES_PER_PAGE = 2;
|
||||
private static final int MAX_FAIL_COUNT = 3;
|
||||
private static final int DELAY_BETWEEN_PAGES = 100;
|
||||
private static final int DELAY_BETWEEN_FAILURES = 750;
|
||||
private final YggdrasilAuthenticationService authenticationService;
|
||||
|
||||
public YggdrasilGameProfileRepository(YggdrasilAuthenticationService authenticationService) {
|
||||
this.authenticationService = authenticationService;
|
||||
}
|
||||
|
||||
public void findProfilesByNames(String[] names, Agent agent, ProfileLookupCallback callback) {
|
||||
Set <String>criteria = Sets.newHashSet();
|
||||
|
||||
for (String name : names) {
|
||||
if (!Strings.isNullOrEmpty(name)) {
|
||||
criteria.add(name.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
int page = 0;
|
||||
|
||||
for (List <String> request : Iterables.partition(criteria, ENTRIES_PER_PAGE)) {
|
||||
int failCount = 0;
|
||||
|
||||
while (true) {
|
||||
boolean failed = false;
|
||||
|
||||
try {
|
||||
ProfileSearchResultsResponse response = this.authenticationService.makeRequest(HttpAuthenticationService.constantURL(SEARCH_PAGE_URL + "/" + agent.getName().toLowerCase()), request, ProfileSearchResultsResponse.class);
|
||||
failCount = 0;
|
||||
LOGGER.debug("Page {} returned {} results, parsing", new Object[] {
|
||||
page,
|
||||
response.getProfiles().length
|
||||
});
|
||||
Set <String>missing = Sets.newHashSet(request);
|
||||
|
||||
for (GameProfile profile : response.getProfiles()) {
|
||||
LOGGER.debug("Successfully looked up profile {}", new Object[] {
|
||||
profile
|
||||
});
|
||||
missing.remove(profile.getName().toLowerCase());
|
||||
callback.onProfileLookupSucceeded(profile);
|
||||
}
|
||||
|
||||
for (String name : missing) {
|
||||
LOGGER.debug("Couldn't find profile {}", new Object[] {
|
||||
name
|
||||
});
|
||||
callback.onProfileLookupFailed(new GameProfile((UUID) null, name), new ProfileNotFoundException("Server did not find the requested profile"));
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(DELAY_BETWEEN_PAGES);
|
||||
} catch (InterruptedException exception) {}
|
||||
} catch (AuthenticationException exception) {
|
||||
++failCount;
|
||||
if (failCount == MAX_FAIL_COUNT) {
|
||||
for (String name : request) {
|
||||
LOGGER.debug("Couldn't find profile {} because of a server error", new Object[] {
|
||||
name
|
||||
});
|
||||
callback.onProfileLookupFailed(new GameProfile((UUID) null, name), exception);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(DELAY_BETWEEN_FAILURES);
|
||||
} catch (InterruptedException interruptedException) {}
|
||||
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!failed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,201 @@
|
||||
package com.mojang.authlib.yggdrasil;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
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.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
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.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
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.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 fr.azures04.modun.EndpointsManager;
|
||||
import fr.azures04.modun.YggdrasilSessionPublicKeyManager;
|
||||
|
||||
public class YggdrasilMinecraftSessionService extends HttpMinecraftSessionService {
|
||||
private static final String[] WHITELISTED_DOMAINS = new String[] {
|
||||
".minecraft.net",
|
||||
".mojang.com",
|
||||
EndpointsManager.getWhitelistedDomain()
|
||||
};
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final String BASE_URL = EndpointsManager.getSessionServer() + "session/minecraft/";
|
||||
private static final URL JOIN_URL = HttpAuthenticationService.constantURL(BASE_URL + "join");
|
||||
private static final URL CHECK_URL = HttpAuthenticationService.constantURL(BASE_URL + "hasJoined");
|
||||
|
||||
private final PublicKey publicKey;
|
||||
private final Gson gson = (new GsonBuilder()).registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create();
|
||||
private final LoadingCache<GameProfile, GameProfile> insecureProfiles = CacheBuilder.newBuilder().expireAfterWrite(6L, TimeUnit.HOURS).build(new CacheLoader <GameProfile, GameProfile>() {
|
||||
public GameProfile load(GameProfile key) throws Exception {
|
||||
return YggdrasilMinecraftSessionService.this.fillGameProfile(key, false);
|
||||
}
|
||||
});
|
||||
|
||||
protected YggdrasilMinecraftSessionService(YggdrasilAuthenticationService authenticationService) {
|
||||
super(authenticationService);
|
||||
|
||||
try {
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(YggdrasilSessionPublicKeyManager.getYggdrasilSessionPublicKey());
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
this.publicKey = keyFactory.generatePublic(spec);
|
||||
} catch (Exception exception) {
|
||||
throw new Error("Missing/invalid yggdrasil public key!");
|
||||
}
|
||||
}
|
||||
|
||||
public void joinServer(GameProfile profile, String authenticationToken, String serverId) throws AuthenticationException {
|
||||
JoinMinecraftServerRequest request = new JoinMinecraftServerRequest();
|
||||
request.accessToken = authenticationToken;
|
||||
request.selectedProfile = profile.getId();
|
||||
request.serverId = serverId;
|
||||
this.getAuthenticationService().makeRequest(JOIN_URL, request, Response.class);
|
||||
}
|
||||
|
||||
public GameProfile hasJoinedServer(GameProfile user, String serverId) throws AuthenticationUnavailableException {
|
||||
Map <String, Object>arguments = new HashMap <>();
|
||||
arguments.put("username", user.getName());
|
||||
arguments.put("serverId", serverId);
|
||||
URL url = HttpAuthenticationService.concatenateURL(CHECK_URL, HttpAuthenticationService.buildQuery(arguments));
|
||||
|
||||
try {
|
||||
HasJoinedMinecraftServerResponse response = this.getAuthenticationService().makeRequest(url, (Object) null, HasJoinedMinecraftServerResponse.class);
|
||||
if (response != null && response.getId() != null) {
|
||||
GameProfile result = new GameProfile(response.getId(), user.getName());
|
||||
if (response.getProperties() != null) {
|
||||
result.getProperties().putAll(response.getProperties());
|
||||
}
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (AuthenticationUnavailableException exception) {
|
||||
throw exception;
|
||||
} catch (AuthenticationException exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<MinecraftProfileTexture.Type, MinecraftProfileTexture>getTextures(GameProfile profile, boolean requireSecure) {
|
||||
Property textureProperty = (Property) Iterables.getFirst(profile.getProperties().get("textures"), (Object) null);
|
||||
if (textureProperty == null) {
|
||||
return new HashMap <>();
|
||||
} else {
|
||||
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)");
|
||||
}
|
||||
}
|
||||
|
||||
MinecraftTexturesPayload result;
|
||||
try {
|
||||
String json = new String(Base64.decodeBase64(textureProperty.getValue()), Charsets.UTF_8);
|
||||
result = (MinecraftTexturesPayload) this.gson.fromJson(json, MinecraftTexturesPayload.class);
|
||||
} catch (JsonParseException exception) {
|
||||
LOGGER.error("Could not decode textures payload", exception);
|
||||
return new HashMap <>();
|
||||
}
|
||||
|
||||
if (result.getTextures() == null) {
|
||||
return new HashMap <>();
|
||||
} else {
|
||||
for (Map.Entry <MinecraftProfileTexture.Type, MinecraftProfileTexture> entry : result.getTextures().entrySet()) {
|
||||
if (!isWhitelistedDomain(((MinecraftProfileTexture) entry.getValue()).getUrl())) {
|
||||
LOGGER.error("Textures payload has been tampered with (non-whitelisted domain)");
|
||||
return new HashMap <>();
|
||||
}
|
||||
}
|
||||
|
||||
return result.getTextures();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure) {
|
||||
if (profile.getId() == null) {
|
||||
return profile;
|
||||
} else {
|
||||
return !requireSecure ? (GameProfile) this.insecureProfiles.getUnchecked(profile) : this.fillGameProfile(profile, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected GameProfile fillGameProfile(GameProfile profile, boolean requireSecure) {
|
||||
try {
|
||||
URL url = HttpAuthenticationService.constantURL(BASE_URL + "profile/" + UUIDTypeAdapter.fromUUID(profile.getId()));
|
||||
url = HttpAuthenticationService.concatenateURL(url, "unsigned=" + !requireSecure);
|
||||
MinecraftProfilePropertiesResponse response = this.getAuthenticationService().makeRequest(url, (Object) null, MinecraftProfilePropertiesResponse.class);
|
||||
if (response == null) {
|
||||
LOGGER.debug("Couldn't fetch profile properties for " + profile + " as the profile does not exist");
|
||||
return profile;
|
||||
} else {
|
||||
GameProfile result = new GameProfile(response.getId(), response.getName());
|
||||
result.getProperties().putAll(response.getProperties());
|
||||
profile.getProperties().putAll(response.getProperties());
|
||||
LOGGER.debug("Successfully fetched profile properties for " + profile);
|
||||
return result;
|
||||
}
|
||||
} catch (AuthenticationException exception) {
|
||||
LOGGER.warn("Couldn't look up profile properties for " + profile, exception);
|
||||
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 exception) {
|
||||
throw new IllegalArgumentException("Invalid URL '" + url + "'");
|
||||
}
|
||||
|
||||
String domain = uri.getHost();
|
||||
|
||||
for (int i = 0; i <WHITELISTED_DOMAINS.length; ++i) {
|
||||
if (domain.endsWith(WHITELISTED_DOMAINS[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,242 @@
|
||||
package com.mojang.authlib.yggdrasil;
|
||||
|
||||
import com.mojang.authlib.Agent;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.HttpAuthenticationService;
|
||||
import com.mojang.authlib.HttpUserAuthentication;
|
||||
import com.mojang.authlib.UserType;
|
||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||
import com.mojang.authlib.exceptions.InvalidCredentialsException;
|
||||
import com.mojang.authlib.yggdrasil.request.AuthenticationRequest;
|
||||
import com.mojang.authlib.yggdrasil.request.RefreshRequest;
|
||||
import com.mojang.authlib.yggdrasil.request.ValidateRequest;
|
||||
import com.mojang.authlib.yggdrasil.response.AuthenticationResponse;
|
||||
import com.mojang.authlib.yggdrasil.response.RefreshResponse;
|
||||
import com.mojang.authlib.yggdrasil.response.Response;
|
||||
import com.mojang.authlib.yggdrasil.response.User;
|
||||
|
||||
import fr.azures04.modun.EndpointsManager;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class YggdrasilUserAuthentication extends HttpUserAuthentication {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final String BASE_URL = EndpointsManager.getAuthServer();
|
||||
private static final URL ROUTE_AUTHENTICATE = HttpAuthenticationService.constantURL(BASE_URL + "authenticate");
|
||||
private static final URL ROUTE_REFRESH = HttpAuthenticationService.constantURL(BASE_URL + "refresh");
|
||||
private static final URL ROUTE_VALIDATE = HttpAuthenticationService.constantURL(BASE_URL + "validate");
|
||||
private static final URL ROUTE_INVALIDATE = HttpAuthenticationService.constantURL(BASE_URL + "invalidate");
|
||||
private static final URL ROUTE_SIGNOUT = HttpAuthenticationService.constantURL(BASE_URL + "signout");
|
||||
private static final String STORAGE_KEY_ACCESS_TOKEN = "accessToken";
|
||||
private final Agent agent;
|
||||
private GameProfile[] profiles;
|
||||
private String accessToken;
|
||||
private boolean isOnline;
|
||||
|
||||
public YggdrasilUserAuthentication(YggdrasilAuthenticationService authenticationService, Agent agent) {
|
||||
super(authenticationService);
|
||||
this.agent = agent;
|
||||
}
|
||||
|
||||
public boolean canLogIn() {
|
||||
return !this.canPlayOnline() && StringUtils.isNotBlank(this.getUsername()) && (StringUtils.isNotBlank(this.getPassword()) || StringUtils.isNotBlank(this.getAuthenticatedToken()));
|
||||
}
|
||||
|
||||
public void logIn() throws AuthenticationException {
|
||||
if (StringUtils.isBlank(this.getUsername())) {
|
||||
throw new InvalidCredentialsException("Invalid username");
|
||||
} else {
|
||||
if (StringUtils.isNotBlank(this.getAuthenticatedToken())) {
|
||||
this.logInWithToken();
|
||||
} else {
|
||||
if (!StringUtils.isNotBlank(this.getPassword())) {
|
||||
throw new InvalidCredentialsException("Invalid password");
|
||||
}
|
||||
|
||||
this.logInWithPassword();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected void logInWithPassword() throws AuthenticationException {
|
||||
if (StringUtils.isBlank(this.getUsername())) {
|
||||
throw new InvalidCredentialsException("Invalid username");
|
||||
} else if (StringUtils.isBlank(this.getPassword())) {
|
||||
throw new InvalidCredentialsException("Invalid password");
|
||||
} else {
|
||||
LOGGER.info("Logging in with username & password");
|
||||
AuthenticationRequest request = new AuthenticationRequest(this, this.getUsername(), this.getPassword());
|
||||
AuthenticationResponse response = this.getAuthenticationService().makeRequest(ROUTE_AUTHENTICATE, request, AuthenticationResponse.class);
|
||||
if (!response.getClientToken().equals(this.getAuthenticationService().getClientToken())) {
|
||||
throw new AuthenticationException("Server requested we change our client token. Don't know how to handle this!");
|
||||
} else {
|
||||
if (response.getSelectedProfile() != null) {
|
||||
this.setUserType(response.getSelectedProfile().isLegacy() ? UserType.LEGACY : UserType.MOJANG);
|
||||
} else if (ArrayUtils.isNotEmpty(response.getAvailableProfiles())) {
|
||||
this.setUserType(response.getAvailableProfiles()[0].isLegacy() ? UserType.LEGACY : UserType.MOJANG);
|
||||
}
|
||||
|
||||
User user = response.getUser();
|
||||
if (user != null && user.getId() != null) {
|
||||
this.setUserid(user.getId());
|
||||
} else {
|
||||
this.setUserid(this.getUsername());
|
||||
}
|
||||
|
||||
this.isOnline = true;
|
||||
this.accessToken = response.getAccessToken();
|
||||
this.profiles = response.getAvailableProfiles();
|
||||
this.setSelectedProfile(response.getSelectedProfile());
|
||||
this.getModifiableUserProperties().clear();
|
||||
this.updateUserProperties(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateUserProperties(User user) {
|
||||
if (user != null) {
|
||||
if (user.getProperties() != null) {
|
||||
this.getModifiableUserProperties().putAll(user.getProperties());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected void logInWithToken() throws AuthenticationException {
|
||||
if (StringUtils.isBlank(this.getUserID())) {
|
||||
if (!StringUtils.isBlank(this.getUsername())) {
|
||||
throw new InvalidCredentialsException("Invalid uuid & username");
|
||||
}
|
||||
|
||||
this.setUserid(this.getUsername());
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(this.getAuthenticatedToken())) {
|
||||
throw new InvalidCredentialsException("Invalid access token");
|
||||
} else {
|
||||
LOGGER.info("Logging in with access token");
|
||||
if (this.checkTokenValidity()) {
|
||||
LOGGER.debug("Skipping refresh call as we're safely logged in.");
|
||||
this.isOnline = true;
|
||||
} else {
|
||||
RefreshRequest request = new RefreshRequest(this);
|
||||
RefreshResponse response = this.getAuthenticationService().makeRequest(ROUTE_REFRESH, request, RefreshResponse.class);
|
||||
if (!response.getClientToken().equals(this.getAuthenticationService().getClientToken())) {
|
||||
throw new AuthenticationException("Server requested we change our client token. Don't know how to handle this!");
|
||||
} else {
|
||||
if (response.getSelectedProfile() != null) {
|
||||
this.setUserType(response.getSelectedProfile().isLegacy() ? UserType.LEGACY : UserType.MOJANG);
|
||||
} else if (ArrayUtils.isNotEmpty(response.getAvailableProfiles())) {
|
||||
this.setUserType(response.getAvailableProfiles()[0].isLegacy() ? UserType.LEGACY : UserType.MOJANG);
|
||||
}
|
||||
|
||||
if (response.getUser() != null && response.getUser().getId() != null) {
|
||||
this.setUserid(response.getUser().getId());
|
||||
} else {
|
||||
this.setUserid(this.getUsername());
|
||||
}
|
||||
|
||||
this.isOnline = true;
|
||||
this.accessToken = response.getAccessToken();
|
||||
this.profiles = response.getAvailableProfiles();
|
||||
this.setSelectedProfile(response.getSelectedProfile());
|
||||
this.getModifiableUserProperties().clear();
|
||||
this.updateUserProperties(response.getUser());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean checkTokenValidity() throws AuthenticationException {
|
||||
ValidateRequest request = new ValidateRequest(this);
|
||||
|
||||
try {
|
||||
this.getAuthenticationService().makeRequest(ROUTE_VALIDATE, request, Response.class);
|
||||
return true;
|
||||
} catch (AuthenticationException exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void logOut() {
|
||||
super.logOut();
|
||||
this.accessToken = null;
|
||||
this.profiles = null;
|
||||
this.isOnline = false;
|
||||
}
|
||||
|
||||
public GameProfile[] getAvailableProfiles() {
|
||||
return this.profiles;
|
||||
}
|
||||
|
||||
public boolean isLoggedIn() {
|
||||
return StringUtils.isNotBlank(this.accessToken);
|
||||
}
|
||||
|
||||
public boolean canPlayOnline() {
|
||||
return this.isLoggedIn() && this.getSelectedProfile() != null && this.isOnline;
|
||||
}
|
||||
|
||||
public void selectGameProfile(GameProfile profile) throws AuthenticationException {
|
||||
if (!this.isLoggedIn()) {
|
||||
throw new AuthenticationException("Cannot change game profile whilst not logged in");
|
||||
} else if (this.getSelectedProfile() != null) {
|
||||
throw new AuthenticationException("Cannot change game profile. You must log out and back in.");
|
||||
} else if (profile != null && ArrayUtils.contains(this.profiles, profile)) {
|
||||
RefreshRequest request = new RefreshRequest(this, profile);
|
||||
RefreshResponse response = this.getAuthenticationService().makeRequest(ROUTE_REFRESH, request, RefreshResponse.class);
|
||||
if (!response.getClientToken().equals(this.getAuthenticationService().getClientToken())) {
|
||||
throw new AuthenticationException("Server requested we change our client token. Don't know how to handle this!");
|
||||
} else {
|
||||
this.isOnline = true;
|
||||
this.accessToken = response.getAccessToken();
|
||||
this.setSelectedProfile(response.getSelectedProfile());
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid profile '" + profile + "'");
|
||||
}
|
||||
}
|
||||
|
||||
public void loadFromStorage(Map <String, Object>credentials) {
|
||||
super.loadFromStorage(credentials);
|
||||
this.accessToken = String.valueOf(credentials.get(STORAGE_KEY_ACCESS_TOKEN));
|
||||
}
|
||||
|
||||
public Map <String, Object>saveForStorage() {
|
||||
Map <String, Object>result = super.saveForStorage();
|
||||
if (StringUtils.isNotBlank(this.getAuthenticatedToken())) {
|
||||
result.put(STORAGE_KEY_ACCESS_TOKEN, this.getAuthenticatedToken());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
@Deprecated
|
||||
public String getSessionToken() {
|
||||
return this.isLoggedIn() && this.getSelectedProfile() != null && this.canPlayOnline() ? String.format("token:%s:%s", this.getAuthenticatedToken(), this.getSelectedProfile().getId()) : null;
|
||||
}
|
||||
|
||||
public String getAuthenticatedToken() {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
public Agent getAgent() {
|
||||
return this.agent;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "YggdrasilAuthenticationService{agent=" + this.agent + ", profiles=" + Arrays.toString(this.profiles) + ", selectedProfile=" + this.getSelectedProfile() + ", username='" + this.getUsername() + '\'' + ", isLoggedIn=" + this.isLoggedIn() + ", userType=" + this.getUserType() + ", canPlayOnline=" + this.canPlayOnline() + ", accessToken='" + this.accessToken + '\'' + ", clientToken='" + this.getAuthenticationService().getClientToken() + '\'' + '}';
|
||||
}
|
||||
|
||||
public YggdrasilAuthenticationService getAuthenticationService() {
|
||||
return (YggdrasilAuthenticationService) super.getAuthenticationService();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.mojang.authlib.yggdrasil.request;
|
||||
|
||||
import com.mojang.authlib.Agent;
|
||||
import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication;
|
||||
|
||||
public class AuthenticationRequest {
|
||||
private Agent agent;
|
||||
private String username;
|
||||
private String password;
|
||||
private String clientToken;
|
||||
private boolean requestUser = true;
|
||||
|
||||
public AuthenticationRequest(YggdrasilUserAuthentication authenticationService, String username, String password) {
|
||||
this.agent = authenticationService.getAgent();
|
||||
this.username = username;
|
||||
this.clientToken = authenticationService.getAuthenticationService().getClientToken();
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.mojang.authlib.yggdrasil.request;
|
||||
|
||||
import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication;
|
||||
|
||||
public class InvalidateRequest {
|
||||
private String accessToken;
|
||||
private String clientToken;
|
||||
|
||||
public InvalidateRequest(YggdrasilUserAuthentication authenticationService) {
|
||||
this.accessToken = authenticationService.getAuthenticatedToken();
|
||||
this.clientToken = authenticationService.getAuthenticationService().getClientToken();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package com.mojang.authlib.yggdrasil.request;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class JoinMinecraftServerRequest {
|
||||
public String accessToken;
|
||||
public UUID selectedProfile;
|
||||
public String serverId;
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.mojang.authlib.yggdrasil.request;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication;
|
||||
|
||||
public class RefreshRequest {
|
||||
private String clientToken;
|
||||
private String accessToken;
|
||||
private GameProfile selectedProfile;
|
||||
private boolean requestUser = true;
|
||||
|
||||
public RefreshRequest(YggdrasilUserAuthentication authenticationService) {
|
||||
this(authenticationService, (GameProfile) null);
|
||||
}
|
||||
|
||||
public RefreshRequest(YggdrasilUserAuthentication authenticationService, GameProfile profile) {
|
||||
this.clientToken = authenticationService.getAuthenticationService().getClientToken();
|
||||
this.accessToken = authenticationService.getAuthenticatedToken();
|
||||
this.selectedProfile = profile;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.mojang.authlib.yggdrasil.request;
|
||||
|
||||
import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication;
|
||||
|
||||
public class ValidateRequest {
|
||||
private String clientToken;
|
||||
private String accessToken;
|
||||
|
||||
public ValidateRequest(YggdrasilUserAuthentication authenticationService) {
|
||||
this.clientToken = authenticationService.getAuthenticationService().getClientToken();
|
||||
this.accessToken = authenticationService.getAuthenticatedToken();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.mojang.authlib.yggdrasil.response;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
|
||||
public class AuthenticationResponse extends Response {
|
||||
private String accessToken;
|
||||
private String clientToken;
|
||||
private GameProfile selectedProfile;
|
||||
private GameProfile[] availableProfiles;
|
||||
private User user;
|
||||
|
||||
public String getAccessToken() {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
public String getClientToken() {
|
||||
return this.clientToken;
|
||||
}
|
||||
|
||||
public GameProfile[] getAvailableProfiles() {
|
||||
return this.availableProfiles;
|
||||
}
|
||||
|
||||
public GameProfile getSelectedProfile() {
|
||||
return this.selectedProfile;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return this.user;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package com.mojang.authlib.yggdrasil.response;
|
||||
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
import java.util.UUID;
|
||||
|
||||
public class HasJoinedMinecraftServerResponse extends Response {
|
||||
private UUID id;
|
||||
private PropertyMap properties;
|
||||
|
||||
public UUID getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public PropertyMap getProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.mojang.authlib.yggdrasil.response;
|
||||
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MinecraftProfilePropertiesResponse extends Response {
|
||||
private UUID id;
|
||||
private String name;
|
||||
private PropertyMap properties;
|
||||
|
||||
public UUID getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public PropertyMap getProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.mojang.authlib.yggdrasil.response;
|
||||
|
||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MinecraftTexturesPayload {
|
||||
private long timestamp;
|
||||
private UUID profileId;
|
||||
private String profileName;
|
||||
private boolean isPublic;
|
||||
private Map <MinecraftProfileTexture.Type, MinecraftProfileTexture>textures;
|
||||
|
||||
public long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public UUID getProfileId() {
|
||||
return this.profileId;
|
||||
}
|
||||
|
||||
public String getProfileName() {
|
||||
return this.profileName;
|
||||
}
|
||||
|
||||
public boolean isPublic() {
|
||||
return this.isPublic;
|
||||
}
|
||||
|
||||
public Map <MinecraftProfileTexture.Type, MinecraftProfileTexture>getTextures() {
|
||||
return this.textures;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package com.mojang.authlib.yggdrasil.response;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public class ProfileSearchResultsResponse extends Response {
|
||||
private GameProfile[] profiles;
|
||||
|
||||
public GameProfile[] getProfiles() {
|
||||
return this.profiles;
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonDeserializer <ProfileSearchResultsResponse>{
|
||||
public ProfileSearchResultsResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
ProfileSearchResultsResponse result = new ProfileSearchResultsResponse();
|
||||
if (json instanceof JsonObject) {
|
||||
JsonObject object = (JsonObject) json;
|
||||
if (object.has("error")) {
|
||||
result.setError(object.getAsJsonPrimitive("error").getAsString());
|
||||
}
|
||||
|
||||
if (object.has("errorMessage")) {
|
||||
result.setError(object.getAsJsonPrimitive("errorMessage").getAsString());
|
||||
}
|
||||
|
||||
if (object.has("cause")) {
|
||||
result.setError(object.getAsJsonPrimitive("cause").getAsString());
|
||||
}
|
||||
} else {
|
||||
result.profiles = (GameProfile[]) context.deserialize(json, GameProfile[].class);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.mojang.authlib.yggdrasil.response;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
|
||||
public class RefreshResponse extends Response {
|
||||
private String accessToken;
|
||||
private String clientToken;
|
||||
private GameProfile selectedProfile;
|
||||
private GameProfile[] availableProfiles;
|
||||
private User user;
|
||||
|
||||
public String getAccessToken() {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
public String getClientToken() {
|
||||
return this.clientToken;
|
||||
}
|
||||
|
||||
public GameProfile[] getAvailableProfiles() {
|
||||
return this.availableProfiles;
|
||||
}
|
||||
|
||||
public GameProfile getSelectedProfile() {
|
||||
return this.selectedProfile;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return this.user;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.mojang.authlib.yggdrasil.response;
|
||||
|
||||
public class Response {
|
||||
private String error;
|
||||
private String errorMessage;
|
||||
private String cause;
|
||||
|
||||
public String getError() {
|
||||
return this.error;
|
||||
}
|
||||
|
||||
public String getCause() {
|
||||
return this.cause;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return this.errorMessage;
|
||||
}
|
||||
|
||||
protected void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
protected void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
protected void setCause(String cause) {
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.mojang.authlib.yggdrasil.response;
|
||||
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
|
||||
public class User {
|
||||
private String id;
|
||||
private PropertyMap properties;
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public PropertyMap getProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
}
|
||||
87
src/main/java/com/mojang/util/QueueLogAppender.java
Normal file
87
src/main/java/com/mojang/util/QueueLogAppender.java
Normal file
@ -0,0 +1,87 @@
|
||||
package com.mojang.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import org.apache.logging.log4j.core.Filter;
|
||||
import org.apache.logging.log4j.core.Layout;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.appender.AbstractAppender;
|
||||
import org.apache.logging.log4j.core.config.Configuration;
|
||||
import org.apache.logging.log4j.core.config.plugins.Plugin;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginElement;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
|
||||
import org.apache.logging.log4j.core.layout.PatternLayout;
|
||||
import org.apache.logging.log4j.core.pattern.RegexReplacement;
|
||||
|
||||
@Plugin(
|
||||
name = "Queue",
|
||||
category = "Core",
|
||||
elementType = "appender",
|
||||
printObject = true
|
||||
)
|
||||
public class QueueLogAppender extends AbstractAppender {
|
||||
private static final int MAX_CAPACITY = 250;
|
||||
private static final Map <String, BlockingQueue <String>> QUEUES = new HashMap <>();
|
||||
private static final ReadWriteLock QUEUE_LOCK = new ReentrantReadWriteLock();
|
||||
private final BlockingQueue <String>queue;
|
||||
|
||||
public QueueLogAppender(String name, Filter filter, Layout <? extends Serializable> layout, boolean ignoreExceptions, BlockingQueue <String>queue) {
|
||||
super(name, filter, layout, ignoreExceptions);
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
public void append(LogEvent event) {
|
||||
if (this.queue.size() >= MAX_CAPACITY) {
|
||||
this.queue.clear();
|
||||
}
|
||||
|
||||
this.queue.add(this.getLayout().toSerializable(event).toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@PluginFactory
|
||||
public static QueueLogAppender createAppender(@PluginAttribute("name") String name, @PluginAttribute("ignoreExceptions") String ignore, @PluginElement("Layout") Layout <? extends Serializable>layout, @PluginElement("Filters") Filter filter, @PluginAttribute("target") String target) {
|
||||
boolean ignoreExceptions = Boolean.parseBoolean(ignore);
|
||||
if (name == null) {
|
||||
LOGGER.error("No name provided for QueueLogAppender");
|
||||
return null;
|
||||
} else {
|
||||
if (target == null) {
|
||||
target = name;
|
||||
}
|
||||
|
||||
QUEUE_LOCK.writeLock().lock();
|
||||
BlockingQueue <String>queue = QUEUES.get(target);
|
||||
if (queue == null) {
|
||||
queue = new LinkedBlockingQueue <>();
|
||||
QUEUES.put(target, queue);
|
||||
}
|
||||
|
||||
QUEUE_LOCK.writeLock().unlock();
|
||||
if (layout == null) {
|
||||
layout = PatternLayout.createLayout((String) null, null, (Configuration) null, (RegexReplacement) null, null, ignoreExceptions, ignoreExceptions, (String) null, (String) null);
|
||||
}
|
||||
|
||||
return new QueueLogAppender(name, filter, layout, ignoreExceptions, queue);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getNextLogEvent(String queueName) {
|
||||
QUEUE_LOCK.readLock().lock();
|
||||
BlockingQueue <String>queue = QUEUES.get(queueName);
|
||||
QUEUE_LOCK.readLock().unlock();
|
||||
if (queue != null) {
|
||||
try {
|
||||
return queue.take();
|
||||
} catch (InterruptedException exception) {}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
25
src/main/java/com/mojang/util/UUIDTypeAdapter.java
Normal file
25
src/main/java/com/mojang/util/UUIDTypeAdapter.java
Normal file
@ -0,0 +1,25 @@
|
||||
package com.mojang.util;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UUIDTypeAdapter extends TypeAdapter <UUID>{
|
||||
public void write(JsonWriter out, UUID value) throws IOException {
|
||||
out.value(fromUUID(value));
|
||||
}
|
||||
|
||||
public UUID read(JsonReader in ) throws IOException {
|
||||
return fromString( in .nextString());
|
||||
}
|
||||
|
||||
public static String fromUUID(UUID value) {
|
||||
return value.toString().replace("-", "");
|
||||
}
|
||||
|
||||
public static UUID fromString(String input) {
|
||||
return UUID.fromString(input.replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"));
|
||||
}
|
||||
}
|
||||
66
src/main/java/fr/azures04/modun/EndpointsManager.java
Normal file
66
src/main/java/fr/azures04/modun/EndpointsManager.java
Normal file
@ -0,0 +1,66 @@
|
||||
package fr.azures04.modun;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class EndpointsManager {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
public static String getAuthServer() {
|
||||
String property = System.getProperty("minecraft.endpoints.authserver");
|
||||
if (property != null && !property.isEmpty()) {
|
||||
return (property + "/").replaceAll("//", "/");
|
||||
} else {
|
||||
property = "https://authserver.mojang.com/";
|
||||
}
|
||||
LOGGER.log(Level.INFO, "[Modun] Auth server endpoint : " + property);
|
||||
return property;
|
||||
}
|
||||
|
||||
public static String getSessionServer() {
|
||||
String property = System.getProperty("minecraft.endpoints.sessionserver");
|
||||
if (property != null && !property.isEmpty()) {
|
||||
return (property + "/").replaceAll("(?<!:)/{2,}", "/");
|
||||
} else {
|
||||
property = "https://sessionserver.mojang.com/";
|
||||
}
|
||||
LOGGER.log(Level.INFO, "[Modun] Session server endpoint : " + property);
|
||||
return property;
|
||||
}
|
||||
|
||||
public static String getAPI() {
|
||||
String property = System.getProperty("minecraft.endpoints.api");
|
||||
if (property != null && !property.isEmpty()) {
|
||||
property = (property + "/").replaceAll("(?<!:)/{2,}", "/");
|
||||
} else {
|
||||
property = "https://api.mojang.com/";
|
||||
}
|
||||
LOGGER.log(Level.INFO, "[Modun] API endpoint : " + property);
|
||||
return property;
|
||||
}
|
||||
|
||||
public static String getMinecraftServicesAPI() {
|
||||
String property = System.getProperty("minecraft.endpoints.minecraftserviceapi");
|
||||
if (property != null && !property.isEmpty()) {
|
||||
property = (property + "/").replaceAll("(?<!:)/{2,}", "/");
|
||||
} else {
|
||||
property = "https://api.minecraftservices.com/";
|
||||
}
|
||||
LOGGER.log(Level.INFO, "[Modun] Minecraft Service API endpoint : " + property);
|
||||
return property;
|
||||
}
|
||||
|
||||
public static String getWhitelistedDomain() {
|
||||
String property = System.getProperty("minecraft.endpoints.whitelisted");
|
||||
if (property != null && !property.isEmpty()) {
|
||||
return (property + "/").replaceAll("(?<!:)/{2,}", "/");
|
||||
} else {
|
||||
property = ".azures.fr";
|
||||
}
|
||||
LOGGER.log(Level.INFO, "[Modun] Whitelisted domain : " + property);
|
||||
return property;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,110 @@
|
||||
package fr.azures04.modun;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Base64;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import com.google.gson.Gson;
|
||||
import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService;
|
||||
import fr.azures04.modun.dto.PublicKeyEntry;
|
||||
import fr.azures04.modun.dto.PublicKeysResponse;
|
||||
|
||||
public class YggdrasilSessionPublicKeyManager {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static final Pattern PEM_PATTERN = Pattern.compile(
|
||||
"-----BEGIN\\s+PUBLIC\\s+KEY-----([^-]+)-----END\\s+PUBLIC\\s+KEY-----",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
public static byte[] convertPemToDer(String pemKey) throws Exception {
|
||||
if (pemKey == null || pemKey.isEmpty()) {
|
||||
throw new IllegalArgumentException("[Modun] The PEM key cannot be null or empty.");
|
||||
}
|
||||
|
||||
Matcher matcher = PEM_PATTERN.matcher(pemKey);
|
||||
|
||||
if (!matcher.find()) {
|
||||
LOGGER.log(Level.WARN, "[Modun] Warning: PEM headers not found. Attempting raw decoding.");
|
||||
String base64Content = pemKey.replaceAll("\\s", "");
|
||||
return Base64.getDecoder().decode(base64Content);
|
||||
}
|
||||
|
||||
String base64Content = matcher.group(1);
|
||||
base64Content = base64Content.replaceAll("\\s", "");
|
||||
return Base64.getDecoder().decode(base64Content);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static PublicKeysResponse getYggdrasilSessionPublicKeyFromAuthProvider() {
|
||||
HttpURLConnection connection = null;
|
||||
|
||||
String apiUrl = EndpointsManager.getMinecraftServicesAPI() + "publickeys";
|
||||
|
||||
try {
|
||||
URL url = new URL(apiUrl);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||
LOGGER.log(Level.ERROR, "[Modun] GET request failed. Code: " + responseCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
Gson gson = new Gson();
|
||||
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
|
||||
PublicKeysResponse result = gson.fromJson(br, PublicKeysResponse.class);
|
||||
return result;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.ERROR, "[Modun] Error retrieving or analysing public keys: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getYggdrasilSessionPublicKey() {
|
||||
try {
|
||||
PublicKeysResponse publicKeys = getYggdrasilSessionPublicKeyFromAuthProvider();
|
||||
if (publicKeys != null && publicKeys.getProfilePropertyKeys() != null && !publicKeys.getProfilePropertyKeys().isEmpty()) {
|
||||
PublicKeyEntry entry = publicKeys.getProfilePropertyKeys().get(0);
|
||||
String pemKey = entry.getPublicKey();
|
||||
if (pemKey != null && !pemKey.isEmpty()) {
|
||||
byte[] derKey = convertPemToDer(pemKey);
|
||||
return derKey;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.ERROR, "[Modun] Error loading key from custom AuthProvider: " + e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
LOGGER.log(Level.INFO, "[Modun] Attempting to load the default session key.");
|
||||
return IOUtils.toByteArray(YggdrasilMinecraftSessionService.class.getResourceAsStream("/yggdrasil_session_pubkey.der"));
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.ERROR, "[Modun] Fatal error: Failed to load default session key.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
14
src/main/java/fr/azures04/modun/dto/PublicKeyEntry.java
Normal file
14
src/main/java/fr/azures04/modun/dto/PublicKeyEntry.java
Normal file
@ -0,0 +1,14 @@
|
||||
package fr.azures04.modun.dto;
|
||||
|
||||
public class PublicKeyEntry {
|
||||
|
||||
private String publicKey;
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(String publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
}
|
||||
25
src/main/java/fr/azures04/modun/dto/PublicKeysResponse.java
Normal file
25
src/main/java/fr/azures04/modun/dto/PublicKeysResponse.java
Normal file
@ -0,0 +1,25 @@
|
||||
package fr.azures04.modun.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PublicKeysResponse {
|
||||
|
||||
private List<PublicKeyEntry> profilePropertyKeys;
|
||||
private List<PublicKeyEntry> authenticationKeys;
|
||||
|
||||
public List<PublicKeyEntry> getProfilePropertyKeys() {
|
||||
return profilePropertyKeys;
|
||||
}
|
||||
|
||||
public List<PublicKeyEntry> getAuthenticationKeys() {
|
||||
return authenticationKeys;
|
||||
}
|
||||
|
||||
public void setProfilePropertyKeys(List<PublicKeyEntry> profilePropertyKeys) {
|
||||
this.profilePropertyKeys = profilePropertyKeys;
|
||||
}
|
||||
|
||||
public void setAuthenticationKeys(List<PublicKeyEntry> authenticationKeys) {
|
||||
this.authenticationKeys = authenticationKeys;
|
||||
}
|
||||
}
|
||||
BIN
src/main/resources/yggdrasil_session_pubkey.der
Normal file
BIN
src/main/resources/yggdrasil_session_pubkey.der
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user